/* Copyright (c) 1995-2000, The Hypersonic SQL 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 Hypersonic SQL 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 THE HYPERSONIC SQL GROUP,
 * 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.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2008, 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.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.Locale;

import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.HashMappedList;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.StringUtil;
import org.hsqldb.lib.java.JavaSystem;
import org.hsqldb.persist.HsqlDatabaseProperties;
import org.hsqldb.scriptio.ScriptWriterBase;
import org.hsqldb.scriptio.ScriptWriterText;
import org.hsqldb.lib.StringConverter;

/**
 * Provides SQL Interpreter services relative to a Session and
 * its Database.
 *
 * The core functionality of this class was inherited from Hypersonic and
 * extensively rewritten and extended in successive versions of HSQLDB.
 *
 * @author Thomas Mueller (Hypersonic SQL Group)
 * @version 1.8.0
 * @since 1.7.2
 */

// fredt@users 20020221 - patch 513005 by sqlbob@users (RMP) - various corrections
// fredt@users 20020430 - patch 549741 by velichko - ALTER TABLE RENAME
// fredt@users 20020405 - patch 1.7.0 - other ALTER TABLE statements
// tony_lai@users 20020820 - patch 595099 - use user-defined PK name
// tony_lai@users 20020820 - patch 595156 - violation of constraint name
// fredt@users 20020912 - patch 1.7.1 by fredt - log alter statements
// kloska@users 20021030 - patch 1.7.2 - ON UPDATE CASCADE | SET NULL | SET DEFAULT
// kloska@users 20021112 - patch 1.7.2 - ON DELETE SET NULL | SET DEFAULT
// boucherb@users 20020310 - disable ALTER TABLE DDL on VIEWs (avoid NPE)
// fredt@users 20030314 - patch 1.7.2 by gilead@users - drop table if exists syntax
// boucherb@users 20030425 - DDL methods are moved to DatabaseCommandInterpreter.java
// boucherb@users 20030425 - refactoring DDL methods into smaller units
// fredt@users 20030609 - support for ALTER COLUMN SET/DROP DEFAULT / RENAME TO
// wondersonic@users 20031205 - IF EXISTS support for DROP INDEX
// fredt@users 20031224 - support for CREATE SEQUENCE ...
// fredt@users 20041209 - patch by tytar@users to set default table type
class DatabaseCommandInterpreter {

    private Tokenizer tokenizer = new Tokenizer();
    private Database  database;
    private Session   session;

    /**
     * Constructs a new DatabaseCommandInterpreter for the given Session
     *
     * @param s session
     */
    DatabaseCommandInterpreter(Session s) {
        session  = s;
        database = s.getDatabase();
    }

    /**
     * Executes the SQL String. This method is always called from a block
     * synchronized on the database object.
     *
     * @param sql query
     * @return the result of executing the given SQL String
     */
    Result execute(String sql) {

        Result result;
        String token;
        int    cmd;

        JavaSystem.gc();

        result = null;
        cmd    = Token.UNKNOWNTOKEN;

        try {
            tokenizer.reset(sql);

            while (true) {
                tokenizer.setPartMarker();
                session.setScripting(false);

                token = tokenizer.getSimpleToken();

                if (token.length() == 0) {
                    session.endSchemaDefinition();

                    break;
                }

                cmd = Token.get(token);

                if (cmd == Token.SEMICOLON) {
                    session.endSchemaDefinition();

                    continue;
                }

                result = executePart(cmd, token);

                if (result.isError()) {
                    session.endSchemaDefinition();

                    break;
                }

                if (session.getScripting()) {
                    database.logger.writeToLog(session,
                                               tokenizer.getLastPart());
                }
            }
        } catch (Throwable t) {
            try {
                if (session.isSchemaDefintion()) {
                    HsqlName schemaName = session.getSchemaHsqlName(null);

                    database.schemaManager.dropSchema(schemaName.name, true);
                    database.logger.writeToLog(session,
                                               Token.T_DROP + ' '
                                               + Token.T_SCHEMA + ' '
                                               + schemaName.statementName
                                               + ' ' + Token.T_CASCADE);
                    session.endSchemaDefinition();
                }
            } catch (HsqlException e) {}

            result = new Result(t, tokenizer.getLastPart());
        }

        return result == null ? Session.emptyUpdateCount
                              : result;
    }

