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


package org.hsqldb;

import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.ParserDQL.CompileContext;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.HsqlList;
import org.hsqldb.result.Result;
import org.hsqldb.rights.Grantee;
import org.hsqldb.rights.User;
import org.hsqldb.types.DTIType;
import org.hsqldb.types.IntervalSecondData;
import org.hsqldb.types.Type;
import org.hsqldb.types.Types;

/**
 * Implementation of Statement for SQL session statements.<p>
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.3.5
 * @since 1.9.0
 */
public class StatementSession extends Statement {

    public static final StatementSession commitNoChainStatement =
        new StatementSession(StatementTypes.COMMIT_WORK,
                             new Object[]{ Boolean.FALSE });
    public static final StatementSession rollbackNoChainStatement =
        new StatementSession(StatementTypes.ROLLBACK_WORK,
                             new Object[]{ Boolean.FALSE });
    public static final StatementSession commitAndChainStatement =
        new StatementSession(StatementTypes.COMMIT_WORK,
                             new Object[]{ Boolean.TRUE });
    public static final StatementSession rollbackAndChainStatement =
        new StatementSession(StatementTypes.ROLLBACK_WORK,
                             new Object[]{ Boolean.TRUE });

    static {
        commitNoChainStatement.sql   = Tokens.T_COMMIT;
        commitAndChainStatement.sql  = Tokens.T_COMMIT + ' ' + Tokens.T_CHAIN;
        rollbackNoChainStatement.sql = Tokens.T_ROLLBACK;
        rollbackAndChainStatement.sql = Tokens.T_ROLLBACK + ' '
                                        + Tokens.T_CHAIN;
        commitNoChainStatement.compileTimestamp    = Long.MAX_VALUE;
        commitAndChainStatement.compileTimestamp   = Long.MAX_VALUE;
        rollbackNoChainStatement.compileTimestamp  = Long.MAX_VALUE;
        rollbackAndChainStatement.compileTimestamp = Long.MAX_VALUE;
    }

    Expression[] expressions;
    Object[]     arguments;

    StatementSession(Session session, CompileContext context, int type,
                     Expression[] args) {

        super(type);

        expressions            = args;
        isTransactionStatement = false;
        isLogged               = false;

        Expression e = expressions[0];

        switch (type) {

            case StatementTypes.SET_TIME_ZONE :
                HsqlList unresolved = e.resolveColumnReferences(session,
                    RangeGroup.emptyGroup, RangeGroup.emptyArray, null);

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

                if (e.dataType == null) {
                    e.setDataType(session, Type.SQL_INTERVAL_HOUR_TO_MINUTE);
                }

                if (e.dataType.isCharacterType()) {
                    e = new ExpressionOp(e, Type.SQL_INTERVAL_HOUR_TO_MINUTE);
                    expressions[0] = e;
                }

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

                group = StatementTypes.X_SQL_SESSION;

                return;

            case StatementTypes.SET_PATH :
            case StatementTypes.SET_NAMES :
            case StatementTypes.SET_ROLE :
            case StatementTypes.SET_SCHEMA :
            case StatementTypes.SET_CATALOG :
            case StatementTypes.SET_SESSION_AUTHORIZATION :
            case StatementTypes.SET_COLLATION :
                group = StatementTypes.X_SQL_SESSION;
                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500,
                                         "StatementSession");
        }

        e.resolveTypes(session, null);

        switch (e.getType()) {

            case OpTypes.VALUE :
                break;

            case OpTypes.DYNAMIC_PARAM :
                e.setDataType(session, Type.SQL_VARCHAR_DEFAULT);
                break;

            case OpTypes.SQL_FUNCTION :
                if (((FunctionSQL) e).isValueFunction()) {
                    break;
                }
            default :
                throw Error.error(ErrorCode.X_0P000);
        }

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

