All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.firebirdsql.gds.impl.GDSHelper Maven / Gradle / Ivy

There is a newer version: 2.2.15
Show newest version
/*
 * Public Firebird Java API.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *    1. Redistributions of source code must retain the above copyright notice, 
 *       this list of conditions and the following disclaimer.
 *    2. 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. 
 *    3. The name of the author may not be used to endorse or promote products 
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.firebirdsql.gds.impl;

import org.firebirdsql.encodings.Encoding;
import org.firebirdsql.encodings.EncodingFactory;
import org.firebirdsql.gds.*;
import org.firebirdsql.jdbc.Synchronizable;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;

import java.sql.SQLException;
import java.util.Collections;
import java.util.List;

/**
 * Helper class for all GDS-related operations.
 */
public final class GDSHelper implements Synchronizable {

    public static final int DEFAULT_BLOB_BUFFER_SIZE = 16 * 1024;

    /**
     * Notification listener about any error that occured in this class.
     */
    public interface GDSHelperErrorListener {

        /**
         * Notify about the error in this class.
         *
         * @param ex
         *         error that occurred.
         */
        void errorOccured(GDSException ex);
    }

    private static final Logger log = LoggerFactory.getLogger(GDSHelper.class, false);

    private final GDS gds;
    private final IscDbHandle currentDbHandle;
    private AbstractIscTrHandle currentTr;
    /**
     * Needed from mcf when killing a db handle when a new tx cannot be started.
     */
    protected DatabaseParameterBuffer dpb;

    private boolean registerResultSets;

    private GDSHelperErrorListener listener;

    /**
     * Create instance of this class.
     */
    public GDSHelper(GDS gds, DatabaseParameterBuffer dpb, IscDbHandle dbHandle, GDSHelperErrorListener listener) {
        this.gds = gds;
        this.dpb = dpb;
        this.currentDbHandle = dbHandle;

        this.registerResultSets = !getDatabaseParameterBuffer().hasArgument(
                DatabaseParameterBufferExtension.NO_RESULT_SET_TRACKING);

        this.listener = listener;
    }

    private void notifyListeners(GDSException ex) {
        if (listener != null)
            listener.errorOccured(ex);
    }

    public AbstractIscTrHandle getCurrentTrHandle() {
        synchronized (getSynchronizationObject()) {
            return currentTr;
        }
    }

    public void setCurrentTrHandle(AbstractIscTrHandle currentTr) {
        synchronized (getSynchronizationObject()) {
            this.currentTr = currentTr;
        }
    }

    public IscDbHandle getCurrentDbHandle() {
        return currentDbHandle;
    }

    public DatabaseParameterBuffer getDatabaseParameterBuffer() {
        return dpb;
    }

    /**
     * @return Connection dialect from DPB (or default if not specified)
     */
    public int getDialect() {
        if (dpb.hasArgument(ISCConstants.isc_dpb_sql_dialect))
            return dpb.getArgumentAsInt(ISCConstants.isc_dpb_sql_dialect);
        return ISCConstants.SQL_DIALECT_CURRENT;
    }

    /**
     * Retrieve a newly allocated statment handle with the current connection.
     *
     * @return The new statement handle
     * @throws GDSException
     *         if a database access error occurs
     */
    public AbstractIscStmtHandle allocateStatement() throws GDSException {
        try {
            AbstractIscStmtHandle stmt = (AbstractIscStmtHandle) gds.createIscStmtHandle();
            gds.iscDsqlAllocateStatement(getCurrentDbHandle(), stmt);
            return stmt;
        } catch (GDSException ex) {
            notifyListeners(ex);
            throw ex;
        }
    }

    /**
     * Retrieve whether this connection is currently involved in a transaction
     *
     * @return true if this connection is currently in a
     * transaction, false otherwise.
     */
    public boolean inTransaction() {
        synchronized (getSynchronizationObject()) {
            return currentTr != null;
        }
    }

    public int getTransactionId(IscTrHandle trHandle)
            throws GDSException {

        try {
            byte[] trInfo = gds.iscTransactionInformation(
                    trHandle, new byte[] { ISCConstants.isc_info_tra_id }, 32);
            if (trInfo.length < 3 || trInfo[0] != ISCConstants.isc_info_tra_id) {
                throw new GDSException("Unexpected response buffer");
            }
            int length = gds.iscVaxInteger(trInfo, 1, 2);
            return gds.iscVaxInteger(trInfo, 3, length);
        } catch (GDSException ex) {
            notifyListeners(ex);
            throw ex;
        }
    }