    private Result executePart(int cmd, String token) throws Throwable {

        Result result   = Session.emptyUpdateCount;
        int    brackets = 0;

        if (session.isSchemaDefintion()) {
            switch (cmd) {

                case Token.CREATE :
                case Token.GRANT :
                    break;

                default :
                    throw Trace.error(Trace.INVALID_IDENTIFIER,
                                      Trace.IN_SCHEMA_DEFINITION,
                                      new Object[]{ token });
            }
        }

        switch (cmd) {

            case Token.OPENBRACKET : {
                Parser parser = new Parser(session, database, tokenizer);

                brackets = parser.parseOpenBracketsSelect() + 1;
            }
            case Token.SELECT : {
                Parser parser = new Parser(session, database, tokenizer);
                CompiledStatement cStatement =
                    parser.compileSelectStatement(brackets);

                if (cStatement.parameters.length != 0) {
                    Trace.doAssert(
                        false,
                        Trace.getMessage(Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
                }

                result = session.sqlExecuteCompiledNoPreChecks(cStatement,
                        null);

                break;
            }
            case Token.INSERT : {
                Parser parser = new Parser(session, database, tokenizer);
                CompiledStatement cStatement = parser.compileInsertStatement();

                if (cStatement.parameters.length != 0) {
                    Trace.doAssert(
                        false,
                        Trace.getMessage(Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
                }

                result = session.sqlExecuteCompiledNoPreChecks(cStatement,
                        null);

                break;
            }
            case Token.UPDATE : {
                Parser parser = new Parser(session, database, tokenizer);
                CompiledStatement cStatement = parser.compileUpdateStatement();

                if (cStatement.parameters.length != 0) {
                    Trace.doAssert(
                        false,
                        Trace.getMessage(Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
                }

                result = session.sqlExecuteCompiledNoPreChecks(cStatement,
                        null);

                break;
            }
            case Token.DELETE : {
                Parser parser = new Parser(session, database, tokenizer);
                CompiledStatement cStatement = parser.compileDeleteStatement();

                if (cStatement.parameters.length != 0) {
                    Trace.doAssert(
                        false,
                        Trace.getMessage(Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
                }

                result = session.sqlExecuteCompiledNoPreChecks(cStatement,
                        null);

                break;
            }
            case Token.CALL : {
                Parser parser = new Parser(session, database, tokenizer);
                CompiledStatement cStatement = parser.compileCallStatement();

                if (cStatement.parameters.length != 0) {
                    Trace.doAssert(
                        false,
                        Trace.getMessage(Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
                }

                result = session.sqlExecuteCompiledNoPreChecks(cStatement,
                        null);

                break;
            }
            case Token.SET :
                processSet();
                break;

            case Token.COMMIT :
                processCommit();
                break;

            case Token.ROLLBACK :
                processRollback();
                break;

            case Token.SAVEPOINT :
                processSavepoint();
                break;

            case Token.RELEASE :
                processReleaseSavepoint();
                break;

            case Token.CREATE :
                processCreate();
                database.setMetaDirty(false);
                break;

            case Token.ALTER :
                processAlter();
                database.setMetaDirty(true);
                break;

            case Token.DROP :
                processDrop();
                database.setMetaDirty(true);
                break;

            case Token.GRANT :
                processGrantOrRevoke(true);
                database.setMetaDirty(false);
                break;

            case Token.REVOKE :
                processGrantOrRevoke(false);
                database.setMetaDirty(true);
                break;

            case Token.CONNECT :
                processConnect();
                database.setMetaDirty(false);
                session.setScripting(false);
                break;

            case Token.DISCONNECT :
                processDisconnect();
                session.setScripting(true);
                break;

            case Token.SCRIPT :
                result = processScript();
                break;

            case Token.SHUTDOWN :
                processShutdown();
                break;

            case Token.CHECKPOINT :
                processCheckpoint();
                break;

            case Token.EXPLAIN :
                result = processExplainPlan();
                break;

            default :
                throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
        }

        return result;
    }

    /**
     * Responsible for parsing and executing the SCRIPT SQL statement
     *
     * @return either an empty result or one in which each row is a DDL or DML
     * @throws IOException
     * @throws HsqlException
     */
    private Result processScript() throws IOException, HsqlException {

        String           token = tokenizer.getString();
        ScriptWriterText dsw   = null;

        session.checkAdmin();

        try {
            if (tokenizer.wasValue()) {
                if (tokenizer.getType() != Types.VARCHAR) {
                    throw Trace.error(Trace.INVALID_IDENTIFIER);
                }

                dsw = new ScriptWriterText(database, token, true, true, true);

                dsw.writeAll();

                return new Result(ResultConstants.UPDATECOUNT);
            } else {
                tokenizer.back();

                return DatabaseScript.getScript(database, false);
            }
        } finally {
            if (dsw != null) {
                dsw.close();
            }
        }
    }

    /**
     *  Responsible for handling CREATE ...
     *
     *  All CREATE command require an ADMIN user except: <p>
     *
     * <pre>
     * CREATE TEMP [MEMORY] TABLE
     * </pre>
     *
     * @throws  HsqlException
     */
    private void processCreate() throws HsqlException {

        boolean unique = false;
        int     tableType;
        boolean isTempTable = false;
        String  token;

        session.checkAdmin();
        session.checkDDLWrite();
        session.setScripting(true);

        if (tokenizer.isGetThis(Token.T_GLOBAL)) {
            tokenizer.getThis(Token.T_TEMPORARY);

            isTempTable = true;
        } else if (tokenizer.isGetThis(Token.T_TEMP)) {
            isTempTable = true;
        } else if (tokenizer.isGetThis(Token.T_TEMPORARY)) {
            isTempTable = true;
        }

        token = tokenizer.getSimpleToken();

        switch (Token.get(token)) {

            // table
            case Token.MEMORY :
                tokenizer.getThis(Token.T_TABLE);
            case Token.TABLE :
                tableType = isTempTable ? Table.TEMP_TABLE
                                        : database.getDefaultTableType();

                processCreateTable(tableType);

                return;

            case Token.CACHED :
                if (isTempTable) {
                    throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                }

                tokenizer.getThis(Token.T_TABLE);
                processCreateTable(Table.CACHED_TABLE);

                return;

            case Token.TEXT :
                if (isTempTable) {
                    throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                }

                tokenizer.getThis(Token.T_TABLE);
                processCreateTable(Table.TEXT_TABLE);

                return;

            default :
                if (isTempTable) {
                    throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                }
        }

        switch (Token.get(token)) {

            // other objects
            case Token.ALIAS :
                processCreateAlias();
                break;

            case Token.SEQUENCE :
                processCreateSequence();
                break;

            case Token.SCHEMA :
                session.setScripting(false);
                processCreateSchema();
                break;

            case Token.TRIGGER :
                processCreateTrigger();
                break;

            case Token.USER :
                processCreateUser();
                break;

            case Token.ROLE :
                database.getGranteeManager().addRole(getUserIdentifier());
                break;

            case Token.VIEW :
                processCreateView();
                break;

            // index
            case Token.UNIQUE :
                unique = true;

                tokenizer.getThis(Token.T_INDEX);

            //fall thru
            case Token.INDEX :
                processCreateIndex(unique);
                break;

            default : {
                throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
            }
        }
    }

    /**
     *  Process a bracketed column list as used in the declaration of SQL
     *  CONSTRAINTS and return an array containing the indexes of the columns
     *  within the table.
     *
     * @param  t table that contains the columns
     * @return  column index map
     * @throws  HsqlException if a column is not found or is duplicate
     */
    private int[] processColumnList(Table t,
                                    boolean acceptAscDesc)
                                    throws HsqlException {

        HashMappedList list = Parser.processColumnList(tokenizer,
            acceptAscDesc);
        int   size = list.size();
        int[] col  = new int[size];

        for (int i = 0; i < size; i++) {
            col[i] = t.getColumnNr((String) list.getKey(i));
        }

        return col;
    }

    /**
     *  Responsible for handling the execution of CREATE TRIGGER SQL
     *  statements. <p>
     *
     *  typical sql is: CREATE TRIGGER tr1 AFTER INSERT ON tab1 CALL "pkg.cls"
     *
     * @throws HsqlException
     */
    private void processCreateTrigger() throws HsqlException {

        Table      t;
        boolean    isForEach;
        boolean    isNowait;
        int        queueSize;
        String     triggerName;
        boolean    isQuoted;
        String     sWhen;
        String     sOper;
        String     tableName;
        String     token;
        String     className;
        TriggerDef td;
        Trigger    o;

        triggerName = tokenizer.getName();

        String schemaname = tokenizer.getLongNameFirst();

        database.schemaManager.checkTriggerExists(triggerName,
                session.getSchemaNameForWrite(schemaname), false);

        isQuoted  = tokenizer.wasQuotedIdentifier();
        isForEach = false;
        isNowait  = false;
        queueSize = TriggerDef.getDefaultQueueSize();
        sWhen     = tokenizer.getSimpleToken();
        sOper     = tokenizer.getSimpleToken();

        tokenizer.getThis(Token.T_ON);

        tableName = tokenizer.getName();

        if (schemaname == null) {
            schemaname =
                session.getSchemaNameForWrite(tokenizer.getLongNameFirst());
        } else if (!schemaname.equals(
                session.getSchemaNameForWrite(tokenizer.getLongNameFirst()))) {
            throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
        }

        t = database.schemaManager.getUserTable(session, tableName,
                schemaname);

        if (t.isView()) {
            throw Trace.error(Trace.NOT_A_TABLE);
        }

        session.setScripting(true);

        // "FOR EACH ROW" or "CALL"
        token = tokenizer.getSimpleToken();

        if (token.equals(Token.T_FOR)) {
            token = tokenizer.getSimpleToken();

            if (token.equals(Token.T_EACH)) {
                token = tokenizer.getSimpleToken();

                if (token.equals(Token.T_ROW)) {
                    isForEach = true;

                    // should be 'NOWAIT' or 'QUEUE' or 'CALL'
                    token = tokenizer.getSimpleToken();
                } else {
                    throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, token);
                }
            } else {
                throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, token);
            }
        }

        if (token.equals(Token.T_NOWAIT)) {
            isNowait = true;

            // should be 'CALL' or 'QUEUE'
            token = tokenizer.getSimpleToken();
        }

        if (token.equals(Token.T_QUEUE)) {
            queueSize = tokenizer.getInt();

            // should be 'CALL'
            token = tokenizer.getSimpleToken();
        }

        if (!token.equals(Token.T_CALL)) {
            throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, token);
        }

        className = tokenizer.getSimpleName();

        if (!tokenizer.wasQuotedIdentifier()) {
            throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, className);
        }

        HsqlName name = database.nameManager.newHsqlName(triggerName,
            isQuoted);

        td = new TriggerDef(name, sWhen, sOper, isForEach, t, className,
                            isNowait, queueSize, database.classLoader);

        t.addTrigger(td);

        if (td.isValid()) {
            try {

                // start the trigger thread
                td.start();
            } catch (Exception e) {
                throw Trace.error(Trace.UNKNOWN_FUNCTION, e.toString());
            }
        }

        database.schemaManager.registerTriggerName(triggerName, t.getName());

// --
    }

    private Column processCreateColumn() throws HsqlException {

        String   token    = tokenizer.getSimpleName();
        boolean  isQuoted = tokenizer.wasQuotedIdentifier();
        HsqlName hsqlName = database.nameManager.newHsqlName(token, isQuoted);

        return processCreateColumn(hsqlName);
    }

    /**
     *  Responsible for handling the creation of table columns during the
     *  process of executing CREATE TABLE DDL statements.
     *
     *  @param  hsqlName name of the column
     *  @return a Column object with indicated attributes
     *  @throws  HsqlException
     */
    private Column processCreateColumn(HsqlName hsqlName)
    throws HsqlException {

        boolean    isIdentity        = false;
        long       identityStart     = database.firstIdentity;
        long       identityIncrement = 1;
        boolean    isPrimaryKey      = false;
        String     typeName;
        int        type;
        int        length      = 0;
        int        scale       = 0;
        boolean    hasLength   = false;
        boolean    isNullable  = true;
        Expression defaultExpr = null;
        String     token;

        typeName = tokenizer.getSimpleToken();
        type     = Types.getTypeNr(typeName);

        if (type == Types.CHAR) {
            if (tokenizer.isGetThis(Token.T_VARYING)) {
                type = Types.VARCHAR;
            }
        }

        if (typeName.equals(Token.T_IDENTITY)) {
            isIdentity   = true;
            isPrimaryKey = true;
        }

        // fredt - when SET IGNORECASE is in effect, all new VARCHAR columns are defined as VARCHAR_IGNORECASE
        if (type == Types.DOUBLE) {
            tokenizer.isGetThis(Token.T_PRECISION);
        }

        if (tokenizer.isGetThis(Token.T_OPENBRACKET)) {
            hasLength = true;
            length    = tokenizer.getInt();

            Trace.check(Types.acceptsPrecisionCreateParam(type),
                        Trace.UNEXPECTED_TOKEN);

            if (type != Types.TIMESTAMP && type != Types.TIME && length == 0) {
                throw Trace.error(Trace.INVALID_SIZE_PRECISION);
            }

            if (tokenizer.isGetThis(Token.T_COMMA)) {
                Trace.check(Types.acceptsScaleCreateParam(type),
                            Trace.UNEXPECTED_TOKEN);

                scale = tokenizer.getInt();
            }

            tokenizer.getThis(Token.T_CLOSEBRACKET);
        } else if (type == Types.CHAR && database.sqlEnforceStrictSize) {
            length = 1;
        } else if (type == Types.VARCHAR && database.sqlEnforceStrictSize) {
            throw Trace.error(Trace.COLUMN_SIZE_REQUIRED);
        }

        /**
         * @todo fredt - drop support for SET IGNORECASE and replace the
         * type name with a qualifier specifying the case sensitivity of VARCHAR
         */
        if (type == Types.VARCHAR && database.isIgnoreCase()) {
            type = Types.VARCHAR_IGNORECASE;
        }

        if (type == Types.FLOAT && length > 53) {
            throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
        }

        if (type == Types.TIMESTAMP) {
            if (!hasLength) {
                length = 6;
            } else if (length != 0 && length != 6) {
                throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
            }
        }

        if (type == Types.TIME) {
            if (length != 0) {
                throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
            }
        }

        token = tokenizer.getSimpleToken();

        if (token.equals(Token.T_DEFAULT)) {
            defaultExpr = processCreateDefaultExpression(type, length, scale);
            token       = tokenizer.getSimpleToken();
        } else if (token.equals(Token.T_GENERATED)) {
            tokenizer.getThis(Token.T_BY);
            tokenizer.getThis(Token.T_DEFAULT);
            tokenizer.getThis(Token.T_AS);
            tokenizer.getThis(Token.T_IDENTITY);

            if (tokenizer.isGetThis(Token.T_OPENBRACKET)) {
                tokenizer.getThis(Token.T_START);
                tokenizer.getThis(Token.T_WITH);

                identityStart = tokenizer.getBigint();

                if (tokenizer.isGetThis(Token.T_COMMA)) {
                    tokenizer.getThis(Token.T_INCREMENT);
                    tokenizer.getThis(Token.T_BY);

                    identityIncrement = tokenizer.getBigint();
                }

                tokenizer.getThis(Token.T_CLOSEBRACKET);
            }

            isIdentity   = true;
            isPrimaryKey = true;
            token        = tokenizer.getSimpleToken();
        }

        // fredt@users - accept IDENTITY before or after NOT NULL
        if (token.equals(Token.T_IDENTITY)) {
            isIdentity   = true;
            isPrimaryKey = true;
            token        = tokenizer.getSimpleToken();
        }

        if (token.equals(Token.T_NULL)) {
            token = tokenizer.getSimpleToken();
        } else if (token.equals(Token.T_NOT)) {
            tokenizer.getThis(Token.T_NULL);

            isNullable = false;
            token      = tokenizer.getSimpleToken();
        }

        if (token.equals(Token.T_IDENTITY)) {
            if (isIdentity) {
                throw Trace.error(Trace.SECOND_PRIMARY_KEY, Token.T_IDENTITY);
            }

            isIdentity   = true;
            isPrimaryKey = true;
            token        = tokenizer.getSimpleToken();
        }

        if (token.equals(Token.T_PRIMARY)) {
            tokenizer.getThis(Token.T_KEY);

            isPrimaryKey = true;
        } else {
            tokenizer.back();
        }

        // make sure IDENTITY and DEFAULT are not used together
        if (isIdentity && defaultExpr != null) {
            throw Trace.error(Trace.UNEXPECTED_TOKEN, Token.T_DEFAULT);
        }

        Column column = new Column(hsqlName, isNullable, type, length, scale,
                                   isPrimaryKey, defaultExpr);

        column.setIdentity(isIdentity, identityStart, identityIncrement);

        return column;
    }

    /**
     * @param type data type of column
     * @param length maximum length of column
     * @throws HsqlException
     * @return new Expression
     */
    private Expression processCreateDefaultExpression(int type, int length,
            int scale) throws HsqlException {

        if (type == Types.OTHER) {
            throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE);
        }

        Parser     parser = new Parser(session, database, tokenizer);
        Expression expr   = parser.readDefaultClause(type);

        expr.resolveTypes(session);

        int newType = expr.getType();

        if (newType == Expression.VALUE || newType == Expression.TRUE
                || newType == Expression.FALSE
                || (newType == Expression.FUNCTION
                    && expr.function.isSimple)) {
            Object defValTemp;

            try {
                defValTemp = expr.getValue(session, type);
            } catch (HsqlException e) {
                throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE);
            }

            if (defValTemp != null && database.sqlEnforceStrictSize) {
                try {
                    Column.enforceSize(defValTemp, type, length, scale, true);
                } catch (HsqlException e) {

                    // default value is too long for fixed size column
                    throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE);
                }
            }

            return expr;
        }

        throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE);
    }

    public static void checkBooleanDefault(String s,
                                           int type) throws HsqlException {

        if (type != Types.BOOLEAN || s == null) {
            return;
        }

        s = s.toUpperCase();

        if (s.equals(Token.T_TRUE) || s.equals(Token.T_FALSE)) {
            return;
        }

        if (s.equals("0") || s.equals("1")) {
            return;
        }

        throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE, s);
    }

    /**
     * Responsible for handling constraints section of CREATE TABLE ...
     *
     * @param t table
     * @param constraint CONSTRAINT keyword used
     * @param primarykeycolumn primary columns
     * @throws HsqlException
     * @return list of constraints
     */
    private HsqlArrayList processCreateConstraints(Table t,
            boolean constraint, int[] primarykeycolumn) throws HsqlException {

        String        token;
        HsqlArrayList tcList;
        Constraint    tempConst;
        HsqlName      pkHsqlName;

// fredt@users 20020225 - comment
// HSQLDB relies on primary index to be the first one defined
// and needs original or system added primary key before any
// non-unique index is created
        tcList = new HsqlArrayList();
        tempConst = new Constraint(null, primarykeycolumn, null, null,
                                   Constraint.MAIN, Constraint.NO_ACTION,
                                   Constraint.NO_ACTION);

// tony_lai@users 20020820 - patch 595099
        pkHsqlName = null;

        tcList.add(tempConst);

        if (!constraint) {
            return tcList;
        }

        while (true) {
            HsqlName cname = null;

            if (tokenizer.isGetThis(Token.T_CONSTRAINT)) {
                token = tokenizer.getName();

                String constraintSchema = tokenizer.getLongNameFirst();

                if (constraintSchema != null) {
                    constraintSchema = session.getSchemaNameForWrite(
                        tokenizer.getLongNameFirst());

                    if (!t.getSchemaName().equals(constraintSchema)) {
                        throw Trace.error(
                            Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS,
                            constraintSchema);
                    }
                }

                cname = database.nameManager.newHsqlName(token,
                        tokenizer.wasQuotedIdentifier());
            }

            token = tokenizer.getSimpleToken();

            switch (Token.get(token)) {

                case Token.PRIMARY : {
                    tokenizer.getThis(Token.T_KEY);

                    // tony_lai@users 20020820 - patch 595099
                    pkHsqlName = cname;

                    int[]      cols = processColumnList(t, false);
                    Constraint mainConst;

                    mainConst = (Constraint) tcList.get(0);

                    if (mainConst.core.mainColArray != null) {
                        if (!ArrayUtil.areEqual(mainConst.core.mainColArray,
                                                cols, cols.length, true)) {
                            throw Trace.error(Trace.SECOND_PRIMARY_KEY);
                        }
                    }

                    mainConst.core.mainColArray = cols;
                    mainConst.constName         = pkHsqlName;

                    break;
                }
                case Token.UNIQUE : {
                    int[] col = processColumnList(t, false);

                    if (cname == null) {
                        cname = database.nameManager.newAutoName("CT");
                    }

                    tempConst = new Constraint(cname, col, null, null,
                                               Constraint.UNIQUE,
                                               Constraint.NO_ACTION,
                                               Constraint.NO_ACTION);

                    tcList.add(tempConst);

                    break;
                }
                case Token.FOREIGN : {
                    tokenizer.getThis(Token.T_KEY);

                    tempConst = processCreateFK(t, cname);

                    if (tempConst.core.refColArray == null) {
                        Constraint mainConst = (Constraint) tcList.get(0);

                        tempConst.core.refColArray =
                            mainConst.core.mainColArray;

                        if (tempConst.core.refColArray == null) {
                            throw Trace.error(Trace.CONSTRAINT_NOT_FOUND,
                                              Trace.TABLE_HAS_NO_PRIMARY_KEY);
                        }
                    }

                    checkFKColumnDefaults(t, tempConst);
                    t.checkColumnsMatch(tempConst.core.mainColArray,
                                        tempConst.core.refTable,
                                        tempConst.core.refColArray);
                    tcList.add(tempConst);

                    break;
                }
                case Token.CHECK : {
                    if (cname == null) {
                        cname = database.nameManager.newAutoName("CT");
                    }

                    tempConst = new Constraint(cname, null, null, null,
                                               Constraint.CHECK,
                                               Constraint.NO_ACTION,
                                               Constraint.NO_ACTION);

                    processCreateCheckConstraintCondition(tempConst);
                    tcList.add(tempConst);

                    break;
                }
            }

            token = tokenizer.getSimpleToken();

            if (token.equals(Token.T_COMMA)) {
                continue;
            }

            if (token.equals(Token.T_CLOSEBRACKET)) {
                break;
            }

            throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
        }

        return tcList;
    }

    /**
     * Responsible for handling check constraints section of CREATE TABLE ...
     *
     * @param c check constraint
     * @throws HsqlException
     */
    private void processCreateCheckConstraintCondition(Constraint c)
    throws HsqlException {

        tokenizer.getThis(Token.T_OPENBRACKET);

        Parser     parser    = new Parser(session, database, tokenizer);
        Expression condition = parser.parseExpression();

        tokenizer.getThis(Token.T_CLOSEBRACKET);

        c.core.check = condition;
    }

    /**
     * Responsible for handling the execution CREATE TABLE SQL statements.
     *
     * @param type Description of the Parameter
     * @throws HsqlException
     */
    private void processCreateTable(int type) throws HsqlException {

        String token = tokenizer.getName();
        HsqlName schemaname =
            session.getSchemaHsqlNameForWrite(tokenizer.getLongNameFirst());

        database.schemaManager.checkUserTableNotExists(session, token,
                schemaname.name);

        boolean isnamequoted = tokenizer.wasQuotedIdentifier();
        int[]   pkCols       = null;
        int     colIndex     = 0;
        boolean constraint   = false;
        Table   t            = newTable(type, token, isnamequoted, schemaname);

        tokenizer.getThis(Token.T_OPENBRACKET);

        while (true) {
            token = tokenizer.getString();

            switch (Token.get(token)) {

                case Token.CONSTRAINT :
                case Token.PRIMARY :
                case Token.FOREIGN :
                case Token.UNIQUE :
                case Token.CHECK :

                    // fredt@users : check for quoted reserved words used as column names
                    constraint = !tokenizer.wasQuotedIdentifier()
                                 && !tokenizer.wasLongName();
            }

            tokenizer.back();

            if (constraint) {
                break;
            }

            Column newcolumn = processCreateColumn();

            t.addColumn(newcolumn);

            if (newcolumn.isPrimaryKey()) {
                Trace.check(pkCols == null, Trace.SECOND_PRIMARY_KEY,
                            newcolumn.columnName.name);

                pkCols = new int[]{ colIndex };
            }

            token = tokenizer.getSimpleToken();

            if (token.equals(Token.T_COMMA)) {
                colIndex++;

                continue;
            }

            if (token.equals(Token.T_CLOSEBRACKET)) {
                break;
            }

            throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
        }

        HsqlArrayList tempConstraints = processCreateConstraints(t,
            constraint, pkCols);

        if (tokenizer.isGetThis(Token.T_ON)) {
            if (!t.isTemp) {
                throw Trace.error(Trace.UNEXPECTED_TOKEN, Token.T_ON);
            }

            tokenizer.getThis(Token.T_COMMIT);

            token = tokenizer.getSimpleToken();

            if (token.equals(Token.T_DELETE)) {}
            else if (token.equals(Token.T_PRESERVE)) {
                t.onCommitPreserve = true;
            } else {
                throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
            }

            tokenizer.getThis(Token.T_ROWS);
        }

        try {
            session.commit();

            Constraint primaryConst = (Constraint) tempConstraints.get(0);

            t.createPrimaryKey(null, primaryConst.core.mainColArray, true);

            if (primaryConst.core.mainColArray != null) {
                if (primaryConst.constName == null) {
                    primaryConst.constName = t.makeSysPKName();
                }

                Constraint newconstraint =
                    new Constraint(primaryConst.constName, t,
                                   t.getPrimaryIndex(),
                                   Constraint.PRIMARY_KEY);

                t.addConstraint(newconstraint);
                database.schemaManager.registerConstraintName(
                    primaryConst.constName.name, t.getName());
            }

            for (int i = 1; i < tempConstraints.size(); i++) {
                Constraint tempConst = (Constraint) tempConstraints.get(i);

                if (tempConst.constType == Constraint.UNIQUE) {
                    TableWorks tableWorks = new TableWorks(session, t);

                    tableWorks.createUniqueConstraint(
                        tempConst.core.mainColArray, tempConst.constName);

                    t = tableWorks.getTable();
                }

                if (tempConst.constType == Constraint.FOREIGN_KEY) {
                    TableWorks tableWorks = new TableWorks(session, t);

                    tableWorks.createForeignKey(tempConst.core.mainColArray,
                                                tempConst.core.refColArray,
                                                tempConst.constName,
                                                tempConst.core.refTable,
                                                tempConst.core.deleteAction,
                                                tempConst.core.updateAction);

                    t = tableWorks.getTable();
                }

                if (tempConst.constType == Constraint.CHECK) {
                    TableWorks tableWorks = new TableWorks(session, t);

                    tableWorks.createCheckConstraint(tempConst,
                                                     tempConst.constName);

                    t = tableWorks.getTable();
                }
            }

            database.schemaManager.linkTable(t);
        } catch (HsqlException e) {

// fredt@users 20020225 - comment
// if a HsqlException is thrown while creating table, any foreign key that has
// been created leaves it modification to the expTable in place
// need to undo those modifications. This should not happen in practice.
            database.schemaManager.removeExportedKeys(t);
            database.schemaManager.removeIndexNames(t.tableName);
            database.schemaManager.removeConstraintNames(t.tableName);

            throw e;
        }
    }

// fredt@users 20020221 - patch 520213 by boucherb@users - self reference FK
// allows foreign keys that reference a column in the same table

    /**
     * @param t table
     * @param cname foreign key name
     * @throws HsqlException
     * @return constraint
     */
    private Constraint processCreateFK(Table t,
                                       HsqlName cname) throws HsqlException {

        int[]  localcol;
        int[]  expcol;
        String expTableName;
        Table  expTable;
        String token;

        localcol = processColumnList(t, false);

        tokenizer.getThis(Token.T_REFERENCES);

        expTableName = tokenizer.getName();

        String constraintSchema = tokenizer.getLongNameFirst();

        if (constraintSchema != null) {
            constraintSchema =
                session.getSchemaNameForWrite(tokenizer.getLongNameFirst());

            if (!t.getSchemaName().equals(constraintSchema)) {
                throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS,
                                  constraintSchema);
            }
        }

        if (t.getName().name.equals(expTableName)) {
            expTable = t;
        } else {
            expTable = database.schemaManager.getTable(session, expTableName,
                    t.getSchemaName());
        }

        expcol = null;
        token  = tokenizer.getSimpleToken();

        tokenizer.back();

        if (token.equals(Token.T_OPENBRACKET)) {
            expcol = processColumnList(expTable, false);
        } else {
            if (expTable.getPrimaryKey() == null) {

                // getPrimaryKey() == null is true while creating the table
                // fredt - FK statement is part of CREATE TABLE and is self-referencing
                // reference must be to same table being created
                // it is resolved in the calling method
                Trace.check(t == expTable, Trace.TABLE_HAS_NO_PRIMARY_KEY);
            } else {
                if (expTable.hasPrimaryKey()) {
                    expcol = expTable.getPrimaryKey();
                } else {
                    throw Trace.error(Trace.CONSTRAINT_NOT_FOUND,
                                      Trace.TABLE_HAS_NO_PRIMARY_KEY);
                }
            }
        }

        token = tokenizer.getSimpleToken();

        // -- In a while loop we parse a maximium of two
        // -- "ON" statements following the foreign key
        // -- definition this can be
        // -- ON [UPDATE|DELETE] [NO ACTION|RESTRICT|CASCADE|SET [NULL|DEFAULT]]
        int deleteAction = Constraint.NO_ACTION;
        int updateAction = Constraint.NO_ACTION;

        while (token.equals(Token.T_ON)) {
            token = tokenizer.getSimpleToken();

            if (deleteAction == Constraint.NO_ACTION
                    && token.equals(Token.T_DELETE)) {
                token = tokenizer.getSimpleToken();

                if (token.equals(Token.T_SET)) {
                    token = tokenizer.getSimpleToken();

                    if (token.equals(Token.T_DEFAULT)) {
                        deleteAction = Constraint.SET_DEFAULT;
                    } else if (token.equals(Token.T_NULL)) {
                        deleteAction = Constraint.SET_NULL;
                    } else {
                        throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                    }
                } else if (token.equals(Token.T_CASCADE)) {
                    deleteAction = Constraint.CASCADE;
                } else if (token.equals(Token.T_RESTRICT)) {

                    // LEGACY compatibility/usability
                    // - same as NO ACTION or nothing at all
                } else {
                    tokenizer.matchThis(Token.T_NO);
                    tokenizer.getThis(Token.T_ACTION);
                }
            } else if (updateAction == Constraint.NO_ACTION
                       && token.equals(Token.T_UPDATE)) {
                token = tokenizer.getSimpleToken();

                if (token.equals(Token.T_SET)) {
                    token = tokenizer.getSimpleToken();

                    if (token.equals(Token.T_DEFAULT)) {
                        updateAction = Constraint.SET_DEFAULT;
                    } else if (token.equals(Token.T_NULL)) {
                        updateAction = Constraint.SET_NULL;
                    } else {
                        throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                    }
                } else if (token.equals(Token.T_CASCADE)) {
                    updateAction = Constraint.CASCADE;
                } else if (token.equals(Token.T_RESTRICT)) {

                    // LEGACY compatibility/usability
                    // - same as NO ACTION or nothing at all
                } else {
                    tokenizer.matchThis(Token.T_NO);
                    tokenizer.getThis(Token.T_ACTION);
                }
            } else {
                throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
            }

            token = tokenizer.getSimpleToken();
        }

        tokenizer.back();

        if (cname == null) {
            cname = database.nameManager.newAutoName("FK");
        }

        return new Constraint(cname, localcol, expTable, expcol,
                              Constraint.FOREIGN_KEY, deleteAction,
                              updateAction);
    }

    /**
     * Responsible for handling the execution CREATE VIEW SQL statements.
     *
     * @throws HsqlException
     */
    private void processCreateView() throws HsqlException {

        String name = tokenizer.getName();
        HsqlName schemaname =
            session.getSchemaHsqlNameForWrite(tokenizer.getLongNameFirst());
        int logposition = tokenizer.getPartMarker();

        database.schemaManager.checkUserViewNotExists(session, name,
                schemaname.name);

        HsqlName viewHsqlName = database.nameManager.newHsqlName(name,
            tokenizer.wasQuotedIdentifier());

        viewHsqlName.schema = schemaname;

        HsqlName[] colList = null;

        if (tokenizer.isGetThis(Token.T_OPENBRACKET)) {
            try {
                HsqlArrayList list = Parser.getColumnNames(database, null,
                    tokenizer, true);

                colList = new HsqlName[list.size()];
                colList = (HsqlName[]) list.toArray(colList);
            } catch (HsqlException e) {

                // fredt - a bug in 1.8.0.0 and previous versions causes view
                // definitions to script without double quotes around column names
                // in certain cases; the workaround here discards the column
                // names
                if (database.isStoredFileAccess()
                        && session.isProcessingScript()) {
                    while (true) {
                        String token = tokenizer.getString();

                        if (token.equals(Token.T_CLOSEBRACKET)
                                || token.equals("")) {
                            break;
                        }
                    }
                } else {
                    throw e;
                }
            }
        }

        tokenizer.getThis(Token.T_AS);
        tokenizer.setPartMarker();

        Parser parser   = new Parser(session, database, tokenizer);
        int    brackets = parser.parseOpenBracketsSelect();
        Select select;

        // accept ORDER BY or ORDRY BY with LIMIT - accept unions
        select = parser.parseSelect(brackets, true, false, true, true);

        if (select.sIntoTable != null) {
            throw (Trace.error(Trace.INVALID_IDENTIFIER, Token.INTO));
        }

        select.prepareResult(session);

        View view = new View(session, database, viewHsqlName,
                             tokenizer.getLastPart(), colList);

        session.commit();
        database.schemaManager.linkTable(view);
        tokenizer.setPartMarker(logposition);
    }

    /**
     * Responsible for handling tail of ALTER TABLE ... RENAME ...
     * @param t table
     * @throws HsqlException
     */
    private void processAlterTableRename(Table t) throws HsqlException {

        String  schema = t.getSchemaName();
        String  newName;
        boolean isquoted;

        // ensures that if temp table, it also belongs to this session
/*
        if (!t.equals(session, name)) {
            throw Trace.error(Trace.TABLE_NOT_FOUND);
        }
*/
        tokenizer.getThis(Token.T_TO);

        newName = tokenizer.getName();

        String newSchema = tokenizer.getLongNameFirst();

        isquoted  = tokenizer.wasQuotedIdentifier();
        newSchema = newSchema == null ? schema
                                      : session.getSchemaNameForWrite(
                                          newSchema);

        if (!schema.equals(newSchema)) {
            throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
        }

        database.schemaManager.checkUserTableNotExists(session, newName,
                schema);
        session.commit();
        session.setScripting(true);
        database.schemaManager.renameTable(session, t, newName, isquoted);
    }

    /**
     * Handles ALTER TABLE statements. <p>
     *
     * ALTER TABLE <name> RENAME TO <newname>
     * ALTER INDEX <name> RENAME TO <newname>
     *
     * ALTER TABLE <name> ADD CONSTRAINT <constname> FOREIGN KEY (<col>, ...)
     * REFERENCE <other table> (<col>, ...) [ON DELETE CASCADE]
     *
     * ALTER TABLE <name> ADD CONSTRAINT <constname> UNIQUE (<col>, ...)
     *
     * @throws HsqlException
     */
    private void processAlter() throws HsqlException {

        String token;

        session.checkAdmin();
        session.checkDDLWrite();
        session.setScripting(true);

        token = tokenizer.getSimpleToken();

        switch (Token.get(token)) {

            case Token.INDEX : {
                processAlterIndex();

                break;
            }
            case Token.SCHEMA : {
                processAlterSchema();

                break;
            }
            case Token.SEQUENCE : {
                processAlterSequence();

                break;
            }
            case Token.TABLE : {
                processAlterTable();

                break;
            }
            case Token.USER : {
                processAlterUser();

                break;
            }
            default : {
                throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
            }
        }
    }

    /**
     * Handles ALTER TABLE DDL.
     *
     * @throws HsqlException
     */
    private void processAlterTable() throws HsqlException {

        String tableName = tokenizer.getName();
        String schema =
            session.getSchemaNameForWrite(tokenizer.getLongNameFirst());
        Table t = database.schemaManager.getUserTable(session, tableName,
            schema);
        String token;

        if (t.isView()) {
            throw Trace.error(Trace.NOT_A_TABLE);
        }

        session.setScripting(true);

        token = tokenizer.getSimpleToken();

        switch (Token.get(token)) {

            case Token.RENAME : {
                processAlterTableRename(t);

                return;
            }
            case Token.ADD : {
                HsqlName cname = null;

                if (tokenizer.isGetThis(Token.T_CONSTRAINT)) {
                    token = tokenizer.getName();

                    String constraintSchema = tokenizer.getLongNameFirst();

                    if (constraintSchema != null) {
                        constraintSchema = session.getSchemaNameForWrite(
                            tokenizer.getLongNameFirst());

                        if (!t.getSchemaName().equals(constraintSchema)) {
                            throw Trace.error(
                                Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS,
                                constraintSchema);
                        }
                    }

                    cname = database.nameManager.newHsqlName(token,
                            tokenizer.wasQuotedIdentifier());
                }

                token = tokenizer.getString();

                if (tokenizer.wasQuotedIdentifier()
                        && tokenizer.wasSimpleName()) {
                    tokenizer.back();
                    processAlterTableAddColumn(t);

                    return;
                }

                if (!tokenizer.wasSimpleToken()) {
                    throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                }

                switch (Token.get(token)) {

                    case Token.FOREIGN :
                        tokenizer.getThis(Token.T_KEY);
                        processAlterTableAddForeignKeyConstraint(t, cname);

                        return;

                    case Token.UNIQUE :
                        processAlterTableAddUniqueConstraint(t, cname);

                        return;

                    case Token.CHECK :
                        processAlterTableAddCheckConstraint(t, cname);

                        return;

                    case Token.PRIMARY :
                        tokenizer.getThis(Token.T_KEY);
                        processAlterTableAddPrimaryKey(t, cname);

                        return;

                    default :
                        if (cname != null) {
                            throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                        }

                        tokenizer.back();
                    case Token.COLUMN :
                        processAlterTableAddColumn(t);

                        return;
                }
            }
            case Token.DROP : {
                token = tokenizer.getString();

                if (tokenizer.wasQuotedIdentifier()
                        && tokenizer.wasSimpleName()) {
                    tokenizer.back();
                    processAlterTableDropColumn(t);

                    return;
                }

                if (!tokenizer.wasSimpleToken()) {
                    throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                }

                switch (Token.get(token)) {

                    case Token.PRIMARY :
                        tokenizer.getThis(Token.T_KEY);

                        if (t.hasPrimaryKey()) {
                            processAlterTableDropConstraint(
                                t, t.getPrimaryConstraint().getName().name);
                        } else {
                            throw Trace.error(Trace.CONSTRAINT_NOT_FOUND,
                                              Trace.TABLE_HAS_NO_PRIMARY_KEY,
                                              new Object[] {
                                "PRIMARY KEY", t.getName().name
                            });
                        }

                        return;

                    case Token.CONSTRAINT :
                        processAlterTableDropConstraint(t);

                        return;

                    default :
                        tokenizer.back();
                    case Token.COLUMN :
                        processAlterTableDropColumn(t);

                        return;
                }
            }
            case Token.ALTER : {
                tokenizer.isGetThis(Token.T_COLUMN);
                processAlterColumn(t);

                return;
            }
            default : {
                throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
            }
        }
    }