        setDatabaseObjects(session, context);
    }

    StatementSession(int type, Object[] args) {

        super(type);

        this.arguments         = args;
        isTransactionStatement = false;
        isLogged               = false;

        switch (type) {

            // logged by statement
            case StatementTypes.SET_SCHEMA :
                group    = StatementTypes.X_SQL_SESSION;
                isLogged = true;
                break;

            case StatementTypes.DECLARE_VARIABLE :
                group    = StatementTypes.X_HSQLDB_SESSION;
                isLogged = true;
                break;

            // cursor
            case StatementTypes.ALLOCATE_CURSOR :
                group = StatementTypes.X_SQL_DATA;
                break;

            case StatementTypes.ALLOCATE_DESCRIPTOR :
            case StatementTypes.DEALLOCATE_DESCRIPTOR :
            case StatementTypes.DEALLOCATE_PREPARE :
                group = StatementTypes.X_SQL_DYNAMIC;
                break;

            //
            case StatementTypes.DYNAMIC_DELETE_CURSOR :
                group = StatementTypes.X_SQL_DATA_CHANGE;
                break;

            case StatementTypes.DYNAMIC_CLOSE :
            case StatementTypes.DYNAMIC_FETCH :
            case StatementTypes.DYNAMIC_OPEN :
                group = StatementTypes.X_SQL_DATA;
                break;

            //
            case StatementTypes.OPEN :
            case StatementTypes.FETCH :
            case StatementTypes.FREE_LOCATOR :
            case StatementTypes.GET_DESCRIPTOR :
            case StatementTypes.HOLD_LOCATOR :
                group = StatementTypes.X_SQL_DATA;
                break;

            //
            case StatementTypes.PREPARABLE_DYNAMIC_DELETE_CURSOR :
            case StatementTypes.PREPARABLE_DYNAMIC_UPDATE_CURSOR :
            case StatementTypes.PREPARE :
                group = StatementTypes.X_SQL_DYNAMIC;
                break;

            // logged by session
            case StatementTypes.DISCONNECT :
                group = StatementTypes.X_SQL_CONNECTION;
                break;

            //
            case StatementTypes.SET_COLLATION :
            case StatementTypes.SET_CONNECTION :
            case StatementTypes.SET_CONSTRAINT :
            case StatementTypes.SET_DESCRIPTOR :
            case StatementTypes.SET_SESSION_AUTOCOMMIT :
            case StatementTypes.SET_SESSION_CHARACTERISTICS :
            case StatementTypes.SET_SESSION_FEATURE :
            case StatementTypes.SET_SESSION_RESULT_MAX_ROWS :
            case StatementTypes.SET_SESSION_RESULT_MEMORY_ROWS :
            case StatementTypes.SET_TRANSFORM_GROUP :
                group = StatementTypes.X_HSQLDB_SESSION;
                break;

            case StatementTypes.SET_SESSION_SQL_IGNORECASE :
                isLogged = true;
                group    = StatementTypes.X_HSQLDB_SESSION;
                break;

            // logged by session if necessary
            case StatementTypes.COMMIT_WORK :
            case StatementTypes.RELEASE_SAVEPOINT :
            case StatementTypes.ROLLBACK_SAVEPOINT :
            case StatementTypes.ROLLBACK_WORK :
            case StatementTypes.SAVEPOINT :
            case StatementTypes.SET_TRANSACTION :
            case StatementTypes.START_TRANSACTION :
                group = StatementTypes.X_SQL_TRANSACTION;
                break;

            case StatementTypes.DECLARE_SESSION_TABLE :
            case StatementTypes.DROP_TABLE :
                group = StatementTypes.X_SQL_SESSION;
                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500,
                                         "StatementSession");
        }
    }

    StatementSession(int type, HsqlName[] readNames, HsqlName[] writeNames) {

        super(type);

        isTransactionStatement = true;
        isLogged               = false;
        readTableNames         = readNames;
        writeTableNames        = writeNames;

        switch (type) {

            case StatementTypes.TRANSACTION_LOCK_CATALOG :
            case StatementTypes.TRANSACTION_UNLOCK_CATALOG :
            case StatementTypes.TRANSACTION_LOCK_TABLE :
                group = StatementTypes.X_HSQLDB_TRANSACTION;
                break;

            default :
                throw Error.runtimeError(ErrorCode.U_S0500,
                                         "StatementSession");
        }
    }

    public Result execute(Session session) {

        Result result;

        try {
            result = getResult(session);
        } catch (Throwable t) {
            result = Result.newErrorResult(t, null);
        }

        if (result.isError()) {
            result.getException().setStatementType(group, type);

            return result;
        }

        try {
            if (isLogged) {
                session.database.logger.writeOtherStatement(session, sql);
            }
        } catch (Throwable e) {
            return Result.newErrorResult(e, sql);
        }

        return result;
    }

    Result getResult(Session session) {

        boolean startTransaction = false;

        if (this.isExplain) {
            return Result.newSingleColumnStringResult("OPERATION",
                    describe(session));
        }

        switch (type) {

            // cursor
            case StatementTypes.ALLOCATE_CURSOR :
            case StatementTypes.ALLOCATE_DESCRIPTOR :
                return Result.updateZeroResult;

            //
            case StatementTypes.COMMIT_WORK : {
                try {
                    boolean chain = ((Boolean) arguments[0]).booleanValue();

                    session.commit(chain);

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.DEALLOCATE_DESCRIPTOR :
            case StatementTypes.DEALLOCATE_PREPARE :
                return Result.updateZeroResult;

            case StatementTypes.DISCONNECT :
                session.close();

                return Result.updateZeroResult;

            //
            case StatementTypes.DYNAMIC_CLOSE :
            case StatementTypes.DYNAMIC_DELETE_CURSOR :
            case StatementTypes.DYNAMIC_FETCH :
            case StatementTypes.DYNAMIC_OPEN :

            //
            case StatementTypes.FETCH :
            case StatementTypes.FREE_LOCATOR :
            case StatementTypes.GET_DESCRIPTOR :
            case StatementTypes.HOLD_LOCATOR :

            //
            case StatementTypes.OPEN :
            case StatementTypes.PREPARABLE_DYNAMIC_DELETE_CURSOR :
            case StatementTypes.PREPARABLE_DYNAMIC_UPDATE_CURSOR :
            case StatementTypes.PREPARE :
                return Result.updateZeroResult;

            case StatementTypes.TRANSACTION_LOCK_CATALOG :
            case StatementTypes.TRANSACTION_UNLOCK_CATALOG :
            case StatementTypes.TRANSACTION_LOCK_TABLE : {
                return Result.updateZeroResult;
            }
            case StatementTypes.RELEASE_SAVEPOINT : {
                String savepoint = (String) arguments[0];

                try {
                    session.releaseSavepoint(savepoint);

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.ROLLBACK_WORK : {
                boolean chain = ((Boolean) arguments[0]).booleanValue();

                session.rollback(chain);

                return Result.updateZeroResult;
            }
            case StatementTypes.ROLLBACK_SAVEPOINT : {
                String savepoint = (String) arguments[0];

                try {
                    session.rollbackToSavepoint(savepoint);

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.SAVEPOINT : {
                String savepoint = (String) arguments[0];

                session.savepoint(savepoint);

                return Result.updateZeroResult;
            }
            case StatementTypes.SET_CATALOG : {
                String name;

                try {
                    name = (String) expressions[0].getValue(session);
                    name = (String) Type.SQL_VARCHAR.trim(session, name, ' ',
                                                          true, true);

                    if (session.database.getCatalogName().name.equals(name)) {
                        return Result.updateZeroResult;
                    }

                    return Result.newErrorResult(
                        Error.error(ErrorCode.X_3D000), sql);
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.SET_CONNECTION :
            case StatementTypes.SET_CONSTRAINT :
            case StatementTypes.SET_DESCRIPTOR :
                return Result.updateZeroResult;

            case StatementTypes.SET_TIME_ZONE : {
                Object value = null;

                if (expressions[0].getType() == OpTypes.VALUE
                        && expressions[0].getConstantValueNoCheck(session)
                           == null) {
                    session.setZoneSeconds(session.sessionTimeZoneSeconds);

                    return Result.updateZeroResult;
                }

                try {
                    value = expressions[0].getValue(session);
                } catch (HsqlException e) {}

                if (value instanceof Result) {
                    Result result = (Result) value;

                    if (result.isData()) {
                        Object[] data   = null;
                        boolean  single = false;

                        if (result.getNavigator().next()) {
                            data   = result.getNavigator().getCurrent();
                            single = !result.getNavigator().next();
                        }

                        if (single && data != null && data[0] != null) {
                            value = data[0];

                            result.getNavigator().release();
                        } else {
                            result.getNavigator().release();

                            return Result.newErrorResult(
                                Error.error(ErrorCode.X_22009), sql);
                        }
                    } else {
                        return Result.newErrorResult(
                            Error.error(ErrorCode.X_22009), sql);
                    }
                } else {
                    if (value == null) {
                        return Result.newErrorResult(
                            Error.error(ErrorCode.X_22009), sql);
                    }
                }

                long seconds = ((IntervalSecondData) value).getSeconds();

                if (-DTIType.timezoneSecondsLimit <= seconds
                        && seconds <= DTIType.timezoneSecondsLimit) {
                    session.setZoneSeconds((int) seconds);

                    return Result.updateZeroResult;
                }

                return Result.newErrorResult(Error.error(ErrorCode.X_22009),
                                             sql);
            }
            case StatementTypes.SET_NAMES :
                return Result.updateZeroResult;

            case StatementTypes.SET_PATH :
                return Result.updateZeroResult;

            case StatementTypes.SET_ROLE : {
                String  name;
                Grantee role = null;

                try {
                    name = (String) expressions[0].getValue(session);

                    if (name != null) {
                        name = (String) Type.SQL_VARCHAR.trim(session, name,
                                                              ' ', true, true);
                        role = session.database.granteeManager.getRole(name);
                    }
                } catch (HsqlException e) {
                    return Result.newErrorResult(
                        Error.error(ErrorCode.X_0P000), sql);
                }

                if (session.isInMidTransaction()) {
                    return Result.newErrorResult(
                        Error.error(ErrorCode.X_25001), sql);
                }

                if (role == null) {
                    session.setRole(null);
                }

                if (session.getGrantee().hasRole(role)) {
                    session.setRole(role);

                    return Result.updateZeroResult;
                } else {
                    return Result.newErrorResult(
                        Error.error(ErrorCode.X_0P000), sql);
                }
            }
            case StatementTypes.SET_SCHEMA : {
                String   name;
                HsqlName schema;

                try {
                    if (expressions == null) {
                        name = ((HsqlName) arguments[0]).name;
                    } else {
                        name = (String) expressions[0].getValue(session);
                    }

                    name = (String) Type.SQL_VARCHAR.trim(session, name, ' ',
                                                          true, true);
                    schema =
                        session.database.schemaManager.getSchemaHsqlName(name);

                    session.setCurrentSchemaHsqlName(schema);

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.SET_SESSION_AUTHORIZATION : {
                if (session.isInMidTransaction()) {
                    return Result.newErrorResult(
                        Error.error(ErrorCode.X_25001), sql);
                }

                try {
                    String user;
                    String password = null;

                    user = (String) expressions[0].getValue(session);
                    user = (String) Type.SQL_VARCHAR.trim(session, user, ' ',
                                                          true, true);

                    if (expressions[1] != null) {
                        password = (String) expressions[1].getValue(session);
                    }

                    User userObject;

                    if (password == null) {
                        userObject = session.database.userManager.get(user);
                    } else {
                        userObject =
                            session.database.getUserManager().getUser(user,
                                password);
                    }

                    if (userObject == null) {
                        throw Error.error(ErrorCode.X_28501);
                    }

                    // override the actual SQL at runtime
                    sql = userObject.getConnectUserSQL();

                    if (userObject == session.getGrantee()) {
                        return Result.updateZeroResult;
                    }

                    if (password == null && !session.isProcessingLog()
                            && userObject.isAdmin()
                            && !session.getGrantee().isAdmin()) {
                        throw Error.error(ErrorCode.X_28000);
                    }

                    if (session.getGrantee().canChangeAuthorisation()) {
                        session.setUser(userObject);
                        session.setRole(null);
                        session.resetSchema();

                        return Result.updateZeroResult;
                    }

                    /** @todo may need different error code */
                    throw Error.error(ErrorCode.X_28000);
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.SET_SESSION_CHARACTERISTICS : {
                try {
                    if (arguments[0] != null) {
                        boolean readonly =
                            ((Boolean) arguments[0]).booleanValue();

                        session.setReadOnlyDefault(readonly);
                    }

                    if (arguments[1] != null) {
                        int level = ((Integer) arguments[1]).intValue();

                        session.setIsolationDefault(level);
                    }

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.SET_COLLATION :
                return Result.updateZeroResult;

            case StatementTypes.SET_TRANSFORM_GROUP :
                return Result.updateZeroResult;

            case StatementTypes.START_TRANSACTION :
                startTransaction = true;

            // fall through
            case StatementTypes.SET_TRANSACTION : {
                try {
                    if (arguments[0] != null) {
                        boolean readonly =
                            ((Boolean) arguments[0]).booleanValue();

                        session.setReadOnly(readonly);
                    }

                    if (arguments[1] != null) {
                        int level = ((Integer) arguments[1]).intValue();

                        session.setIsolation(level);
                    }

                    if (startTransaction) {
                        session.startTransaction();
                    }

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }

            //
            case StatementTypes.SET_SESSION_AUTOCOMMIT : {
                boolean mode = ((Boolean) arguments[0]).booleanValue();

                try {
                    session.setAutoCommit(mode);

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.DECLARE_VARIABLE : {
                ColumnSchema[] variables = (ColumnSchema[]) arguments[0];

                try {
                    for (int i = 0; i < variables.length; i++) {
                        session.sessionContext.addSessionVariable(
                            variables[i]);
                    }

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.SET_SESSION_FEATURE : {
                String  feature = (String) arguments[0];
                Boolean value   = (Boolean) arguments[1];

                session.setFeature(feature, value.booleanValue());

                return Result.updateZeroResult;
            }
            case StatementTypes.SET_SESSION_RESULT_MAX_ROWS : {
                int size = ((Integer) arguments[0]).intValue();

                session.setSQLMaxRows(size);

                return Result.updateZeroResult;
            }
            case StatementTypes.SET_SESSION_RESULT_MEMORY_ROWS : {
                int size = ((Integer) arguments[0]).intValue();

                session.setResultMemoryRowCount(size);

                return Result.updateZeroResult;
            }
            case StatementTypes.SET_SESSION_SQL_IGNORECASE : {
                try {
                    boolean mode = ((Boolean) arguments[0]).booleanValue();

                    session.setIgnoreCase(mode);

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.DECLARE_SESSION_TABLE : {
                Table         table           = (Table) arguments[0];
                HsqlArrayList tempConstraints = (HsqlArrayList) arguments[1];
                StatementDMQL statement       = (StatementDMQL) arguments[3];
                Boolean       ifNotExists     = (Boolean) arguments[4];

                try {
                    if (tempConstraints.size() != 0) {
                        table =
                            ParserDDL.addTableConstraintDefinitions(session,
                                table, tempConstraints, null, false);
                    }

                    table.compile(session, null);

                    try {
                        session.sessionContext.addSessionTable(table);
                    } catch (HsqlException e) {
                        if (ifNotExists != null
                                && ifNotExists.booleanValue()) {
                            return Result.updateZeroResult;
                        } else {
                            return Result.newErrorResult(e, sql);
                        }
                    }

                    if (table.hasLobColumn) {
                        throw Error.error(ErrorCode.X_42534);
                    }

                    if (statement != null) {
                        Result result = statement.execute(session);

                        table.insertIntoTable(session, result);
                    }

                    return Result.updateZeroResult;
                } catch (HsqlException e) {
                    return Result.newErrorResult(e, sql);
                }
            }
            case StatementTypes.DROP_TABLE : {
                HsqlName name     = (HsqlName) arguments[0];
                Boolean  ifExists = (Boolean) arguments[1];
                Table table =
                    session.sessionContext.findSessionTable(name.name);

                if (table == null) {
                    if (ifExists.booleanValue()) {
                        return Result.updateZeroResult;
                    } else {
                        throw Error.error(ErrorCode.X_42501, name.name);
                    }
                }

                session.sessionData.persistentStoreCollection.removeStore(
                    table);
                session.sessionContext.dropSessionTable(name.name);

                return Result.updateZeroResult;
            }
            default :
                throw Error.runtimeError(ErrorCode.U_S0500,
                                         "StatementSession");
        }
    }

    public boolean isAutoCommitStatement() {
        return false;
    }

    public String describe(Session session) {
        return sql;
    }

    public boolean isCatalogLock(int model) {
        return false;
    }

    public boolean isCatalogChange() {
        return false;
    }
}