    public long getTransactionIdLong(IscTrHandle trHandle) throws GDSException {
        try {
            byte[] trInfo = gds.iscTransactionInformation(
                    trHandle, new byte[] { ISCConstants.isc_info_tra_id }, 32);
            if (trInfo.length < 3 || trInfo[0] != ISCConstants.isc_info_tra_id) {
                throw new GDSException("Unexpected response buffer");
            }
            int length = gds.iscVaxInteger(trInfo, 1, 2);
            return gds.iscVaxLong(trInfo, 3, length);
        } catch (GDSException ex) {
            notifyListeners(ex);
            throw ex;
        }
    }

    /**
     * Prepare an SQL string for execution (within the database server) in the
     * context of a statement handle.
     *
     * @param stmt
     *         The statement handle within which the SQL statement will be
     *         prepared
     * @param sql
     *         The SQL statement to be prepared
     * @param describeBind
     *         Send bind data to the database server
     * @throws GDSException
     *         if a Firebird-specific error occurs
     * @throws SQLException
     *         if a database access error occurs
     */
    public void prepareStatement(AbstractIscStmtHandle stmt, String sql,
            boolean describeBind) throws GDSException, SQLException {

        try {
            if (log != null) log.trace("preparing sql: " + sql);

            String localEncoding =
                    dpb.getArgumentAsString(DatabaseParameterBufferExtension.LOCAL_ENCODING);

            String mappingPath =
                    dpb.getArgumentAsString(DatabaseParameterBufferExtension.MAPPING_PATH);

            Encoding encoding =
                    EncodingFactory.getEncoding(localEncoding, mappingPath);

            int dialect = getDialect();

            XSQLDA out = gds.iscDsqlPrepare(currentTr, stmt,
                    encoding.encodeToCharset(sql), dialect);

            if (out.sqld != out.sqln)
                throw new GDSException("Not all columns returned");

            if (describeBind)
                gds.iscDsqlDescribeBind(stmt, ISCConstants.SQLDA_VERSION1);

            stmt.statement = sql;

        } catch (GDSException ex) {
            notifyListeners(ex);
            throw ex;
        }
    }

    /**
     * Execute a statement in the database.
     *
     * @param stmt
     *         The handle to the statement to be executed
     * @param sendOutSqlda
     *         Determines if the XSQLDA structure should be sent to the
     *         database
     * @throws GDSException
     *         if a Firebird-specific error occurs
     */
    public void executeStatement(AbstractIscStmtHandle stmt, boolean sendOutSqlda)
            throws GDSException {
        try {

            if (log != null && log.isDebugEnabled())
                log.debug("Executing " + stmt.statement);

            // System.out.println("Executing " + stmt.statement);

            gds.iscDsqlExecute2(currentTr, stmt, ISCConstants.SQLDA_VERSION1,
                    stmt.getInSqlda(), (sendOutSqlda) ? stmt.getOutSqlda() : null);
        } catch (GDSException ex) {
            notifyListeners(ex);
            throw ex;
        }
    }

    /**
     * Execute a SQL statement directly with the current connection.
     *
     * @param statement
     *         The SQL statement to execute
     * @throws GDSException
     *         if a Firebird-specific error occurs
     */
    public void executeImmediate(String statement) throws GDSException {
        try {
            gds.iscDsqlExecImmed2(getIscDBHandle(), currentTr, statement, 3,
                    null, null);
        } catch (GDSException ex) {
            notifyListeners(ex);
            throw ex;
        }
    }

    /**
     * Fetch data from a statement in the database.
     *
     * @param stmt
     *         handle to the statement from which data will be fetched
     * @param fetchSize
     *         The number of records to fetch
     * @throws GDSException
     *         if a Firebird-specific error occurs
     */
    public void fetch(AbstractIscStmtHandle stmt, int fetchSize) throws GDSException {
        try {
            gds.iscDsqlFetch(stmt, ISCConstants.SQLDA_VERSION1,
                    stmt.getOutSqlda(), fetchSize);

            if (registerResultSets)
                currentTr.registerStatementWithTransaction(stmt);
        } catch (GDSException ex) {
            notifyListeners(ex);
            throw ex;
        }
    }