    /**
     * Handles ALTER COLUMN
     *
     * @param t table
     * @throws HsqlException
     */
    private void processAlterColumn(Table t) throws HsqlException {

        String columnName  = tokenizer.getSimpleName();
        int    columnIndex = t.getColumnNr(columnName);
        Column column      = t.getColumn(columnIndex);
        String token       = tokenizer.getSimpleToken();

        switch (Token.get(token)) {

            case Token.RENAME : {
                tokenizer.getThis(Token.T_TO);
                processAlterColumnRename(t, column);

                return;
            }
            case Token.DROP : {
                tokenizer.getThis(Token.T_DEFAULT);

                TableWorks tw = new TableWorks(session, t);

                tw.setColDefaultExpression(columnIndex, null);

                return;
            }
            case Token.SET : {

//4-8-2005 MarcH and HuugO ALTER TABLE <tablename> ALTER COLUMN <column name> SET [NOT] NULL support added
                token = tokenizer.getSimpleToken();

                if (token.equals(Token.T_NOT)) {
                    tokenizer.getThis(Token.T_NULL);

                    TableWorks tw = new TableWorks(session, t);

                    tw.setColNullability(column, false);
                } else if (token.equals(Token.T_NULL)) {
                    TableWorks tw = new TableWorks(session, t);

                    tw.setColNullability(column, true);
                } else if (token.equals(Token.T_DEFAULT)) {

                    //alter table alter column set default
                    TableWorks tw     = new TableWorks(session, t);
                    int        type   = column.getType();
                    int        length = column.getSize();
                    int        scale  = column.getScale();
                    Expression expr = processCreateDefaultExpression(type,
                        length, scale);

                    tw.setColDefaultExpression(columnIndex, expr);
                } else {
                    throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                }

                return;
            }
            case Token.RESTART : {
                tokenizer.getThis(Token.T_WITH);

                long identityStart = tokenizer.getBigint();
                int  id            = t.getIdentityColumn();

                if (id == -1) {
                    throw Trace.error(Trace.OPERATION_NOT_SUPPORTED);
                }

                t.identitySequence.reset(identityStart);

                return;
            }
            default : {
                tokenizer.back();
                processAlterColumnType(t, column);
            }
        }
    }

    private void processAlterColumnType(Table table,
                                        Column oldCol) throws HsqlException {

        Column     newCol = processCreateColumn(oldCol.columnName);
        TableWorks tw     = new TableWorks(session, table);

        tw.reTypeColumn(oldCol, newCol);
    }

    /**
     * Responsible for handling tail of ALTER COLUMN ... RENAME ...
     * @param t table
     * @param column column
     * @throws HsqlException
     */
    private void processAlterColumnRename(Table t,
                                          Column column) throws HsqlException {

        String  newName  = tokenizer.getSimpleName();
        boolean isquoted = tokenizer.wasQuotedIdentifier();

        if (t.findColumn(newName) > -1) {
            throw Trace.error(Trace.COLUMN_ALREADY_EXISTS, newName);
        }

        t.database.schemaManager.checkColumnIsInView(t,
                column.columnName.name);
        session.commit();
        session.setScripting(true);
        t.renameColumn(column, newName, isquoted);
    }

    /**
     * Handles ALTER INDEX.
     *
     * @throws HsqlException
     */
    private void processAlterIndex() throws HsqlException {

        // only the one supported operation, so far
        processAlterIndexRename();
    }

    private void processAlterSchema() throws HsqlException {

        // only the one supported operation, so far
        processAlterSchemaRename();
    }

    /**
     * Responsible for handling parse and execute of SQL DROP DDL
     *
     * @throws  HsqlException
     */
    private void processDrop() throws HsqlException {

        String  token;
        boolean isview;

        session.checkReadWrite();
        session.checkAdmin();
        session.setScripting(true);

        token  = tokenizer.getSimpleToken();
        isview = false;

        switch (Token.get(token)) {

            case Token.INDEX : {
                processDropIndex();

                break;
            }
            case Token.SCHEMA : {
                processDropSchema();

                break;
            }
            case Token.SEQUENCE : {
                processDropSequence();

                break;
            }
            case Token.TRIGGER : {
                processDropTrigger();

                break;
            }
            case Token.USER : {
                processDropUser();

                break;
            }
            case Token.ROLE : {
                database.getGranteeManager().dropRole(
                    tokenizer.getSimpleName());

                break;
            }
            case Token.VIEW : {
                isview = true;
            }    //fall thru
            case Token.TABLE : {
                processDropTable(isview);

                break;
            }
            default : {
                throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
            }
        }
    }

    /**
     *  Responsible for handling the execution of GRANT and REVOKE SQL
     *  statements.
     *
     * @param grant true if grant, false if revoke
     * @throws HsqlException
     */
    private void processGrantOrRevoke(boolean grant) throws HsqlException {

        int    right;
        Object accessKey;
        String token;

        session.checkAdmin();
        session.checkDDLWrite();
        session.setScripting(true);

        right = 0;
        token = tokenizer.getSimpleToken();

        tokenizer.back();

        if (!GranteeManager.validRightString(token)) {
            processRoleGrantOrRevoke(grant);

            return;
        }

        do {
            token = tokenizer.getSimpleToken();
            right |= GranteeManager.getCheckRight(token);
        } while (tokenizer.isGetThis(Token.T_COMMA));

        tokenizer.getThis(Token.T_ON);

        accessKey = null;

        if (tokenizer.isGetThis(Token.T_CLASS)) {
            accessKey = tokenizer.getSimpleName();

            if (!tokenizer.wasQuotedIdentifier()) {
                throw Trace.error(Trace.QUOTED_IDENTIFIER_REQUIRED);
            }
        } else {
            token = tokenizer.getName();

            String schema =
                session.getSchemaName(tokenizer.getLongNameFirst());
            Table t = database.schemaManager.getTable(session, token, schema);

            accessKey = t.getName();

            session.setScripting(true);
        }

        tokenizer.getThis(grant ? Token.T_TO
                                : Token.T_FROM);

        token = getUserIdentifier();

        GranteeManager gm = database.getGranteeManager();

        if (grant) {
            gm.grant(token, accessKey, right);
        } else {
            gm.revoke(token, accessKey, right);
        }
    }

    /**
     * Responsible for handling CONNECT
     *
     * @throws HsqlException
     */
    private void processConnect() throws HsqlException {

        String userName;
        String password;
        User   user;

        tokenizer.getThis(Token.T_USER);

        userName = getUserIdentifier();

        if (tokenizer.isGetThis(Token.T_PASSWORD)) {

            // legacy log statement or connect statement issued by user
            password = getPassword();
            user     = database.getUserManager().getUser(userName, password);

            session.commit();
            session.setUser(user);
            database.logger.logConnectUser(session);
        } else if (session.isProcessingLog) {

            // processing log statement
            // do not change the user, as isSys() must remain true when processing log
            session.commit();
        } else {

            // force throw if not log statement
            tokenizer.getThis(Token.T_PASSWORD);
        }
    }

    /**
     * Responsible for handling the execution of SET SQL statements
     *
     * @throws  HsqlException
     */
    private void processSet() throws HsqlException {

        String token;

        session.setScripting(true);

        token = tokenizer.getSimpleToken();

        switch (Token.get(token)) {

            case Token.PROPERTY : {
                HsqlDatabaseProperties p;

                session.checkAdmin();

                token = tokenizer.getSimpleName();

                if (!tokenizer.wasQuotedIdentifier()) {
                    throw Trace.error(Trace.QUOTED_IDENTIFIER_REQUIRED);
                }

                p = database.getProperties();

                boolean isboolean  = p.isBoolean(token);
                boolean isintegral = p.isIntegral(token);
                boolean isstring   = p.isString(token);

                Trace.check(isboolean || isintegral || isstring,
                            Trace.ACCESS_IS_DENIED, token);

                int    type  = isboolean ? Types.BOOLEAN
                                         : isintegral ? Types.INTEGER
                                                      : Types.VARCHAR;
                Object value = tokenizer.getInType(type);

                if (HsqlDatabaseProperties.hsqldb_cache_file_scale.equals(
                        token)) {
                    if (database.logger.hasCache()
                            || ((Integer) value).intValue() != 8) {
                        Trace.throwerror(Trace.ACCESS_IS_DENIED, token);
                    }
                }

                p.setDatabaseProperty(token, value.toString().toLowerCase());
                p.setDatabaseVariables();

                break;
            }
            case Token.SCHEMA : {
                session.setScripting(false);
                session.setSchema(tokenizer.getSimpleName());

                break;
            }
            case Token.PASSWORD : {
                session.checkDDLWrite();
                session.getUser().setPassword(getPassword());

                break;
            }
            case Token.READONLY : {
                session.commit();
                session.setReadOnly(processTrueOrFalse());

                break;
            }
            case Token.LOGSIZE : {
                session.checkAdmin();
                session.checkDDLWrite();

                int i = tokenizer.getInt();

                database.logger.setLogSize(i);

                break;
            }
            case Token.SCRIPTFORMAT : {
                session.checkAdmin();
                session.checkDDLWrite();
                session.setScripting(false);

                token = tokenizer.getSimpleToken();

                int i = ArrayUtil.find(ScriptWriterBase.LIST_SCRIPT_FORMATS,
                                       token);

                if (i == 0 || i == 1 || i == 3) {
                    database.logger.setScriptType(i);
                } else {
                    throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                }

                break;
            }
            case Token.IGNORECASE : {
                session.checkAdmin();
                session.checkDDLWrite();
                database.setIgnoreCase(processTrueOrFalse());

                break;
            }
            case Token.MAXROWS : {
                session.setScripting(false);

                int i = tokenizer.getInt();

                session.setSQLMaxRows(i);

                break;
            }
            case Token.AUTOCOMMIT : {
                session.setAutoCommit(processTrueOrFalse());

                break;
            }
            case Token.TABLE : {
                session.checkAdmin();
                session.checkDDLWrite();

                token = tokenizer.getName();

                String schema = session.getSchemaNameForWrite(
                    tokenizer.getLongNameFirst());
                Table t = database.schemaManager.getTable(session, token,
                    schema);

                token = tokenizer.getSimpleToken();

                session.setScripting(true);

                switch (Token.get(token)) {

                    default : {
                        throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                    }
                    case Token.SOURCE : {
                        session.checkAdmin();

                        // SET TABLE <table> SOURCE HEADER
                        if (tokenizer.isGetThis(Token.T_HEADER)) {
                            token = tokenizer.getString();

                            if (!tokenizer.wasQuotedIdentifier()) {
                                throw Trace.error(Trace.TEXT_TABLE_SOURCE);
                            }

                            try {
                                t.setHeader(token);
                            } catch (Throwable e) {
                                if (session.isProcessingLog()
                                        || session.isProcessingScript()) {
                                    HsqlException warning =
                                        Trace.error(Trace.GENERAL_IO_ERROR,
                                                    e.getMessage());

                                    session.addWarning(warning);

                                    // add an entry to applog too
                                } else {
                                    if (e instanceof HsqlException) {
                                        throw (HsqlException) e;
                                    } else {
                                        throw Trace.error(
                                            Trace.GENERAL_IO_ERROR,
                                            e.getMessage());
                                    }
                                }
                            }

                            break;
                        }

                        // SET TABLE <table> SOURCE ON
                        if (tokenizer.isGetThis(Token.T_ON)) {
                            t.connect(session);
                            database.setMetaDirty(false);

                            break;
                        }

                        // SET TABLE <table> SOURCE OFF
                        if (tokenizer.isGetThis(Token.T_OFF)) {
                            t.disconnect(session);
                            database.setMetaDirty(false);

                            break;
                        }

                        // SET TABLE <table> SOURCE <source>
                        token = tokenizer.getString();

                        if (!tokenizer.wasQuotedIdentifier()) {
                            throw Trace.error(Trace.TEXT_TABLE_SOURCE);
                        }

                        boolean isDesc = false;

                        isDesc = tokenizer.isGetThis(Token.T_DESC);

                        try {
                            t.setDataSource(session, token, isDesc, false);
                        } catch (Throwable e) {
                            if (session.isProcessingLog()
                                    || session.isProcessingScript()) {
                                HsqlException warning =
                                    Trace.error(Trace.GENERAL_IO_ERROR,
                                                e.getMessage());

                                session.addWarning(warning);

                                // add an entry to applog too
                            } else {
                                if (e instanceof HsqlException) {
                                    throw (HsqlException) e;
                                } else {
                                    throw Trace.error(Trace.GENERAL_IO_ERROR,
                                                      e.getMessage());
                                }
                            }
                        }

                        break;
                    }
                    case Token.READONLY : {
                        session.checkAdmin();
                        t.setDataReadOnly(processTrueOrFalse());
                        database.setMetaDirty(false);

                        break;
                    }
                    case Token.INDEX : {
                        session.checkAdmin();

                        String roots =
                            (String) tokenizer.getInType(Types.VARCHAR);

                        t.setIndexRoots(roots);

                        break;
                    }
                }

                break;
            }
            case Token.REFERENTIAL_INTEGRITY : {
                session.checkAdmin();
                session.checkDDLWrite();
                session.setScripting(false);
                database.setReferentialIntegrity(processTrueOrFalse());

                break;
            }
            case Token.CHECKPOINT : {
                session.checkAdmin();
                session.checkDDLWrite();
                tokenizer.getThis(Token.T_DEFRAG);

                int size = tokenizer.getInt();

                database.getProperties().setProperty(
                    HsqlDatabaseProperties.hsqldb_defrag_limit, size);

                break;
            }
            case Token.WRITE_DELAY : {
                session.checkAdmin();
                session.checkDDLWrite();

                int delay = 0;

                tokenizer.getString();

                Object value = tokenizer.getAsValue();

                if (tokenizer.getType() == Types.INTEGER) {
                    delay = ((Integer) value).intValue();
                } else if (Boolean.TRUE.equals(value)) {
                    delay = database.getProperties().getDefaultWriteDelay();
                } else if (Boolean.FALSE.equals(value)) {
                    delay = 0;
                } else {
                    throw Trace.error(Trace.UNEXPECTED_TOKEN);
                }

                if (!tokenizer.isGetThis("MILLIS")) {
                    delay *= 1000;
                }

                database.logger.setWriteDelay(delay);

                break;
            }
            case Token.DATABASE : {
                session.checkAdmin();
                session.checkDDLWrite();
                tokenizer.getThis(Token.T_COLLATION);

                String cname = tokenizer.getSimpleName();

                if (!tokenizer.wasQuotedIdentifier()) {
                    throw Trace.error(Trace.INVALID_IDENTIFIER);
                }

                database.collation.setCollation(cname);

                break;
            }
            default : {
                throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
            }
        }
    }

    /**
     * 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" or "FALSE"
     */
    private boolean processTrueOrFalse() throws HsqlException {

        String sToken = tokenizer.getSimpleToken();

        if (sToken.equals(Token.T_TRUE)) {
            return true;
        } else if (sToken.equals(Token.T_FALSE)) {
            return false;
        } else {
            throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken);
        }
    }

    /**
     * Responsible for  handling the execution of COMMIT [WORK]
     *
     * @throws  HsqlException
     */
    private void processCommit() throws HsqlException {
        tokenizer.isGetThis(Token.T_WORK);
        session.commit();
    }

    /**
     * Responsible for handling the execution of ROLLBACK SQL statements.
     *
     * @throws  HsqlException
     */
    private void processRollback() throws HsqlException {

        String  token;
        boolean toSavepoint;

        token       = tokenizer.getSimpleToken();
        toSavepoint = false;

        if (token.equals(Token.T_WORK)) {

            // do nothing
        } else if (token.equals(Token.T_TO)) {
            tokenizer.getThis(Token.T_SAVEPOINT);

            token       = tokenizer.getSimpleName();
            toSavepoint = true;
        } else {
            tokenizer.back();
        }

        if (toSavepoint) {
            session.rollbackToSavepoint(token);
        } else {
            session.rollback();
        }
    }

    /**
     * Responsible for handling the execution of SAVEPOINT SQL statements.
     *
     * @throws  HsqlException
     */
    private void processSavepoint() throws HsqlException {

        String token;

        token = tokenizer.getSimpleName();

        session.savepoint(token);
    }

    /**
     * Responsible for handling the execution of SHUTDOWN SQL statements
     *
     * @throws  HsqlException
     */
    private void processShutdown() throws HsqlException {

        int    closemode;
        String token;

        // HUH?  We should *NEVER* be able to get here if session is closed
        if (!session.isClosed()) {
            session.checkAdmin();
        }

        closemode = Database.CLOSEMODE_NORMAL;
        token     = tokenizer.getSimpleToken();

        // fredt - todo - catch misspelt qualifiers here and elsewhere
        if (token.equals(Token.T_IMMEDIATELY)) {
            closemode = Database.CLOSEMODE_IMMEDIATELY;
        } else if (token.equals(Token.T_COMPACT)) {
            closemode = Database.CLOSEMODE_COMPACT;
        } else if (token.equals(Token.T_SCRIPT)) {
            closemode = Database.CLOSEMODE_SCRIPT;
        } else if (token.equals(Token.T_SEMICOLON)) {

            // only semicolon is accepted here
        } else if (token.length() != 0) {
            throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
        }

        database.close(closemode);
    }

    /**
     * Responsible for handling CHECKPOINT [DEFRAG].
     *
     * @throws  HsqlException
     */
    private void processCheckpoint() throws HsqlException {

        boolean defrag;
        String  token;

        session.checkAdmin();
        session.checkDDLWrite();

        defrag = false;
        token  = tokenizer.getSimpleToken();

        if (token.equals(Token.T_DEFRAG)) {
            defrag = true;
        } else if (token.equals(Token.T_SEMICOLON)) {

            // only semicolon is accepted here
        } else if (token.length() != 0) {
            throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
        }

        database.logger.checkpoint(defrag);
    }

// --------------------- new methods / simplifications ------------------------
    private HsqlName newIndexHsqlName(String name,
                                      boolean isQuoted) throws HsqlException {

        return HsqlName.isReservedName(name)
               ? database.nameManager.newAutoName("USER", name)
               : database.nameManager.newHsqlName(name, isQuoted);
    }

    private Table newTable(int type, String name, boolean quoted,
                           HsqlName schema) throws HsqlException {

        HsqlName tableHsqlName = database.nameManager.newHsqlName(name,
            quoted);

        tableHsqlName.schema = schema;

        switch (type) {

            case Table.TEMP_TEXT_TABLE :
            case Table.TEXT_TABLE : {
                return new TextTable(database, tableHsqlName, type);
            }
            default : {
                return new Table(database, tableHsqlName, type);
            }
        }
    }

    /**
     * Checks if the attributes of the Column argument, c, are compatible with
     * the operation of adding such a Column to the Table argument, t.
     *
     * @param t to which to add the Column, c
     * @param c the Column to add to the Table, t
     * @throws HsqlException if the operation of adding the Column, c, to
     *      the table t is not valid
     */
    private void checkAddColumn(Table t, Column c) throws HsqlException {

        boolean canAdd = true;

        if (t.findColumn(c.columnName.name) != -1) {
            throw Trace.error(Trace.COLUMN_ALREADY_EXISTS, c.columnName.name);
        }

        if (c.isPrimaryKey() && t.hasPrimaryKey()) {
            canAdd = false;
        }

        if (canAdd && !t.isEmpty(session)) {
            canAdd = c.isNullable() || c.getDefaultExpression() != null;
        }

        if (!canAdd) {
            throw Trace.error(Trace.BAD_ADD_COLUMN_DEFINITION);
        }
    }

    private void checkFKColumnDefaults(Table t,
                                       Constraint tc) throws HsqlException {

        boolean check = tc.core.updateAction == Constraint.SET_DEFAULT;

        check = check || tc.core.deleteAction == Constraint.SET_DEFAULT;

        if (check) {
            int[] localCol = tc.core.mainColArray;

            for (int j = 0; j < localCol.length; j++) {
                Column     column  = t.getColumn(localCol[j]);
                Expression defExpr = column.getDefaultExpression();

                if (defExpr == null) {
                    String columnName = column.columnName.name;

                    throw Trace.error(Trace.NO_DEFAULT_VALUE_FOR_COLUMN,
                                      new Object[]{ columnName });
                }
            }
        }
    }

    private void processAlterSequence() throws HsqlException {

        long   start;
        String name       = tokenizer.getName();
        String schemaname = tokenizer.getLongNameFirst();

        schemaname = session.getSchemaNameForWrite(schemaname);

        tokenizer.getThis(Token.T_RESTART);
        tokenizer.getThis(Token.T_WITH);

        start = tokenizer.getBigint();

        NumberSequence seq = database.schemaManager.getSequence(name,
            schemaname);

        seq.reset(start);
    }

    /**
     * Handles ALTER INDEX &lt;index-name&gt; RENAME.
     *
     * @throws HsqlException
     */
    private void processAlterIndexRename() throws HsqlException {

        String name = tokenizer.getName();
        String schema =
            session.getSchemaNameForWrite(tokenizer.getLongNameFirst());

        tokenizer.getThis(Token.T_RENAME);
        tokenizer.getThis(Token.T_TO);

        String newName   = tokenizer.getName();
        String newSchema = tokenizer.getLongNameFirst();

        newSchema = newSchema == null ? schema
                                      : session.getSchemaNameForWrite(
                                          newSchema);

        boolean isQuoted = tokenizer.wasQuotedIdentifier();

        if (!schema.equals(newSchema)) {
            throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
        }

        Table t = database.schemaManager.findUserTableForIndex(session, name,
            schema);

        if (t == null) {
            throw Trace.error(Trace.INDEX_NOT_FOUND, name);
        }

        database.schemaManager.checkIndexExists(name, t.getSchemaName(), true);

        if (HsqlName.isReservedName(name)) {
            throw Trace.error(Trace.SYSTEM_INDEX, name);
        }

        if (HsqlName.isReservedName(newName)) {
            throw Trace.error(Trace.BAD_INDEX_CONSTRAINT_NAME, newName);
        }

        session.setScripting(true);
        session.commit();
        t.getIndex(name).setName(newName, isQuoted);
        database.schemaManager.renameIndex(name, newName, t.getName());
    }

    /**
     * Handles ALTER SCHEMA ... RENAME TO .
     *
     * @throws HsqlException
     */
    private void processAlterSchemaRename() throws HsqlException {

        String name = tokenizer.getSimpleName();

        tokenizer.getThis(Token.T_RENAME);
        tokenizer.getThis(Token.T_TO);

        String  newName  = tokenizer.getSimpleName();
        boolean isQuoted = tokenizer.wasQuotedIdentifier();

        database.schemaManager.renameSchema(name, newName, isQuoted);
    }

    /**
     *
     * @param t table
     * @throws HsqlException
     */
    private void processAlterTableAddColumn(Table t) throws HsqlException {

        String token;
        int    colindex = t.getColumnCount();
        Column column   = processCreateColumn();

        checkAddColumn(t, column);

        if (tokenizer.isGetThis(Token.T_BEFORE)) {
            token    = tokenizer.getSimpleName();
            colindex = t.getColumnNr(token);
        }

        session.commit();

        TableWorks tableWorks = new TableWorks(session, t);

        tableWorks.addColumn(column, colindex);

        return;
    }

    /**
     * Responsible for handling tail of ALTER TABLE ... DROP COLUMN ...
     *
     * @param t table
     * @throws HsqlException
     */
    private void processAlterTableDropColumn(Table t) throws HsqlException {

        String token;
        int    colindex;

        token    = tokenizer.getName();
        colindex = t.getColumnNr(token);

        session.commit();

        TableWorks tableWorks = new TableWorks(session, t);

        tableWorks.dropColumn(colindex);
    }

    /**
     * Responsible for handling tail of ALTER TABLE ... DROP CONSTRAINT ...
     *
     * @param t table
     * @throws HsqlException
     */
    private void processAlterTableDropConstraint(Table t)
    throws HsqlException {
        processAlterTableDropConstraint(t, tokenizer.getName());
    }

    /**
     * Responsible for handling tail of ALTER TABLE ... DROP CONSTRAINT ...
     *
     * @param t table
     * @param name
     * @throws HsqlException
     */
    private void processAlterTableDropConstraint(Table t,
            String cname) throws HsqlException {

        session.commit();

        TableWorks tableWorks = new TableWorks(session, t);

        tableWorks.dropConstraint(cname);

        return;
    }

    /**
     * If an invalid alias is encountered while processing an old script,
     * simply discard it.
     */
    private void processCreateAlias() throws HsqlException {

        String alias;
        String methodFQN;

        try {
            alias = tokenizer.getSimpleName();
        } catch (HsqlException e) {
            if (session.isProcessingScript()) {
                alias = null;
            } else {
                throw e;
            }
        }

        tokenizer.getThis(Token.T_FOR);

        methodFQN = upgradeMethodFQN(tokenizer.getSimpleName());

        if (alias != null) {
            database.getAliasMap().put(alias, methodFQN);
        }
    }

    private void processCreateIndex(boolean unique) throws HsqlException {

        Table   t;
        String  indexName       = tokenizer.getName();
        String  schema          = tokenizer.getLongNameFirst();
        boolean indexNameQuoted = tokenizer.wasQuotedIdentifier();

        tokenizer.getThis(Token.T_ON);

        String tablename = tokenizer.getName();
        String tableschema =
            session.getSchemaNameForWrite(tokenizer.getLongNameFirst());

        if (schema != null && !schema.equals(tableschema)) {
            throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
        }

        t = database.schemaManager.getTable(session, tablename, tableschema);

        database.schemaManager.checkIndexExists(indexName, t.getSchemaName(),
                false);

        HsqlName indexHsqlName = newIndexHsqlName(indexName, indexNameQuoted);
        int[]    indexColumns  = processColumnList(t, true);
        String   extra         = tokenizer.getSimpleToken();

        if (!Token.T_DESC.equals(extra) && !Token.T_ASC.equals(extra)) {
            tokenizer.back();
        }

        session.commit();
        session.setScripting(true);

        TableWorks tableWorks = new TableWorks(session, t);

        tableWorks.createIndex(indexColumns, indexHsqlName, unique, false,
                               false);
    }

    /**
     * limitations in Tokenizer dictate that initial value or increment must
     * be positive
     * @throws HsqlException
     */
    private void processCreateSequence() throws HsqlException {

/*
        CREATE SEQUENCE <name>
        [AS {INTEGER | BIGINT}]
        [START WITH <value>]
        [INCREMENT BY <value>]
*/
        int     type      = Types.INTEGER;
        long    increment = 1;
        long    start     = 0;
        String  name      = tokenizer.getName();
        boolean isquoted  = tokenizer.wasQuotedIdentifier();
        HsqlName schemaname =
            session.getSchemaHsqlNameForWrite(tokenizer.getLongNameFirst());

        if (tokenizer.isGetThis(Token.T_AS)) {
            String typestring = tokenizer.getSimpleToken();

            type = Types.getTypeNr(typestring);

            Trace.check(type == Types.INTEGER || type == Types.BIGINT,
                        Trace.WRONG_DATA_TYPE);
        }

        if (tokenizer.isGetThis(Token.T_START)) {
            tokenizer.getThis(Token.T_WITH);

            start = tokenizer.getBigint();
        }

        if (tokenizer.isGetThis(Token.T_INCREMENT)) {
            tokenizer.getThis(Token.T_BY);

            increment = tokenizer.getBigint();
        }

        HsqlName hsqlname = database.nameManager.newHsqlName(name, isquoted);

        hsqlname.schema = schemaname;

        database.schemaManager.createSequence(hsqlname, start, increment,
                                              type);
    }

    /**
     * CREATE SCHEMA PUBLIC in scripts should pass this, so we do not throw
     * if this schema is created a second time
     */
    private void processCreateSchema() throws HsqlException {

        String  name     = tokenizer.getSimpleName();
        boolean isquoted = tokenizer.wasQuotedIdentifier();

        if (session.isSchemaDefintion()) {
            throw Trace.error(Trace.INVALID_IDENTIFIER);
        }

        tokenizer.getThis(Token.T_AUTHORIZATION);
        tokenizer.getThis(GranteeManager.DBA_ADMIN_ROLE_NAME);

        if (database.schemaManager.schemaExists(name)) {
            if (!session.isProcessingScript) {
                throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
            }
        } else {
            database.schemaManager.createSchema(name, isquoted);
        }

        HsqlName schemaName = database.schemaManager.getSchemaHsqlName(name);

        database.logger.writeToLog(session,
                                   DatabaseScript.getSchemaCreateDDL(database,
                                       schemaName));
        database.logger.writeToLog(session,
                                   "SET SCHEMA " + schemaName.statementName);
        session.startSchemaDefinition(name);

        session.loggedSchema = session.currentSchema;
    }

    private void processCreateUser() throws HsqlException {

        String  name;
        String  password;
        boolean admin;

        name = getUserIdentifier();

        tokenizer.getThis(Token.T_PASSWORD);

        password = getPassword();
        admin    = tokenizer.isGetThis(Token.T_ADMIN);

        database.getUserManager().createUser(name, password);

        if (admin) {
            database.getGranteeManager().grant(
                name, GranteeManager.DBA_ADMIN_ROLE_NAME);
        }
    }

    private void processDisconnect() throws HsqlException {
        session.close();
    }

    private void processDropTable(boolean isView) throws HsqlException {

        boolean ifexists = false;
        boolean cascade  = false;

        if (tokenizer.isGetThis(Token.T_IF)) {
            tokenizer.getThis(Token.T_EXISTS);

            ifexists = true;
        }

        String name   = tokenizer.getName();
        String schema = tokenizer.getLongNameFirst();

        if (tokenizer.isGetThis(Token.T_IF)) {
            tokenizer.getThis(Token.T_EXISTS);

            ifexists = true;
        }

        cascade = tokenizer.isGetThis(Token.T_CASCADE);

        if (!cascade) {
            tokenizer.isGetThis(Token.T_RESTRICT);
        }

        if (ifexists && schema != null
                && !database.schemaManager.schemaExists(schema)) {
            return;
        }

        schema = session.getSchemaNameForWrite(schema);

        database.schemaManager.dropTable(session, name, schema, ifexists,
                                         isView, cascade);
    }

    private void processDropUser() throws HsqlException {

        session.checkAdmin();
        session.checkDDLWrite();

        String userName = getPassword();

        if (database.getSessionManager().isUserActive(userName)) {

            // todo - new error message "cannot drop a user that is currently connected."    // NOI18N
            throw Trace.error(Trace.ACCESS_IS_DENIED);
        }

        database.getUserManager().dropUser(userName);
    }

    private void processDropSequence() throws HsqlException {

        boolean ifexists = false;

        session.checkAdmin();
        session.checkDDLWrite();

        String name = tokenizer.getName();
        String schemaname =
            session.getSchemaNameForWrite(tokenizer.getLongNameFirst());

        if (tokenizer.isGetThis(Token.T_IF)) {
            tokenizer.getThis(Token.T_EXISTS);

            ifexists = true;
        }

        boolean cascade = tokenizer.isGetThis(Token.T_CASCADE);

        if (!cascade) {
            tokenizer.isGetThis(Token.T_RESTRICT);
        }

        NumberSequence sequence = database.schemaManager.findSequence(name,
            schemaname);

        if (sequence == null) {
            if (ifexists) {
                return;
            } else {
                throw Trace.error(Trace.SEQUENCE_NOT_FOUND);
            }
        }

        database.schemaManager.checkCascadeDropViews(sequence, cascade);
        database.schemaManager.dropSequence(sequence);
    }

    private void processDropTrigger() throws HsqlException {

        session.checkAdmin();
        session.checkDDLWrite();

        String triggername = tokenizer.getName();
        String schemaname =
            session.getSchemaNameForWrite(tokenizer.getLongNameFirst());

        database.schemaManager.dropTrigger(session, triggername, schemaname);
    }

    private void processDropIndex() throws HsqlException {

        String name = tokenizer.getName();
        String schema =
            session.getSchemaNameForWrite(tokenizer.getLongNameFirst());
        boolean ifexists = false;

        // accept a table name - no check performed if it is the right table
        if (tokenizer.isGetThis(Token.T_ON)) {
            tokenizer.getName();
        }

        if (tokenizer.isGetThis(Token.T_IF)) {
            tokenizer.getThis(Token.T_EXISTS);

            ifexists = true;
        }

        session.checkAdmin();
        session.checkDDLWrite();
        database.schemaManager.dropIndex(session, name, schema, ifexists);
    }

    private void processDropSchema() throws HsqlException {

        String  name    = tokenizer.getSimpleName();
        boolean cascade = tokenizer.isGetThis(Token.T_CASCADE);

        if (!cascade) {
            tokenizer.isGetThis(Token.T_RESTRICT);
        }

        processDropSchema(name, cascade);
    }

    private void processDropSchema(String name,
                                   boolean cascade) throws HsqlException {

        if (!database.schemaManager.schemaExists(name)) {
            throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
        }

        database.schemaManager.dropSchema(name, cascade);

        if (name.equals(session.getSchemaName(null))) {
            session.setSchema(database.schemaManager.getDefaultSchemaName());
        }
    }

    private Result processExplainPlan() throws IOException, HsqlException {

        // PRE:  we assume only one DML or DQL has been submitted
        //       and simply ignore anything following the first
        //       sucessfully compliled statement
        String            token;
        Parser            parser;
        int               cmd;
        CompiledStatement cs;
        Result            result;
        String            line;
        LineNumberReader  lnr;

        tokenizer.getThis(Token.T_PLAN);
        tokenizer.getThis(Token.T_FOR);

        parser = new Parser(session, database, tokenizer);
        token  = tokenizer.getSimpleToken();
        cmd    = Token.get(token);
        result = Result.newSingleColumnResult("OPERATION", Types.VARCHAR);

        int brackets = 0;

        switch (cmd) {

            case Token.OPENBRACKET :
                brackets = parser.parseOpenBracketsSelect() + 1;
            case Token.SELECT :
                cs = parser.compileSelectStatement(brackets);
                break;

            case Token.INSERT :
                cs = parser.compileInsertStatement();
                break;

            case Token.UPDATE :
                cs = parser.compileUpdateStatement();
                break;

            case Token.DELETE :
                cs = parser.compileDeleteStatement();
                break;

            case Token.CALL :
                cs = parser.compileCallStatement();
                break;

            default :

                // - No real need to throw, so why bother?
                // - Just return result with no rows for now
                // - Later, maybe there will be plan desciptions
                //   for other operations
                return result;
        }

        lnr = new LineNumberReader(new StringReader(cs.describe(session)));

        while (null != (line = lnr.readLine())) {
            result.add(new Object[]{ line });
        }

        return result;
    }

// fredt@users 20010701 - patch 1.6.1 by fredt - open <1.60 db files
// convert org.hsql.Library aliases from versions < 1.60 to org.hsqldb
// fredt@users 20020221 - patch 513005 by sqlbob@users (RMP) - ABS function
    static final String oldLib    = "org.hsql.Library.";
    static final int    oldLibLen = oldLib.length();
    static final String newLib    = "org.hsqldb.Library.";

    private static String upgradeMethodFQN(String fqn) {

        if (fqn.startsWith(oldLib)) {
            fqn = newLib + fqn.substring(oldLibLen);
        } else if (fqn.equals("java.lang.Math.abs")) {
            fqn = "org.hsqldb.Library.abs";
        }

        return fqn;
    }

    /**
     * Processes a SELECT INTO for a new table.
     */
    Result processSelectInto(Result result, HsqlName intoHsqlName,
                             int intoType) throws HsqlException {

        // fredt@users 20020215 - patch 497872 by Nitin Chauhan
        // to require column labels in SELECT INTO TABLE
        int colCount = result.getColumnCount();

        for (int i = 0; i < colCount; i++) {
            if (result.metaData.colLabels[i].length() == 0) {
                throw Trace.error(Trace.LABEL_REQUIRED);
            }
        }

        // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
        Table t = (intoType == Table.TEXT_TABLE)
                  ? new TextTable(database, intoHsqlName, intoType)
                  : new Table(database, intoHsqlName, intoType);

        t.addColumns(result.metaData, result.getColumnCount());
        t.createPrimaryKey();
        database.schemaManager.linkTable(t);

        // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
        if (intoType == Table.TEXT_TABLE) {
            try {

                // Use default lowercase name "<table>.csv" (with invalid
                // char's converted to underscores):
                String txtSrc =
                    StringUtil.toLowerSubset(intoHsqlName.name, '_') + ".csv";

                t.setDataSource(session, txtSrc, false, true);
                logTableDDL(t);
                t.insertIntoTable(session, result);
            } catch (HsqlException e) {
                database.schemaManager.dropTable(session, intoHsqlName.name,
                                                 null, false, false, false);

                throw (e);
            }
        } else {
            logTableDDL(t);

            // SELECT .. INTO can't fail because of constraint violation
            t.insertIntoTable(session, result);
        }

        Result uc = new Result(ResultConstants.UPDATECOUNT);

        uc.updateCount = result.getSize();

        return uc;
    }

    /**
     *  Logs the DDL for a table created with INTO.
     *  Uses two dummy arguments for getTableDDL() as the new table has no
     *  FK constraints.
     *
     *
     * @param t table
     * @throws  HsqlException
     */
    private void logTableDDL(Table t) throws HsqlException {

        StringBuffer tableDDL;
        String       sourceDDL;

        tableDDL = new StringBuffer();

        DatabaseScript.getTableDDL(database, t, 0, null, true, tableDDL);

        sourceDDL = DatabaseScript.getDataSource(t);

        database.logger.writeToLog(session, tableDDL.toString());

        if (sourceDDL != null) {
            database.logger.writeToLog(session, sourceDDL);
        }
    }

    private void processAlterTableAddUniqueConstraint(Table t,
            HsqlName n) throws HsqlException {

        int[] col;

        col = processColumnList(t, false);

        if (n == null) {
            n = database.nameManager.newAutoName("CT");
        }

        session.commit();

        TableWorks tableWorks = new TableWorks(session, t);

        tableWorks.createUniqueConstraint(col, n);
    }

    private void processAlterTableAddForeignKeyConstraint(Table t,
            HsqlName n) throws HsqlException {

        Constraint tc;

        if (n == null) {
            n = database.nameManager.newAutoName("FK");
        }

        tc = processCreateFK(t, n);

        checkFKColumnDefaults(t, tc);
        t.checkColumnsMatch(tc.core.mainColArray, tc.core.refTable,
                            tc.core.refColArray);
        session.commit();

        TableWorks tableWorks = new TableWorks(session, t);

        tableWorks.createForeignKey(tc.core.mainColArray, tc.core.refColArray,
                                    tc.constName, tc.core.refTable,
                                    tc.core.deleteAction,
                                    tc.core.updateAction);
    }

    private void processAlterTableAddCheckConstraint(Table table,
            HsqlName name) throws HsqlException {

        Constraint check;

        if (name == null) {
            name = database.nameManager.newAutoName("CT");
        }

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

        processCreateCheckConstraintCondition(check);
        session.commit();

        TableWorks tableWorks = new TableWorks(session, table);

        tableWorks.createCheckConstraint(check, name);
    }

    private void processAlterTableAddPrimaryKey(Table t,
            HsqlName n) throws HsqlException {

        int[] col;

        col = processColumnList(t, false);

        session.commit();

        TableWorks tableWorks = new TableWorks(session, t);

        tableWorks.addPrimaryKey(col, n);
    }

    private void processReleaseSavepoint() throws HsqlException {

        String token;

        tokenizer.getThis(Token.T_SAVEPOINT);

        token = tokenizer.getSimpleName();

        session.releaseSavepoint(token);
    }

    private void processAlterUser() throws HsqlException {

        String userName;
        String password;
        User   userObject;

        userName   = getUserIdentifier();
        userObject = (User) database.getUserManager().getUsers().get(userName);

        Trace.check(userObject != null, Trace.USER_NOT_FOUND, userName);
        tokenizer.getThis(Token.T_SET);
        tokenizer.getThis(Token.T_PASSWORD);

        password = getPassword();

        userObject.setPassword(password);
        database.logger.writeToLog(session, userObject.getAlterUserDDL());
        session.setScripting(false);
    }

    private String getUserIdentifier() throws HsqlException {

        String    token = tokenizer.getString();
        Tokenizer t     = new Tokenizer(token);

        return t.getSimpleName();
    }

    private String getPassword() throws HsqlException {

        String token = tokenizer.getString();

        return token.toUpperCase(Locale.ENGLISH);
    }

    /**
     *  Responsible for handling the execution of GRANT/REVOKE role...
     *  statements.
     *
     * @throws HsqlException
     */
    private void processRoleGrantOrRevoke(boolean grant) throws HsqlException {

        String         token;
        HsqlArrayList  list = new HsqlArrayList();
        String         role;
        GranteeManager granteeManager = database.getGranteeManager();

        do {
            role = tokenizer.getSimpleToken();

            Trace.check(granteeManager.isRole(role),
                        (grant ? Trace.NO_SUCH_ROLE_GRANT
                               : Trace.NO_SUCH_ROLE_REVOKE));
            list.add(role);
        } while (tokenizer.isGetThis(Token.T_COMMA));

        tokenizer.getThis(grant ? Token.T_TO
                                : Token.T_FROM);

        token = getUserIdentifier();

        GranteeManager gm = database.getGranteeManager();

        for (int i = 0; i < list.size(); i++) {
            if (grant) {
                gm.grant(token, (String) list.get(i));
            } else {
                gm.revoke(token, (String) list.get(i));
            }
        }
    }
}
