/* 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.result;

import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;

import org.hsqldb.ColumnBase;
import org.hsqldb.Database;
import org.hsqldb.HsqlException;
import org.hsqldb.Session;
import org.hsqldb.SessionInterface;
import org.hsqldb.SqlInvariants;
import org.hsqldb.Statement;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.DataOutputStream;
import org.hsqldb.map.ValuePool;
import org.hsqldb.navigator.RowSetNavigator;
import org.hsqldb.navigator.RowSetNavigatorClient;
import org.hsqldb.rowio.RowInputInterface;
import org.hsqldb.rowio.RowOutputInterface;
import org.hsqldb.types.Charset;
import org.hsqldb.types.Collation;
import org.hsqldb.types.Type;

/**
 *  The primary unit of communication between Connection, Server and Session
 *  objects.
 *
 *  An HSQLDB Result object encapsulates all requests (such as to alter or
 *  query session settings, to allocate and execute statements, etc.) and all
 *  responses (such as exception indications, update counts, result sets and
 *  result set metadata). It also implements the HSQL wire protocol for
 *  communicating all such requests and responses across the network.
 *  Uses a navigator for data.
 *
 * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net)
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.4.1
 * @since 1.9.0
 */
public class Result {

    public static final Result[]       emptyArray = new Result[0];
    public static final ResultMetaData sessionAttributesMetaData;

    static {
        SqlInvariants.isSystemSchemaName(SqlInvariants.SYSTEM_SCHEMA);
        Charset.getDefaultInstance();
        Collation.getDefaultInstance();

        sessionAttributesMetaData =
            ResultMetaData.newResultMetaData(SessionInterface.INFO_LIMIT);

        for (int i = 0; i < Session.INFO_LIMIT; i++) {
            sessionAttributesMetaData.columns[i] = new ColumnBase(null, null,
                    null, null);
        }

        sessionAttributesMetaData.columns[Session.INFO_ID].setType(
            Type.SQL_INTEGER);
        sessionAttributesMetaData.columns[Session.INFO_INTEGER].setType(
            Type.SQL_INTEGER);
        sessionAttributesMetaData.columns[Session.INFO_BOOLEAN].setType(
            Type.SQL_BOOLEAN);
        sessionAttributesMetaData.columns[Session.INFO_VARCHAR].setType(
            Type.SQL_VARCHAR);
        sessionAttributesMetaData.prepareData();
    }

    private static final ResultMetaData emptyMeta =
        ResultMetaData.newResultMetaData(0);
    public static final Result emptyGeneratedResult =
        Result.newDataResult(emptyMeta);
    public static final Result updateZeroResult = newUpdateCountResult(0);
    public static final Result updateOneResult  = newUpdateCountResult(1);

    // type of result
    public byte mode;

    // database ID
    int databaseID;

    // session ID
    long sessionID;

    // result id
    private long id;

    // database name for new connection
    private String databaseName;

    // user / password for new connection
    // error strings in error results
    private String mainString;
    private String subString;
    private String zoneString;

    // vendor error code
    int errorCode;

    // the exception if this is an error
    private HsqlException exception;

    // prepared statement id
    long statementID;

    // statement type based on whether it returns an update count or a result set
    // type of session info requested
    int statementReturnType;

    // max rows (out)
    // update count (in)
    // fetch part result count (in)
    // time zone seconds (connect)
    public int updateCount;

    // fetch size (in)
    private int fetchSize;

    // secondary result
    private Result chainedResult;

    //
    private int lobCount;
    ResultLob   lobResults;

    /** A Result object's metadata */
    public ResultMetaData metaData;

    /** Additional meta data for parameters used in PREPARE_ACK results */
    public ResultMetaData parameterMetaData;

    /** Additional meta data for required generated columns */
    public ResultMetaData generatedMetaData;

    //
    public int rsProperties;

    //
    public int queryTimeout;

    //
    int generateKeys;

    // simple value for PSM, or parameter array
    public Object valueData;

    //
    public Statement statement;

    Result(int mode) {
        this.mode = (byte) mode;
    }

    public Result(int mode, int count) {
        this.mode   = (byte) mode;
        updateCount = count;
    }

    public static Result newResult(RowSetNavigator nav) {

        Result result = new Result(ResultConstants.DATA);

        result.navigator = nav;

        return result;
    }

    public static Result newResult(int type) {

        RowSetNavigator navigator = null;
        Result          result    = null;

        switch (type) {

            case ResultConstants.CALL_RESPONSE :
            case ResultConstants.EXECUTE :
            case ResultConstants.UPDATE_RESULT :
                break;

            case ResultConstants.BATCHEXECUTE :
            case ResultConstants.BATCHEXECDIRECT :
                navigator = new RowSetNavigatorClient(4);
                break;

            case ResultConstants.SETSESSIONATTR :
            case ResultConstants.PARAM_METADATA :
                navigator = new RowSetNavigatorClient(1);
                break;

            case ResultConstants.BATCHEXECRESPONSE :
                navigator = new RowSetNavigatorClient(4);
                break;

            case ResultConstants.DATA :
            case ResultConstants.DATAHEAD :
            case ResultConstants.DATAROWS :
            case ResultConstants.GENERATED :
                break;

            case ResultConstants.LARGE_OBJECT_OP :
                throw Error.runtimeError(ErrorCode.U_S0500, "Result");
            default :
        }

        result           = new Result(type);
        result.navigator = navigator;

        return result;
    }

    public static Result newResult(DataInput dataInput,
                                   RowInputInterface in) throws IOException {
        return newResult(null, dataInput.readByte(), dataInput, in);
    }

    public static Result newResult(Session session, int mode,
                                   DataInput dataInput,
                                   RowInputInterface in) throws IOException {

        try {
            if (mode == ResultConstants.LARGE_OBJECT_OP) {
                return ResultLob.newLob(dataInput, false);
            }

            Result result = newResult(session, dataInput, in, mode);

            return result;
        } catch (IOException e) {
            throw Error.error(ErrorCode.X_08000);
        }
    }

    public void readAdditionalResults(SessionInterface session,
                                      DataInputStream inputStream,
                                      RowInputInterface in)
                                      throws IOException {

        Result currentResult = this;

        setSession(session);

        while (true) {
            int addedResultMode = inputStream.readByte();

            if (addedResultMode == ResultConstants.NONE) {
                return;
            }

            currentResult = newResult(null, inputStream, in, addedResultMode);

            addChainedResult(currentResult);
        }
    }

    public void readLobResults(SessionInterface session,
                               DataInputStream inputStream,
                               RowInputInterface in) throws IOException {

        Result  currentResult = this;
        boolean hasLob        = false;

        setSession(session);

        while (true) {
            int addedResultMode = inputStream.readByte();

            if (addedResultMode == ResultConstants.LARGE_OBJECT_OP) {
                ResultLob resultLob = ResultLob.newLob(inputStream, false);

                if (session instanceof Session) {
                    ((Session) session).allocateResultLob(resultLob,
                                                          inputStream);
                } else {
                    currentResult.addLobResult(resultLob);
                }

                hasLob = true;

                continue;
            } else if (addedResultMode == ResultConstants.NONE) {
                break;
            } else {
                throw Error.runtimeError(ErrorCode.U_S0500, "Result");
            }
        }

        if (hasLob) {
            ((Session) session).registerResultLobs(currentResult);
        }
    }

    private static Result newResult(Session session, DataInput dataInput,
                                    RowInputInterface in,
                                    int mode) throws IOException {

        Result result = newResult(mode);
        int    length = dataInput.readInt();

        in.resetRow(0, length);

        byte[]    byteArray = in.getBuffer();
        final int offset    = 4;

        dataInput.readFully(byteArray, offset, length - offset);

        switch (mode) {

            case ResultConstants.GETSESSIONATTR :
                result.statementReturnType = in.readByte();
                break;

            case ResultConstants.DISCONNECT :
            case ResultConstants.RESETSESSION :
            case ResultConstants.STARTTRAN :
                break;

            case ResultConstants.PREPARE :
                result.setStatementType(in.readByte());

                result.mainString   = in.readString();
                result.rsProperties = in.readByte();
                result.generateKeys = in.readByte();

                if (result.generateKeys == ResultConstants
                        .RETURN_GENERATED_KEYS_COL_NAMES || result
                        .generateKeys == ResultConstants
                        .RETURN_GENERATED_KEYS_COL_INDEXES) {
                    result.generatedMetaData = new ResultMetaData(in);
                }
                break;

            case ResultConstants.CLOSE_RESULT :
                result.id = in.readLong();
                break;

            case ResultConstants.FREESTMT :
                result.statementID = in.readLong();
                break;

            case ResultConstants.EXECDIRECT :
                result.updateCount         = in.readInt();
                result.fetchSize           = in.readInt();
                result.statementReturnType = in.readByte();
                result.mainString          = in.readString();
                result.rsProperties        = in.readByte();
                result.queryTimeout        = in.readShort();
                result.generateKeys        = in.readByte();

                if (result.generateKeys == ResultConstants
                        .RETURN_GENERATED_KEYS_COL_NAMES || result
                        .generateKeys == ResultConstants
                        .RETURN_GENERATED_KEYS_COL_INDEXES) {
                    result.generatedMetaData = new ResultMetaData(in);
                }
                break;

            case ResultConstants.CONNECT :
                result.databaseName = in.readString();
                result.mainString   = in.readString();
                result.subString    = in.readString();
                result.zoneString   = in.readString();
                result.updateCount  = in.readInt();
                break;

            case ResultConstants.ERROR :
            case ResultConstants.WARNING :
                result.mainString = in.readString();
                result.subString  = in.readString();
                result.errorCode  = in.readInt();
                break;

            case ResultConstants.CONNECTACKNOWLEDGE :
                result.databaseID   = in.readInt();
                result.sessionID    = in.readLong();
                result.databaseName = in.readString();
                result.mainString   = in.readString();
                result.generateKeys = in.readInt();
                break;

            case ResultConstants.UPDATECOUNT :
                result.updateCount = in.readInt();
                break;

            case ResultConstants.ENDTRAN : {
                int type = in.readInt();

                result.setActionType(type);                     // endtran type

                switch (type) {

                    case ResultConstants.TX_SAVEPOINT_NAME_RELEASE :
                    case ResultConstants.TX_SAVEPOINT_NAME_ROLLBACK :
                        result.mainString = in.readString();    // savepoint name
                        break;

                    case ResultConstants.TX_COMMIT :
                    case ResultConstants.TX_ROLLBACK :
                    case ResultConstants.TX_COMMIT_AND_CHAIN :
                    case ResultConstants.TX_ROLLBACK_AND_CHAIN :
                    case ResultConstants.PREPARECOMMIT :
                        break;

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

                break;
            }
            case ResultConstants.SETCONNECTATTR : {
                int type = in.readInt();                        // attr type

                result.setConnectionAttrType(type);

                switch (type) {

                    case ResultConstants.SQL_ATTR_SAVEPOINT_NAME :
                        result.mainString = in.readString();    // savepoint name
                        break;

                    //  case ResultConstants.SQL_ATTR_AUTO_IPD :
                    //      - always true
                    //  default: throw - case never happens
                    default :
                        throw Error.runtimeError(ErrorCode.U_S0500, "Result");
                }

                break;
            }
            case ResultConstants.SQLCANCEL :
                result.databaseID   = in.readInt();
                result.sessionID    = in.readLong();
                result.statementID  = in.readLong();
                result.generateKeys = in.readInt();
                result.mainString   = in.readString();
                break;

            case ResultConstants.PREPARE_ACK :
                result.statementReturnType = in.readByte();
                result.statementID         = in.readLong();
                result.rsProperties        = in.readByte();
                result.metaData            = new ResultMetaData(in);
                result.parameterMetaData   = new ResultMetaData(in);
                break;

            case ResultConstants.CALL_RESPONSE :
                result.updateCount         = in.readInt();
                result.fetchSize           = in.readInt();
                result.statementID         = in.readLong();
                result.statementReturnType = in.readByte();
                result.rsProperties        = in.readByte();
                result.metaData            = new ResultMetaData(in);
                result.valueData           = readSimple(in, result.metaData);
                break;

            case ResultConstants.EXECUTE :
                result.updateCount  = in.readInt();
                result.fetchSize    = in.readInt();
                result.statementID  = in.readLong();
                result.rsProperties = in.readByte();
                result.queryTimeout = in.readShort();

                Statement statement =
                    session.statementManager.getStatement(session,
                        result.statementID);

                if (statement == null) {

                    // invalid statement
                    result.mode      = ResultConstants.EXECUTE_INVALID;
                    result.valueData = ValuePool.emptyObjectArray;

                    break;
                }

                result.statement = statement;
                result.metaData  = result.statement.getParametersMetaData();
                result.valueData = readSimple(in, result.metaData);
                break;

            case ResultConstants.UPDATE_RESULT : {
                result.id = in.readLong();

                int type = in.readInt();

                result.setActionType(type);

                result.metaData  = new ResultMetaData(in);
                result.valueData = readSimple(in, result.metaData);

                break;
            }
            case ResultConstants.BATCHEXECRESPONSE :
            case ResultConstants.BATCHEXECUTE :
            case ResultConstants.BATCHEXECDIRECT :
            case ResultConstants.SETSESSIONATTR : {
                result.updateCount  = in.readInt();
                result.fetchSize    = in.readInt();
                result.statementID  = in.readLong();
                result.queryTimeout = in.readShort();
                result.metaData     = new ResultMetaData(in);

                result.navigator.readSimple(in, result.metaData);

                break;
            }
            case ResultConstants.PARAM_METADATA : {
                result.metaData = new ResultMetaData(in);

                result.navigator.read(in, result.metaData);

                break;
            }
            case ResultConstants.REQUESTDATA : {
                result.id          = in.readLong();
                result.updateCount = in.readInt();
                result.fetchSize   = in.readInt();

                break;
            }
            case ResultConstants.DATAHEAD :
            case ResultConstants.DATA :
            case ResultConstants.GENERATED : {
                result.id           = in.readLong();
                result.updateCount  = in.readInt();
                result.fetchSize    = in.readInt();
                result.rsProperties = in.readByte();
                result.metaData     = new ResultMetaData(in);
                result.navigator    = new RowSetNavigatorClient();

                result.navigator.read(in, result.metaData);

                break;
            }
            case ResultConstants.DATAROWS : {
                result.metaData  = new ResultMetaData(in);
                result.navigator = new RowSetNavigatorClient();

                result.navigator.read(in, result.metaData);

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

        return result;
    }

    /**
     * For interval PSM return values
     */
    public static Result newPSMResult(int type, String label, Object value) {

        Result result = newResult(ResultConstants.VALUE);

        result.errorCode  = type;
        result.mainString = label;
        result.valueData  = value;

        return result;
    }

    /**
     * For interval PSM return values
     */
    public static Result newPSMResult(Object value) {

        Result result = newResult(ResultConstants.VALUE);

        result.valueData = value;

        return result;
    }

    /**
     * For SQLPREPARE
     * For preparation of SQL prepared statements.
     */
    public static Result newPrepareStatementRequest() {
        return newResult(ResultConstants.PREPARE);
    }

    /**
     * For SQLEXECUTE
     * For execution of SQL prepared statements.
     * The parameters are set afterwards as the Result is reused
     */
    public static Result newPreparedExecuteRequest(Type[] types,
            long statementId) {

        Result result = newResult(ResultConstants.EXECUTE);

        result.metaData    = ResultMetaData.newSimpleResultMetaData(types);
        result.statementID = statementId;
        result.valueData   = ValuePool.emptyObjectArray;

        return result;
    }

    /**
     * For CALL_RESPONSE
     * For execution of SQL callable statements.
     */
    public static Result newCallResponse(Type[] types, long statementId,
                                         Object[] values) {

        Result result = newResult(ResultConstants.CALL_RESPONSE);

        result.metaData    = ResultMetaData.newSimpleResultMetaData(types);
        result.statementID = statementId;
        result.valueData   = values;

        return result;
    }

    /**
     * For UPDATE_RESULT
     * The parameters are set afterwards as the Result is reused
     */
    public static Result newUpdateResultRequest(Type[] types, long id) {

        Result result = newResult(ResultConstants.UPDATE_RESULT);

        result.metaData  = ResultMetaData.newUpdateResultMetaData(types);
        result.id        = id;
        result.valueData = new Object[]{};

        return result;
    }

    /**
     * For UPDATE_RESULT results
     * The parameters are set by this method as the Result is reused
     */
    public void setPreparedResultUpdateProperties(Object[] parameterValues) {
        valueData = parameterValues;
    }

    /**
     * For SQLEXECUTE results
     * The parameters are set by this method as the Result is reused
     */
    public void setPreparedExecuteProperties(Object[] parameterValues,
            int maxRows, int fetchSize, int resultProps, int timeout) {

        mode              = ResultConstants.EXECUTE;
        valueData         = parameterValues;
        updateCount       = maxRows;
        this.fetchSize    = fetchSize;
        this.rsProperties = resultProps;
        queryTimeout      = timeout;
    }

    /**
     * For BATCHEXECUTE
     */
    public void setBatchedPreparedExecuteRequest() {

        mode = ResultConstants.BATCHEXECUTE;

        if (navigator == null) {
            navigator = new RowSetNavigatorClient(4);
        } else {
            navigator.clear();
        }

        updateCount    = 0;
        this.fetchSize = 0;
    }

    public void addBatchedPreparedExecuteRequest(Object[] parameterValues) {
        navigator.add(parameterValues);
    }

    /**
     * For BATCHEXECDIRECT
     */
    public static Result newBatchedExecuteRequest() {

        Type[] types  = new Type[]{ Type.SQL_VARCHAR };
        Result result = newResult(ResultConstants.BATCHEXECDIRECT);

        result.metaData = ResultMetaData.newSimpleResultMetaData(types);

        return result;
    }

    /**
     * For BATCHEXERESPONSE for a BATCHEXECUTE or BATCHEXECDIRECT
     */
    public static Result newBatchedExecuteResponse(int[] updateCounts,
            Result generatedResult, Result e) {

        Result result = newResult(ResultConstants.BATCHEXECRESPONSE);

        result.addChainedResult(generatedResult);
        result.addChainedResult(e);

        Type[] types = new Type[]{ Type.SQL_INTEGER };

        result.metaData = ResultMetaData.newSimpleResultMetaData(types);

        Object[][] table = new Object[updateCounts.length][];

        for (int i = 0; i < updateCounts.length; i++) {
            table[i] = new Object[]{ ValuePool.getInt(updateCounts[i]) };
        }

        ((RowSetNavigatorClient) result.navigator).setData(table);

        return result;
    }

    public static Result newResetSessionRequest() {

        Result result = newResult(ResultConstants.RESETSESSION);

        return result;
    }

    public static Result newConnectionAttemptRequest(String user,
            String password, String database, String zoneString,
            int timeZoneSeconds) {

        Result result = newResult(ResultConstants.CONNECT);

        result.mainString   = user;
        result.subString    = password;
        result.zoneString   = zoneString;
        result.databaseName = database;
        result.updateCount  = timeZoneSeconds;

        return result;
    }

    public static Result newConnectionAcknowledgeResponse(Session session) {

        Result result = newResult(ResultConstants.CONNECTACKNOWLEDGE);

        result.sessionID    = session.getId();
        result.databaseID   = session.getDatabase().getDatabaseID();
        result.databaseName = session.getDatabase().getNameString();
        result.mainString =
            session.getDatabase().getProperties()
                .getClientPropertiesAsString();
        result.generateKeys = session.getRandomId();

        return result;
    }

    public static Result newUpdateZeroResult() {
        return new Result(ResultConstants.UPDATECOUNT, 0);
    }

    public static Result newUpdateCountResult(int count) {
        return new Result(ResultConstants.UPDATECOUNT, count);
    }

    public static Result newUpdateCountResult(ResultMetaData meta, int count) {

        Result result     = newResult(ResultConstants.UPDATECOUNT);
        Result dataResult = newGeneratedDataResult(meta);

        result.updateCount = count;

        result.addChainedResult(dataResult);

        return result;
    }

    public static Result newSingleColumnResult(ResultMetaData meta) {

        Result result = newResult(ResultConstants.DATA);

        result.metaData  = meta;
        result.navigator = new RowSetNavigatorClient();

        return result;
    }

    public static Result newSingleColumnResult(String colName) {

        Result result = newResult(ResultConstants.DATA);

        result.metaData  = ResultMetaData.newSingleColumnMetaData(colName);
        result.navigator = new RowSetNavigatorClient(8);

        return result;
    }

    public static Result newSingleColumnStringResult(String colName,
            String contents) {

        Result result = Result.newSingleColumnResult(colName);
        LineNumberReader lnr =
            new LineNumberReader(new StringReader(contents));

        while (true) {
            String line = null;

            try {
                line = lnr.readLine();
            } catch (Exception e) {}

            if (line == null) {
                break;
            }

            result.getNavigator().add(new Object[]{ line });
        }

        return result;
    }

    public static Result newDoubleColumnResult(String colNameA,
            String colNameB) {

        Result result = newResult(ResultConstants.DATA);

        result.metaData = ResultMetaData.newDoubleColumnMetaData(colNameA,
                colNameB);
        result.navigator = new RowSetNavigatorClient(8);

        return result;
    }

    public static Result newPrepareResponse(Statement statement) {

        Result r = newResult(ResultConstants.PREPARE_ACK);

        r.statement   = statement;
        r.statementID = statement.getID();

        int csType = statement.getType();

        r.statementReturnType = statement.getStatementReturnType();
        r.metaData            = statement.getResultMetaData();
        r.parameterMetaData   = statement.getParametersMetaData();

        return r;
    }

    public static Result newCancelRequest(int randomId, long statementId,
                                          String sql) {

        Result r = newResult(ResultConstants.SQLCANCEL);

        r.statementID  = statementId;
        r.mainString   = sql;
        r.generateKeys = randomId;

        return r;
    }

    public static Result newFreeStmtRequest(long statementID) {

        Result r = newResult(ResultConstants.FREESTMT);

        r.statementID = statementID;

        return r;
    }

    /**
     * For direct execution of SQL statements. The statement and other
     *  parameters are set afterwards as the Result is reused
     */
    public static Result newExecuteDirectRequest() {
        return newResult(ResultConstants.EXECDIRECT);
    }

    /**
     * For both EXECDIRECT and PREPARE
     */
    public void setPrepareOrExecuteProperties(String sql, int maxRows,
            int fetchSize, int statementReturnType, int timeout,
            int resultSetProperties, int keyMode, int[] generatedIndexes,
            String[] generatedNames) {

        mainString               = sql;
        updateCount              = maxRows;
        this.fetchSize           = fetchSize;
        this.statementReturnType = statementReturnType;
        queryTimeout             = timeout;
        rsProperties             = resultSetProperties;
        generateKeys             = keyMode;
        generatedMetaData =
            ResultMetaData.newGeneratedColumnsMetaData(generatedIndexes,
                generatedNames);
    }

    public static Result newSetSavepointRequest(String name) {

        Result result;

        result = newResult(ResultConstants.SETCONNECTATTR);

        result.setConnectionAttrType(ResultConstants.SQL_ATTR_SAVEPOINT_NAME);
        result.setMainString(name);

        return result;
    }

    public static Result newRequestDataResult(long id, int offset, int count) {

        Result result = newResult(ResultConstants.REQUESTDATA);

        result.id          = id;
        result.updateCount = offset;
        result.fetchSize   = count;

        return result;
    }

    public static Result newDataResult(ResultMetaData md) {

        Result result = newResult(ResultConstants.DATA);

        result.navigator = new RowSetNavigatorClient();
        result.metaData  = md;

        return result;
    }

    public static Result newGeneratedDataResult(ResultMetaData md) {

        Result result = newResult(ResultConstants.GENERATED);

        result.navigator = new RowSetNavigatorClient();
        result.metaData  = md;

        return result;
    }

    /**
     * initially, only used for updatability
     */
    public int getExecuteProperties() {
        return rsProperties;
    }

    public static Result newDataHeadResult(SessionInterface session,
                                           Result source, int offset,
                                           int count) {

        if (offset + count > source.navigator.getSize()) {
            count = source.navigator.getSize() - offset;
        }

        Result result = newResult(ResultConstants.DATAHEAD);

        result.metaData = source.metaData;
        result.navigator = new RowSetNavigatorClient(source.navigator, offset,
                count);

        result.navigator.setId(source.navigator.getId());
        result.setSession(session);

        result.rsProperties = source.rsProperties;
        result.fetchSize    = source.fetchSize;

        return result;
    }

    public static Result newDataRowsResult(Result source, int offset,
                                           int count) {

        if (offset + count > source.navigator.getSize()) {
            count = source.navigator.getSize() - offset;
        }

        Result result = newResult(ResultConstants.DATAROWS);

        result.id       = source.id;
        result.metaData = source.metaData;
        result.navigator = new RowSetNavigatorClient(source.navigator, offset,
                count);

        return result;
    }

    public static Result newDataRowsResult(RowSetNavigator navigator) {

        Result result = newResult(ResultConstants.DATAROWS);

        result.navigator = navigator;

        return result;
    }

    /**
     * Result structure used for set/get session attributes
     */
    public static Result newSessionAttributesResult() {

        Result result = newResult(ResultConstants.DATA);

        result.navigator = new RowSetNavigatorClient(1);
        result.metaData  = sessionAttributesMetaData;

        result.navigator.add(new Object[SessionInterface.INFO_LIMIT]);

        return result;
    }

    public static Result newWarningResult(HsqlException w) {

        Result result = newResult(ResultConstants.WARNING);

        result.mainString = w.getMessage();
        result.subString  = w.getSQLState();
        result.errorCode  = w.getErrorCode();

        return result;
    }

    public static Result newErrorResult(Throwable t) {
        return newErrorResult(t, null);
    }

    /** @todo 1.9.0 fredt - move the messages to Error.java */
    public static Result newErrorResult(Throwable t, String statement) {

        Result result = newResult(ResultConstants.ERROR);

        if (t instanceof HsqlException) {
            result.exception  = (HsqlException) t;
            result.mainString = result.exception.getMessage();
            result.subString  = result.exception.getSQLState();

            if (statement != null) {
                result.mainString += " in statement [" + statement + "]";
            }

            result.errorCode = result.exception.getErrorCode();
        } else if (t instanceof OutOfMemoryError) {

            // gc() at this point may clear the memory allocated so far

            /** @todo 1.9.0 - review if it's better to gc higher up the stack */
            System.gc();

            result.exception  = Error.error(ErrorCode.OUT_OF_MEMORY, t);
            result.mainString = result.exception.getMessage();
            result.subString  = result.exception.getSQLState();
            result.errorCode  = result.exception.getErrorCode();
        } else if (t instanceof Throwable) {
            result.exception  = Error.error(ErrorCode.GENERAL_ERROR, t);
            result.mainString = result.exception.getMessage();
            result.subString  = result.exception.getSQLState();
            result.errorCode  = result.exception.getErrorCode();
        } else {
            result.exception  = Error.error(ErrorCode.GENERAL_ERROR);
            result.mainString = result.exception.getMessage();
            result.subString  = result.exception.getSQLState();
            result.errorCode  = result.exception.getErrorCode();
        }

        return result;
    }

    public void write(SessionInterface session, DataOutputStream dataOut,
                      RowOutputInterface rowOut) throws IOException {

        rowOut.reset();
        rowOut.writeByte(mode);

        int startPos = rowOut.size();

        rowOut.writeSize(0);

        switch (mode) {

            case ResultConstants.GETSESSIONATTR :
                rowOut.writeByte(statementReturnType);
                break;

            case ResultConstants.DISCONNECT :
            case ResultConstants.RESETSESSION :
            case ResultConstants.STARTTRAN :
                break;

            case ResultConstants.PREPARE :
                rowOut.writeByte(statementReturnType);
                rowOut.writeString(mainString);
                rowOut.writeByte(rsProperties);
                rowOut.writeByte(generateKeys);

                if (generateKeys == ResultConstants
                        .RETURN_GENERATED_KEYS_COL_NAMES || generateKeys == ResultConstants
                        .RETURN_GENERATED_KEYS_COL_INDEXES) {
                    generatedMetaData.write(rowOut);
                }
                break;

            case ResultConstants.FREESTMT :
                rowOut.writeLong(statementID);
                break;

            case ResultConstants.CLOSE_RESULT :
                rowOut.writeLong(id);
                break;

            case ResultConstants.EXECDIRECT :
                rowOut.writeInt(updateCount);
                rowOut.writeInt(fetchSize);
                rowOut.writeByte(statementReturnType);
                rowOut.writeString(mainString);
                rowOut.writeByte(rsProperties);
                rowOut.writeShort(queryTimeout);
                rowOut.writeByte(generateKeys);

                if (generateKeys == ResultConstants
                        .RETURN_GENERATED_KEYS_COL_NAMES || generateKeys == ResultConstants
                        .RETURN_GENERATED_KEYS_COL_INDEXES) {
                    generatedMetaData.write(rowOut);
                }
                break;

            case ResultConstants.CONNECT :
                rowOut.writeString(databaseName);
                rowOut.writeString(mainString);
                rowOut.writeString(subString);
                rowOut.writeString(zoneString);
                rowOut.writeInt(updateCount);
                break;

            case ResultConstants.ERROR :
            case ResultConstants.WARNING :
                rowOut.writeString(mainString);
                rowOut.writeString(subString);
                rowOut.writeInt(errorCode);
                break;

            case ResultConstants.CONNECTACKNOWLEDGE :
                rowOut.writeInt(databaseID);
                rowOut.writeLong(sessionID);
                rowOut.writeString(databaseName);
                rowOut.writeString(mainString);
                rowOut.writeInt(generateKeys);
                break;

            case ResultConstants.UPDATECOUNT :
                rowOut.writeInt(updateCount);
                break;

            case ResultConstants.ENDTRAN : {
                int type = getActionType();

                rowOut.writeInt(type);                     // endtran type

                switch (type) {

                    case ResultConstants.TX_SAVEPOINT_NAME_RELEASE :
                    case ResultConstants.TX_SAVEPOINT_NAME_ROLLBACK :
                        rowOut.writeString(mainString);    // savepoint name
                        break;

                    case ResultConstants.TX_COMMIT :
                    case ResultConstants.TX_ROLLBACK :
                    case ResultConstants.TX_COMMIT_AND_CHAIN :
                    case ResultConstants.TX_ROLLBACK_AND_CHAIN :
                    case ResultConstants.PREPARECOMMIT :
                        break;

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

                break;
            }
            case ResultConstants.SQLCANCEL :
                rowOut.writeInt(databaseID);
                rowOut.writeLong(sessionID);
                rowOut.writeLong(statementID);
                rowOut.writeInt(generateKeys);
                rowOut.writeString(mainString);
                break;

            case ResultConstants.PREPARE_ACK :
                rowOut.writeByte(statementReturnType);
                rowOut.writeLong(statementID);
                rowOut.writeByte(rsProperties);
                metaData.write(rowOut);
                parameterMetaData.write(rowOut);
                break;

            case ResultConstants.CALL_RESPONSE :
                rowOut.writeInt(updateCount);
                rowOut.writeInt(fetchSize);
                rowOut.writeLong(statementID);
                rowOut.writeByte(statementReturnType);
                rowOut.writeByte(rsProperties);
                metaData.write(rowOut);
                writeSimple(rowOut, metaData, (Object[]) valueData);
                break;

            case ResultConstants.EXECUTE :
                rowOut.writeInt(updateCount);
                rowOut.writeInt(fetchSize);
                rowOut.writeLong(statementID);
                rowOut.writeByte(rsProperties);
                rowOut.writeShort(queryTimeout);
                writeSimple(rowOut, metaData, (Object[]) valueData);
                break;

            case ResultConstants.UPDATE_RESULT :
                rowOut.writeLong(id);
                rowOut.writeInt(getActionType());
                metaData.write(rowOut);
                writeSimple(rowOut, metaData, (Object[]) valueData);
                break;

            case ResultConstants.BATCHEXECRESPONSE :
            case ResultConstants.BATCHEXECUTE :
            case ResultConstants.BATCHEXECDIRECT :
            case ResultConstants.SETSESSIONATTR : {
                rowOut.writeInt(updateCount);
                rowOut.writeInt(fetchSize);
                rowOut.writeLong(statementID);
                rowOut.writeShort(queryTimeout);
                metaData.write(rowOut);
                navigator.writeSimple(rowOut, metaData);

                break;
            }
            case ResultConstants.PARAM_METADATA : {
                metaData.write(rowOut);
                navigator.write(rowOut, metaData);

                break;
            }
            case ResultConstants.SETCONNECTATTR : {
                int type = getConnectionAttrType();

                rowOut.writeInt(type);                     // attr type / updateCount

                switch (type) {

                    case ResultConstants.SQL_ATTR_SAVEPOINT_NAME :
                        rowOut.writeString(mainString);    // savepoint name
                        break;

                    // case ResultConstants.SQL_ATTR_AUTO_IPD // always true
                    // default: // throw, but case never happens
                    default :
                        throw Error.runtimeError(ErrorCode.U_S0500, "Result");
                }

                break;
            }
            case ResultConstants.REQUESTDATA : {
                rowOut.writeLong(id);
                rowOut.writeInt(updateCount);
                rowOut.writeInt(fetchSize);

                break;
            }
            case ResultConstants.DATAROWS :
                metaData.write(rowOut);
                navigator.write(rowOut, metaData);
                break;

            case ResultConstants.DATAHEAD :
            case ResultConstants.DATA :
            case ResultConstants.GENERATED :
                rowOut.writeLong(id);
                rowOut.writeInt(updateCount);
                rowOut.writeInt(fetchSize);
                rowOut.writeByte(rsProperties);
                metaData.write(rowOut);
                navigator.write(rowOut, metaData);
                break;

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

        rowOut.writeSize(rowOut.size() - startPos);
        dataOut.write(rowOut.getOutputStream().getBuffer(), 0, rowOut.size());

        int    count   = getLobCount();
        Result current = this;

        for (int i = 0; i < count; i++) {
            ResultLob lob = current.lobResults;

            lob.writeBody(session, dataOut);

            current = current.lobResults;
        }

        if (chainedResult == null) {
            dataOut.writeByte(ResultConstants.NONE);
        } else {
            chainedResult.write(session, dataOut, rowOut);
        }

        dataOut.flush();
    }

    public int getType() {
        return mode;
    }

    public boolean isData() {
        return mode == ResultConstants.DATA
               || mode == ResultConstants.DATAHEAD;
    }

    public boolean isError() {
        return mode == ResultConstants.ERROR;
    }

    public boolean isWarning() {
        return mode == ResultConstants.WARNING;
    }

    public boolean isUpdateCount() {
        return mode == ResultConstants.UPDATECOUNT;
    }

    public boolean isSimpleValue() {
        return mode == ResultConstants.VALUE;
    }

    public boolean hasGeneratedKeys() {
        return mode == ResultConstants.UPDATECOUNT && chainedResult != null;
    }

    public HsqlException getException() {
        return exception;
    }

    public long getStatementID() {
        return statementID;
    }

    public void setStatementID(long statementId) {
        this.statementID = statementId;
    }

    public String getMainString() {
        return mainString;
    }

    public void setMainString(String sql) {
        this.mainString = sql;
    }

    public String getSubString() {
        return subString;
    }

    public String getZoneString() {
        return zoneString;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public Object getValueObject() {
        return valueData;
    }

    public void setValueObject(Object value) {
        valueData = value;
    }

    public Statement getStatement() {
        return statement;
    }

    public void setStatement(Statement statement) {
        this.statement = statement;
    }

    public String getDatabaseName() {
        return databaseName;
    }

    public void setMaxRows(int count) {
        updateCount = count;
    }

    public int getFetchSize() {
        return this.fetchSize;
    }

    public void setFetchSize(int count) {
        fetchSize = count;
    }

    public int getUpdateCount() {
        return updateCount;
    }

    public int getConnectionAttrType() {
        return updateCount;
    }

    public void setConnectionAttrType(int type) {
        updateCount = type;
    }

    public int getActionType() {
        return updateCount;
    }

    public void setActionType(int type) {
        updateCount = type;
    }

    public long getSessionId() {
        return sessionID;
    }

    public void setSessionId(long id) {
        sessionID = id;
    }

    public void setSession(SessionInterface session) {

        if (navigator != null) {
            navigator.setSession(session);
        }
    }

    public int getDatabaseId() {
        return databaseID;
    }

    public void setDatabaseId(int id) {
        databaseID = id;
    }

    public long getResultId() {
        return id;
    }

    public void setResultId(long id) {

        this.id = id;

        if (navigator != null) {
            navigator.setId(id);
        }
    }

    public void setUpdateCount(int count) {
        updateCount = count;
    }

    public void setAsTransactionEndRequest(int subType, String savepoint) {

        mode        = ResultConstants.ENDTRAN;
        updateCount = subType;
        mainString  = savepoint == null ? ""
                                        : savepoint;
    }

    public Object[] getSingleRowData() {

        initialiseNavigator();
        navigator.next();

        Object[] data = navigator.getCurrent();

        data = (Object[]) ArrayUtil.resizeArrayIfDifferent(data,
                metaData.getColumnCount());

        return data;
    }

    public Object[] getParameterData() {
        return (Object[]) valueData;
    }

    public Object[] getSessionAttributes() {

        initialiseNavigator();
        navigator.next();

        return navigator.getCurrent();
    }

    public void setResultType(int type) {
        mode = (byte) type;
    }

    public void setStatementType(int type) {
        statementReturnType = type;
    }

    public int getStatementType() {
        return statementReturnType;
    }

    public void setSessionRandomID(int id) {
        generateKeys = id;
    }

    public int getSessionRandomID() {
        return generateKeys;
    }

    public int getGeneratedResultType() {
        return generateKeys;
    }

    public ResultMetaData getGeneratedResultMetaData() {
        return generatedMetaData;
    }

    public Result getChainedResult() {
        return chainedResult;
    }

    public Result getUnlinkChainedResult() {

        Result result = chainedResult;

        chainedResult = null;

        return result;
    }

    public void addChainedResult(Result result) {

        Result current = this;

        while (current.chainedResult != null) {
            current = current.chainedResult;
        }

        current.chainedResult = result;
    }

    public void addWarnings(HsqlException[] warnings) {

        for (int i = 0; i < warnings.length; i++) {
            Result warning = newWarningResult(warnings[i]);

            addChainedResult(warning);
        }
    }

    public int getLobCount() {
        return lobCount;
    }

    public ResultLob getLOBResult() {
        return lobResults;
    }

    public void addLobResult(ResultLob result) {

        Result current = this;

        while (current.lobResults != null) {
            current = current.lobResults;
        }

        current.lobResults = result;

        lobCount++;
    }

    public void clearLobResults() {
        lobResults = null;
        lobCount   = 0;
    }

    public void addRows(String[] sql) {

        if (sql == null) {
            return;
        }

        for (int i = 0; i < sql.length; i++) {
            String[] s = new String[1];

            s[0] = sql[i];

            initialiseNavigator().add(s);
        }
    }

    public void addRow(String[] sql) {

        if (sql == null) {
            return;
        }

        initialiseNavigator().add(sql);
    }

    private static Object[] readSimple(RowInputInterface in,
                                       ResultMetaData meta)
                                       throws IOException {

        int size = in.readInt();

        return in.readData(meta.columnTypes);
    }

    private static void writeSimple(RowOutputInterface out,
                                    ResultMetaData meta,
                                    Object[] data) throws IOException {

        out.writeInt(1);
        out.writeData(meta.getColumnCount(), meta.columnTypes, data, null,
                      null);
    }

//----------- Navigation
    public RowSetNavigator navigator;

    public RowSetNavigator getNavigator() {
        return navigator;
    }

    public void setNavigator(RowSetNavigator navigator) {
        this.navigator = navigator;
    }

    public RowSetNavigator initialiseNavigator() {

        switch (mode) {

            case ResultConstants.BATCHEXECUTE :
            case ResultConstants.BATCHEXECDIRECT :
            case ResultConstants.BATCHEXECRESPONSE :
            case ResultConstants.SETSESSIONATTR :
            case ResultConstants.PARAM_METADATA :
                navigator.beforeFirst();

                return navigator;

            case ResultConstants.DATA :
            case ResultConstants.DATAHEAD :
            case ResultConstants.GENERATED :
                navigator.reset();

                return navigator;

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