    /**
     * Set the cursor name for a statement.
     *
     * @param stmt
     *         handle to statement for which the cursor name will be set
     * @param cursorName
     *         the name for the cursor
     * @throws GDSException
     *         if a Firebird-specific database access error occurs
     */
    public void setCursorName(AbstractIscStmtHandle stmt, String cursorName)
            throws GDSException {

        try {
            gds.iscDsqlSetCursorName(stmt, cursorName, 0);
        } catch (GDSException ex) {
            notifyListeners(ex);
            throw ex;
        }
    }

    /**
     * Close a statement that is allocated in the database. The statement can be
     * optionally deallocated.
     *
     * @param stmt
     *         handle to the statement to be closed
     * @param deallocate
     *         if true, the statement will be deallocated,
     *         otherwise it will not be deallocated
     * @throws GDSException
     *         if a Firebird-specific database access error occurs
     */
    public void closeStatement(AbstractIscStmtHandle stmt, boolean deallocate) throws GDSException {
        closeStatement(stmt, deallocate, false);
    }

    /**
     * Close a statement that is allocated in the database. The statement can be optionally deallocated.
     * 

* When this method is called in preparation of a commit or rollback (see {@code transactionEnd}), then this method * will do nothing if it doesn't need to deallocate the statement handle. *

* * @param stmt * handle to the statement to be closed * @param deallocate * if {@code true}, the statement will be deallocated, otherwise it will not be deallocated * @param transactionEnd * {@code true} when the call to this method is initiated in response to a transaction commit or rollback. * @throws GDSException * if a Firebird-specific database access error occurs */ public void closeStatement(AbstractIscStmtHandle stmt, boolean deallocate, boolean transactionEnd) throws GDSException { if (!deallocate && transactionEnd) return; try { if (!deallocate && !stmt.hasOpenResultSet()) return; try { gds.iscDsqlFreeStatement(stmt, (deallocate) ? ISCConstants.DSQL_drop : ISCConstants.DSQL_close); } catch (GDSException ex) { // we do not handle exceptions from statement closing if (deallocate) throw ex; boolean recloseClosedCursorError = false; GDSException tempEx = ex; do { if (tempEx.getIntParam() == ISCConstants.isc_dsql_cursor_close_err) { recloseClosedCursorError = true; break; } tempEx = tempEx.getNext(); } while (tempEx != null); if (!recloseClosedCursorError) throw ex; } } catch (GDSException ex) { notifyListeners(ex); throw ex; } } /** * Fetch the count information for a statement handle. The count information * that is updated includes the counts for update, insert, delete and * select, and it is set in the handle itself. * * @param stmt * handle to the statement for which counts will be fetched * @throws GDSException * if a Firebird-specific database access error occurs */ public void getSqlCounts(AbstractIscStmtHandle stmt) throws GDSException { try { gds.getSqlCounts(stmt); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public void populateStatementInfo(AbstractIscStmtHandle fixedStmt) throws GDSException { final byte[] REQUEST = new byte[] { ISCConstants.isc_info_sql_get_plan, ISCConstants.isc_info_sql_stmt_type, ISCConstants.isc_info_end }; try { int bufferSize = 1024; byte[] buffer; while (true) { buffer = gds.iscDsqlSqlInfo(fixedStmt, REQUEST, bufferSize); if (buffer[0] != ISCConstants.isc_info_truncated) { break; } bufferSize *= 2; } if (buffer[0] == ISCConstants.isc_info_end) { throw new GDSException(ISCConstants.isc_req_sync); } String executionPlan = null; int statementType = IscStmtHandle.TYPE_UNKNOWN; int dataLength = -1; for (int i = 0; i < buffer.length; i++) { switch (buffer[i]) { case ISCConstants.isc_info_sql_get_plan: dataLength = gds.iscVaxInteger(buffer, ++i, 2); i += 2; // Skipping first char as it is a linefeed executionPlan = new String(buffer, i + 1, dataLength - 1); i += dataLength - 1; break; case ISCConstants.isc_info_sql_stmt_type: dataLength = gds.iscVaxInteger(buffer, ++i, 2); i += 2; statementType = gds.iscVaxInteger(buffer, i, dataLength); i += dataLength; break; case ISCConstants.isc_info_end: case 0: break; default: throw new GDSException(ISCConstants.isc_req_sync); } } fixedStmt.setExecutionPlan(executionPlan); fixedStmt.setStatementType(statementType); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } /** * Open a handle to a new blob within the current transaction with the given * id. * * @param blob_id * The identifier to be given to the blob * @param segmented * If true, the blob will be segmented, otherwise * is will be streamed * @throws GDSException * if a Firebird-specific database error occurs */ public IscBlobHandle openBlob(long blob_id, boolean segmented) throws GDSException { try { IscBlobHandle blob = gds.createIscBlobHandle(); blob.setBlobId(blob_id); BlobParameterBuffer blobParameterBuffer = gds.createBlobParameterBuffer(); blobParameterBuffer.addArgument(BlobParameterBuffer.TYPE, segmented ? BlobParameterBuffer.TYPE_SEGMENTED : BlobParameterBuffer.TYPE_STREAM); gds.iscOpenBlob2(currentDbHandle, currentTr, blob, blobParameterBuffer); return blob; } catch (GDSException ex) { notifyListeners(ex); throw ex; } } /** * Create a new blob within the current transaction. * * @param segmented * If true the blob will be segmented, otherwise * it will be streamed * @throws GDSException * if a Firebird-specific database error occurs */ public IscBlobHandle createBlob(boolean segmented) throws GDSException { try { IscBlobHandle blob = gds.createIscBlobHandle(); BlobParameterBuffer blobParameterBuffer = gds.createBlobParameterBuffer(); blobParameterBuffer.addArgument(BlobParameterBuffer.TYPE, segmented ? BlobParameterBuffer.TYPE_SEGMENTED : BlobParameterBuffer.TYPE_STREAM); gds.iscCreateBlob2(currentDbHandle, currentTr, blob, blobParameterBuffer); return blob; } catch (GDSException ex) { notifyListeners(ex); throw ex; } } /** * Get a segment from a blob. * * @param blob * Handle to the blob from which the segment is to be fetched * @param len * The maximum length to fetch * @throws GDSException * if a Firebird-specific database access error occurs */ public byte[] getBlobSegment(IscBlobHandle blob, int len) throws GDSException { try { return gds.iscGetSegment(blob, len); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } /** * Close a blob that has been opened within the database. * * @param blob * Handle to the blob to be closed * @throws GDSException * if a Firebird-specific database access error occurs */ public void closeBlob(IscBlobHandle blob) throws GDSException { try { gds.iscCloseBlob(blob); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public void seekBlob(IscBlobHandle blob, int position, int mode) throws GDSException { try { gds.iscSeekBlob(blob, position, mode); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } /** * Write a segment of data to a blob. * * @param blob * handle to the blob to which data will be written * @param buf * segment of data to be written to the blob * @throws GDSException * if a Firebird-specific database access error occurs */ public void putBlobSegment(IscBlobHandle blob, byte[] buf) throws GDSException { try { gds.iscPutSegment(blob, buf); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public byte[] getBlobInfo(IscBlobHandle blob, byte[] requestItems, int bufferLength) throws GDSException { try { return gds.iscBlobInfo(blob, requestItems, bufferLength); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public static final byte[] BLOB_LENGTH_REQUEST = new byte[] { ISCConstants.isc_info_blob_total_length }; public int getBlobLength(IscBlobHandle blob) throws GDSException { try { byte[] info = gds.iscBlobInfo(blob, BLOB_LENGTH_REQUEST, 20); if (info.length == 0 || info[0] != ISCConstants.isc_info_blob_total_length) throw new GDSException(ISCConstants.isc_req_sync); int dataLength = gds.iscVaxInteger(info, 1, 2); return gds.iscVaxInteger(info, 3, dataLength); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public AbstractIscTrHandle startTransaction(TransactionParameterBuffer tpb) throws GDSException { try { AbstractIscTrHandle trHandle = (AbstractIscTrHandle) gds.createIscTrHandle(); gds.iscStartTransaction(trHandle, currentDbHandle, tpb); setCurrentTrHandle(trHandle); return trHandle; } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public void prepareTransaction(AbstractIscTrHandle trHandle, byte[] message) throws GDSException { try { gds.iscPrepareTransaction2(trHandle, message); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public void commitTransaction(AbstractIscTrHandle trHandle) throws GDSException { try { gds.iscCommitTransaction(trHandle); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public void rollbackTransaction(AbstractIscTrHandle trHandle) throws GDSException { try { gds.iscRollbackTransaction(trHandle); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public void detachDatabase() throws GDSException { try { gds.iscDetachDatabase(currentDbHandle); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } /** * Cancel the currently running operation. */ public void cancelOperation() throws GDSException { try { gds.fbCancelOperation(currentDbHandle, ISCConstants.fb_cancel_raise); } catch (GDSException ex) { notifyListeners(ex); throw ex; } } public int iscVaxInteger(byte[] buffer, int pos, int length) { return gds.iscVaxInteger(buffer, pos, length); } // for DatabaseMetaData. /** * Get the name of the database product that we're connected to. * * @return The database product name (i.e. Firebird or Interbase) */ public String getDatabaseProductName() { /** @todo add check if mc is not null */ return currentDbHandle.getDatabaseProductName(); } /** * Get the version of the the database that we're connected to * * @return the database product version */ public String getDatabaseProductVersion() { /** @todo add check if mc is not null */ return currentDbHandle.getDatabaseProductVersion(); } /** * Get the major version number of the database that we're connected to. * * @return The major version number of the database */ public int getDatabaseProductMajorVersion() { /** @todo add check if mc is not null */ return currentDbHandle.getDatabaseProductMajorVersion(); } /** * Get the minor version number of the database that we're connected to. * * @return The minor version number of the database */ public int getDatabaseProductMinorVersion() { /** @todo add check if mc is not null */ return currentDbHandle.getDatabaseProductMinorVersion(); } /** * Compares the version of this database to the specified major and * minor version. *

* This method follows the semantics of {@link Comparable}: returns a * negative value if the version of this database connection is smaller than * the supplied arguments, 0 if they are equal or positive if its bigger. *

* * @param major * Major version to compare * @param minor * Minor version to compare * @return a negative integer, zero, or a positive integer as this database * version is less than, equal to, or greater than the specified * major and minor version */ public int compareToVersion(int major, int minor) { int differenceMajor = getDatabaseProductMajorVersion() - major; if (differenceMajor == 0) { return getDatabaseProductMinorVersion() - minor; } return differenceMajor; } /** * Get the database login name of the user that we're connected as. * * @return The username of the current database user */ public String getUserName() { return dpb.getArgumentAsString(ISCConstants.isc_dpb_user); } /** * Get the buffer length for blobs for this connection. * * @return The length of blob buffers */ public int getBlobBufferLength() { if (dpb.hasArgument(DatabaseParameterBufferExtension.BLOB_BUFFER_SIZE)) return dpb.getArgumentAsInt(DatabaseParameterBufferExtension.BLOB_BUFFER_SIZE); else return DEFAULT_BLOB_BUFFER_SIZE; } /** * Get the encoding used for this connection. * * @return The name of the encoding used */ public String getIscEncoding() { try { String result = dpb.getArgumentAsString(ISCConstants.isc_dpb_lc_ctype); if (result == null) result = "NONE"; return result; } catch (NullPointerException ex) { return "NONE"; } } public String getJavaEncoding() { return dpb.getArgumentAsString(DatabaseParameterBufferExtension.LOCAL_ENCODING); } public String getMappingPath() { return dpb.getArgumentAsString(DatabaseParameterBufferExtension.MAPPING_PATH); } /** * Get all warnings associated with current connection. * * @return list of {@link GDSException}instances representing warnings for * this database connection. */ public List getWarnings() { if (currentDbHandle == null) return Collections.EMPTY_LIST; else return currentDbHandle.getWarnings(); } /** * Clear warnings for this database connection. */ public void clearWarnings() { if (currentDbHandle != null) currentDbHandle.clearWarnings(); } /** * Get connection handle for direct Firebird API access * * @return internal handle for connection */ public IscDbHandle getIscDBHandle() { return currentDbHandle; } /** * Get Firebird API handler (sockets/native/embeded/etc) * * @return handler object for internal API calls */ public GDS getInternalAPIHandler() { return gds; } @Override public final Object getSynchronizationObject() { return currentDbHandle; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy