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

org.firebirdsql.jdbc.FBConnection Maven / Gradle / Ivy

There is a newer version: 4.0.10.java7
Show newest version
/*
 * Firebird Open Source JavaEE Connector - JDBC Driver
 *
 * Distributable under LGPL license.
 * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * LGPL License for more details.
 *
 * This file was created by members of the firebird development team.
 * All individual contributions remain the Copyright (C) of those
 * individuals.  Contributors to this file are either listed here or
 * can be obtained from a source control history command.
 *
 * All rights reserved.
 */
package org.firebirdsql.jdbc;

import org.firebirdsql.gds.DatabaseParameterBuffer;
import org.firebirdsql.gds.ISCConstants;
import org.firebirdsql.gds.JaybirdErrorCodes;
import org.firebirdsql.gds.TransactionParameterBuffer;
import org.firebirdsql.gds.impl.DatabaseParameterBufferExtension;
import org.firebirdsql.gds.impl.GDSHelper;
import org.firebirdsql.gds.ng.FbDatabase;
import org.firebirdsql.gds.ng.FbExceptionBuilder;
import org.firebirdsql.jca.FBConnectionRequestInfo;
import org.firebirdsql.jca.FBLocalTransaction;
import org.firebirdsql.jca.FBManagedConnection;
import org.firebirdsql.jca.FirebirdLocalTransaction;
import org.firebirdsql.jdbc.escape.FBEscapedParser;
import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;
import org.firebirdsql.util.SQLExceptionChainBuilder;

import javax.resource.ResourceException;
import java.sql.*;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

import static org.firebirdsql.gds.impl.DatabaseParameterBufferExtension.USE_FIREBIRD_AUTOCOMMIT;

/**
 * The class FBConnection is a handle to a 
 * {@link FBManagedConnection}.
 *
 * @author David Jencks
 * @author Mark Rotteveel
 */
public class FBConnection implements FirebirdConnection, Synchronizable {

    private static final Logger log = LoggerFactory.getLogger(FBConnection.class);

    private static final String GET_CLIENT_INFO_SQL = "SELECT "
                + "    rdb$get_context('USER_SESSION', ?) session_context "
                + "  , rdb$get_context('USER_TRANSACTION', ?) tx_context " 
                + "FROM rdb$database";

    private static final String SET_CLIENT_INFO_SQL = "SELECT "
                + "  rdb$set_context('USER_SESSION', ?, ?) session_context " 
                + "FROM rdb$database";

    protected FBManagedConnection mc;

    private FBLocalTransaction localTransaction;
    private FBDatabaseMetaData metaData;
    
    protected final InternalTransactionCoordinator txCoordinator;

    private SQLWarning firstWarning;
     
    // This set contains all allocated but not closed statements
    // It is used to close them before the connection is closed
    protected final Set activeStatements = Collections.synchronizedSet(new HashSet());
    
    private int resultSetHoldability = ResultSet.CLOSE_CURSORS_AT_COMMIT;

    private StoredProcedureMetaData storedProcedureMetaData;
    private FBEscapedParser escapedParser;
	 
    /**
     * Create a new AbstractConnection instance based on a
     * {@link FBManagedConnection}.
     *
     * @param mc A FBManagedConnection around which this connection is based
     */
    public FBConnection(FBManagedConnection mc) {
        this.mc = mc;
        
        txCoordinator = new InternalTransactionCoordinator(this);
        
        FBConnectionRequestInfo cri = mc.getConnectionRequestInfo();

        resultSetHoldability = cri.hasArgument(DatabaseParameterBufferExtension.RESULT_SET_HOLDABLE)
                ? ResultSet.HOLD_CURSORS_OVER_COMMIT
                : ResultSet.CLOSE_CURSORS_AT_COMMIT;
    }
    
    public FBObjectListener.StatementListener getStatementListener() {
        return txCoordinator;
    }
    
    public int getHoldability() throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            return resultSetHoldability;
        }
    }

    public void setHoldability(int holdability) throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            this.resultSetHoldability = holdability;
        }
    }

    /**
     * Check if this connection is valid. This method should be invoked before
     * executing any action in this class.
     * 
     * @throws SQLException if this connection has been closed and cannot be 
     * used anymore.
     */
    protected void checkValidity() throws SQLException {
        if (isClosed()) {
            throw new FBSQLException("This connection is closed and cannot be used now.",
                SQLStateConstants.SQL_STATE_CONNECTION_CLOSED);
        }
    }
    
    /**
     * This method should be invoked by each of the statements in the 
     * {@link Statement#close()} method. Here we remove statement from the
     * activeStatements set, so we do not need to close it 
     * later.
     * 
     * @param stmt statement that was closed.
     */
    void notifyStatementClosed(FBStatement stmt) {
        if (!activeStatements.remove(stmt)) {
            // NOTE: This can also happen if the statement object was intercepted and replaced by a proxy
            // Examples: some of the tests in the Hibernate test suite (although that should be fixed now)
            log.warn("Specified statement was not created by this connection: " + stmt);
        }
    }
    
    /**
     * This method closes all active statements and cleans resources.
     * 
     * @throws SQLException if at least one of the active statements failed
     * to close gracefully.
     */
    protected void freeStatements() throws SQLException {
        // copy statements to avoid concurrent modification exception
        List statements = new ArrayList<>(activeStatements);
        
        // iterate through the set, close statements and collect exceptions
        SQLExceptionChainBuilder chain = new SQLExceptionChainBuilder<>();
        for (Statement stmt : statements) {
            try {
                stmt.close();
            } catch(SQLException ex) {
                chain.append(ex);
            }
        }
        
        // throw exception if there is any
        if (chain.hasException()) throw chain.getException();
    }

    /**
     * Set the {@link FBManagedConnection} around which this connection is
     * based.
     * @param mc The FBManagedConnection around which this connection is based
     */
    public void setManagedConnection(FBManagedConnection mc) {
        synchronized (getSynchronizationObject()) {
            //close any prepared statements we may have executed.
            if (this.mc != mc && metaData != null) {
                try {
                    metaData.close();
                } finally {
                    metaData = null;
                }
            }
            this.mc = mc;
        }
    }
    
    public FBManagedConnection getManagedConnection() {
        synchronized (getSynchronizationObject()) {
            return mc;
        }
    }

    /**
     * Get connection handle for direct Firebird API access
     *
     * @return internal handle for connection
     * @throws SQLException
     *         if handle needed to be created and creation failed
     */
    @Override
    public FbDatabase getFbDatabase() throws SQLException {
        return getGDSHelper().getCurrentDatabase();
    }

    /**
     * Get database parameter buffer for this connection.
     * 
     * @return instance of {@link DatabaseParameterBuffer}.
     */
    public DatabaseParameterBuffer getDatabaseParameterBuffer() {
        return mc != null ? mc.getConnectionRequestInfo().getDpb() : null;
    }

    @Deprecated
	public void setTransactionParameters(int isolationLevel, int[] parameters) throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            TransactionParameterBuffer tpbParams = createTransactionParameterBuffer();

            for (int parameter : parameters) {
                tpbParams.addArgument(parameter);
            }

            setTransactionParameters(isolationLevel, tpbParams);
        }
	}
    
    public TransactionParameterBuffer getTransactionParameters(int isolationLevel) throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            return mc.getTransactionParameters(isolationLevel);
        }
    }

    public TransactionParameterBuffer createTransactionParameterBuffer() throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            return getFbDatabase().createTransactionParameterBuffer();
        }
    }
    
    public void setTransactionParameters(int isolationLevel, TransactionParameterBuffer tpb) throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            if (mc.isManagedEnvironment()) {
                throw new FBSQLException("Cannot set transaction parameters in managed environment.");
            }

            mc.setTransactionParameters(isolationLevel, tpb);
        }
    }
    
    public void setTransactionParameters(TransactionParameterBuffer tpb) throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            try {
                if (getLocalTransaction().inTransaction()) {
                    throw new FBSQLException("Cannot set transaction parameters when transaction is already started.");
                }

                mc.setTransactionParameters(tpb);
            } catch (ResourceException ex) {
                throw new FBSQLException(ex);
            }
        }
    }

    /**
     * Creates a Statement object for sending
     * SQL statements to the database.
     * SQL statements without parameters are normally
     * executed using Statement objects. If the same SQL statement
     * is executed many times, it is more efficient to use a
     * PreparedStatement object.
     *

* * Result sets created using the returned Statement * object will by default have forward-only type and read-only concurrency. * * @return a new Statement object * @exception SQLException if a database access error occurs */ public Statement createStatement() throws SQLException { return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, resultSetHoldability); } /** * Creates a PreparedStatement object for sending * parameterized SQL statements to the database. * * A SQL statement with or without IN parameters can be * pre-compiled and stored in a PreparedStatement object. This * object can then be used to efficiently execute this statement * multiple times. * *

Note: This method is optimized for handling * parametric SQL statements that benefit from precompilation. If * the driver supports precompilation, * the method prepareStatement will send * the statement to the database for precompilation. Some drivers * may not support precompilation. In this case, the statement may * not be sent to the database until the PreparedStatement is * executed. This has no direct effect on users; however, it does * affect which method throws certain SQLExceptions. * * * Result sets created using the returned PreparedStatement will have * forward-only type and read-only concurrency, by default. * * @param sql a SQL statement that may contain one or more '?' IN * parameter placeholders * @return a new PreparedStatement object containing the * pre-compiled statement * @exception SQLException if a database access error occurs */ public PreparedStatement prepareStatement(String sql) throws SQLException { return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); } /** * Creates a CallableStatement object for calling * database stored procedures. * The CallableStatement object provides * methods for setting up its IN and OUT parameters, and * methods for executing the call to a stored procedure. * *

Note: This method is optimized for handling stored * procedure call statements. Some drivers may send the call * statement to the database when the method prepareCall * is done; others * may wait until the CallableStatement object * is executed. This has no * direct effect on users; however, it does affect which method * throws certain SQLExceptions. * * Result sets created using the returned CallableStatement will have * forward-only type and read-only concurrency, by default. * * @param sql a SQL statement that may contain one or more '?' * parameter placeholders. Typically this statement is a JDBC * function call escape string. * @return a new CallableStatement object containing the * pre-compiled SQL statement * @exception SQLException if a database access error occurs */ public CallableStatement prepareCall(String sql) throws SQLException { return prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); } public Blob createBlob() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); return new FBBlob(getGDSHelper(), txCoordinator); } } public Clob createClob() throws SQLException { FBBlob blob = (FBBlob)createBlob(); return new FBClob(blob); } public Struct createStruct(String typeName, Object[] attributes) throws SQLException { checkValidity(); throw new FBDriverNotCapableException("Type STRUCT not supported"); } public Array createArrayOf(String typeName, Object[] elements) throws SQLException { checkValidity(); throw new FBDriverNotCapableException("Type ARRAY not yet supported"); } /** * Converts the given SQL statement into the system's native SQL grammar. * A driver may convert the JDBC sql grammar into its system's * native SQL grammar prior to sending it; this method returns the * native form of the statement that the driver would have sent. * * @param sql a SQL statement that may contain one or more '?' * parameter placeholders * @return the native form of this statement * @exception SQLException if a database access error occurs */ public String nativeSQL(String sql) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); return getEscapedParser().parse(sql); } } /** * Returns the FBEscapedParser instance for this connection. * * @return Instance of FBEscapedParser */ protected FBEscapedParser getEscapedParser() { if (escapedParser == null) { DatabaseParameterBuffer dpb = getDatabaseParameterBuffer(); EscapeParserMode mode = dpb.hasArgument(DatabaseParameterBufferExtension.USE_STANDARD_UDF) ? EscapeParserMode.USE_STANDARD_UDF : EscapeParserMode.USE_BUILT_IN; escapedParser = new FBEscapedParser(mode); } return escapedParser; } /** * Sets this connection's auto-commit mode. * If a connection is in auto-commit mode, then all its SQL * statements will be executed and committed as individual * transactions. Otherwise, its SQL statements are grouped into * transactions that are terminated by a call to either * the method commit or the method rollback. * By default, new connections are in auto-commit * mode. * * The commit occurs when the statement completes or the next * execute occurs, whichever comes first. In the case of * statements returning a ResultSet, the statement completes when * the last row of the ResultSet has been retrieved or the * ResultSet has been closed. In advanced cases, a single * statement may return multiple results as well as output * parameter values. In these cases the commit occurs when all results and * output parameter values have been retrieved. * * @param autoCommit true enables auto-commit; false disables * auto-commit. * @exception SQLException if a database access error occurs */ public void setAutoCommit(boolean autoCommit) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); if (getAutoCommit() == autoCommit) { return; } // FIXME : Behavior in switch might be wrong, see also setSavePoint txCoordinator.switchTransactionCoordinator(autoCommit); } } protected void setTransactionCoordinator(boolean managedConnection, boolean autoCommit) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); txCoordinator.setTransactionCoordinator(managedConnection, autoCommit); } } public void setManagedEnvironment(boolean managedConnection) throws SQLException { synchronized (getSynchronizationObject()) { setTransactionCoordinator(managedConnection, true); } } /** * Gets the current auto-commit state. * * @return the current state of auto-commit mode * @exception SQLException if a database access error occurs * @see #setAutoCommit */ public boolean getAutoCommit() throws SQLException { synchronized (getSynchronizationObject()) { if (isClosed()) { throw new FBSQLException("You cannot getAutoCommit on an unassociated closed connection."); } return txCoordinator.getAutoCommit(); } } /** * Makes all changes made since the previous * commit/rollback permanent and releases any database locks * currently held by the Connection. This method should be * used only when auto-commit mode has been disabled. * * @exception SQLException if a database access error occurs * @see #setAutoCommit */ public void commit() throws SQLException { synchronized (getSynchronizationObject()) { if (isClosed()) { throw new FBSQLException( "You cannot commit a closed connection.", SQLStateConstants.SQL_STATE_CONNECTION_CLOSED); } if (mc.inDistributedTransaction()) { throw new FBSQLException("Connection enlisted in distributed transaction", SQLStateConstants.SQL_STATE_INVALID_TX_STATE); } txCoordinator.commit(); invalidateTransactionLifetimeObjects(); } } /** * Drops all changes made since the previous * commit/rollback and releases any database locks currently held * by this Connection. This method should be used only when auto- * commit has been disabled. * * @exception SQLException if a database access error occurs * @see #setAutoCommit */ public void rollback() throws SQLException { synchronized (getSynchronizationObject()) { if (isClosed()) { throw new FBSQLException( "You cannot rollback closed connection.", SQLStateConstants.SQL_STATE_CONNECTION_CLOSED); } if (mc.inDistributedTransaction()) { throw new FBSQLException("Connection enlisted in distributed transaction", SQLStateConstants.SQL_STATE_INVALID_TX_STATE); } txCoordinator.rollback(); invalidateTransactionLifetimeObjects(); } } /** * Invalidate everything that should only last for the lifetime of the current transaction. */ protected void invalidateTransactionLifetimeObjects(){ invalidateSavepoints(); storedProcedureMetaData = null; } /** * Releases a Connection's database and JDBC resources * immediately instead of waiting for * them to be automatically released. * *

Note: A Connection is automatically closed when it is * garbage collected. Certain fatal errors also result in a closed * Connection. * * @exception SQLException if a database access error occurs */ public void close() throws SQLException { SQLExceptionChainBuilder chainBuilder = new SQLExceptionChainBuilder<>(); synchronized (getSynchronizationObject()) { if (log.isTraceEnabled()) { log.trace("Connection closed requested at", new RuntimeException("Connection close logging")); } try { freeStatements(); if (metaData != null) metaData.close(); } catch (SQLException e) { chainBuilder.append(e); } finally { metaData = null; if (mc != null) { // leave managed transactions alone, they are normally // committed after the Connection handle is closed. if (!mc.inDistributedTransaction()) { try { txCoordinator.handleConnectionClose(); } catch (SQLException e) { chainBuilder.append(e); } finally { try { setAutoCommit(true); } catch (SQLException e) { if (!SQLStateConstants.SQL_STATE_CONNECTION_CLOSED.equals(e.getSQLState())) { chainBuilder.append(e); } } } } mc.close(this); mc = null; } } } if (chainBuilder.hasException()) { throw chainBuilder.getException(); } } /** * Tests to see if a Connection is closed. * * @return true if the connection is closed; false if it's still open */ public boolean isClosed() { return mc == null; } public boolean isValid(int timeout) throws SQLException { if (timeout < 0) { throw new SQLException("Timeout should be >= 0", SQLStateConstants.SQL_STATE_INVALID_ARG_VALUE); } synchronized (getSynchronizationObject()) { if (isClosed()) { return false; } // TODO Check if we can set the connection timeout temporarily for this call if (timeout != 0) { addWarning(new SQLWarning( String.format("Connection.isValid does not support non-zero timeouts, timeout value %d has been ignored", timeout))); } try { getFbDatabase().getDatabaseInfo( new byte[] { ISCConstants.isc_info_ods_version, ISCConstants.isc_info_end }, 10); return true; } catch (SQLException ex) { return false; } } } //====================================================================== // Advanced features: /** * Gets the metadata regarding this connection's database. * A Connection's database is able to provide information * describing its tables, its supported SQL grammar, its stored * procedures, the capabilities of this connection, and so on. This * information is made available through a DatabaseMetaData * object. * * @return a DatabaseMetaData object for this Connection * @exception SQLException if a database access error occurs */ public DatabaseMetaData getMetaData() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); if (metaData == null) metaData = new FBDatabaseMetaData(this); return metaData; } } /** * Puts this connection in read-only mode as a hint to enable * database optimizations. * *

Note: This method cannot be called while in the * middle of a transaction. * * @param readOnly true enables read-only mode; false disables * read-only mode. * @exception SQLException if a database access error occurs */ public void setReadOnly(boolean readOnly) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); try { if (getLocalTransaction().inTransaction() && !mc.isManagedEnvironment()) throw new FBSQLException("Calling setReadOnly(boolean) method " + "is not allowed when transaction is already started."); mc.setReadOnly(readOnly); } catch (ResourceException ex) { throw new FBSQLException(ex); } } } /** * Tests to see if the connection is in read-only mode. * * @return true if connection is read-only and false otherwise * @exception SQLException if a database access error occurs */ public boolean isReadOnly() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); return mc.isReadOnly(); } } /** * Sets a catalog name in order to select * a subspace of this Connection's database in which to work. * If the driver does not support catalogs, it will * silently ignore this request. * * @exception SQLException if a database access error occurs */ public void setCatalog(String catalog) throws SQLException { checkValidity(); } /** * Returns the Connection's current catalog name. * * @return the current catalog name or null * @exception SQLException if a database access error occurs */ public String getCatalog() throws SQLException { checkValidity(); return null; } /** * Attempts to change the transaction * isolation level to the one given. * The constants defined in the interface Connection * are the possible transaction isolation levels. * *

Calling this method will commit any current transaction. * * @param level one of the TRANSACTION_* isolation values with the * exception of TRANSACTION_NONE; some databases may not support * other values * @exception SQLException if a database access error occurs * @see DatabaseMetaData#supportsTransactionIsolationLevel */ public void setTransactionIsolation(int level) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); try { if (!getAutoCommit() && !mc.isManagedEnvironment()) txCoordinator.commit(); mc.setTransactionIsolation(level); } catch (ResourceException re) { throw new FBSQLException(re); } } } /** * Gets this Connection's current transaction isolation level. * * @return the current TRANSACTION_* mode value * @exception SQLException if a database access error occurs */ public int getTransactionIsolation() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); try { return mc.getTransactionIsolation(); } catch (ResourceException e) { throw new FBSQLException(e); } } } /** * Returns the first warning reported by calls on this Connection. * *

Note: Subsequent warnings will be chained to this * SQLWarning. * * @return the first SQLWarning or null * @exception SQLException if a database access error occurs */ public SQLWarning getWarnings() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); return firstWarning; } } /** * Clears all warnings reported for this Connection object. * After a call to this method, the method getWarnings * returns null until a new warning is * reported for this Connection. * * @exception SQLException if a database access error occurs */ public void clearWarnings() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); firstWarning = null; } } /** * * Creates a Statement object that will generate * ResultSet objects with the given type and concurrency. * This method is the same as the createStatement method * above, but it allows the default result set * type and result set concurrency type to be overridden. * * @param resultSetType a result set type; see ResultSet.TYPE_XXX * @param resultSetConcurrency a concurrency type; see ResultSet.CONCUR_XXX * @return a new Statement object * @exception SQLException if a database access error occurs * @since 1.2 * @see What Is in the JDBC 2.0 API */ public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return createStatement(resultSetType, resultSetConcurrency, this.resultSetHoldability); } /** * Creates a Statement object that will generate * ResultSet objects with the given type, concurrency, * and holdability. * This method is the same as the createStatement method * above, but it allows the default result set * type, concurrency, and holdability to be overridden. * * @param resultSetType one of the following ResultSet * constants: * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @param resultSetConcurrency one of the following ResultSet * constants: * ResultSet.CONCUR_READ_ONLY or * ResultSet.CONCUR_UPDATABLE * @param resultSetHoldability one of the following ResultSet * constants: * ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT * @return a new Statement object that will generate * ResultSet objects with the given type, * concurrency, and holdability * @exception SQLException if a database access error occurs * or the given parameters are not ResultSet * constants indicating type, concurrency, and holdability * @see ResultSet * @since 1.4 */ public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); if (resultSetHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT && resultSetType == ResultSet.TYPE_FORWARD_ONLY) { addWarning(FbExceptionBuilder .forWarning(JaybirdErrorCodes.jb_resultSetTypeUpgradeReasonHoldability) .toFlatSQLException(SQLWarning.class)); resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE; } if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE) { addWarning(FbExceptionBuilder .forWarning(JaybirdErrorCodes.jb_resultSetTypeDowngradeReasonScrollSensitive) .toFlatSQLException(SQLWarning.class)); resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE; } checkHoldability(resultSetType, resultSetHoldability); Statement stmt = new FBStatement(getGDSHelper(), resultSetType, resultSetConcurrency, resultSetHoldability, txCoordinator); activeStatements.add(stmt); return stmt; } } /** * Check whether result set type and holdability are compatible. * * @param resultSetType desired result set type. * @param resultSetHoldability desired result set holdability. * * @throws SQLException if specified result set type and holdability are * not compatibe. */ private void checkHoldability(int resultSetType, int resultSetHoldability) throws SQLException { boolean holdable = resultSetHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT; boolean notScrollable = resultSetType != ResultSet.TYPE_SCROLL_INSENSITIVE; if (holdable && notScrollable) throw new FBDriverNotCapableException( "Holdable cursors are supported only " + "for scrollable insensitive result sets."); } /** * * Creates a PreparedStatement object that will generate * ResultSet objects with the given type and concurrency. * This method is the same as the prepareStatement method * above, but it allows the default result set * type and result set concurrency type to be overridden. * * @param resultSetType a result set type; see ResultSet.TYPE_XXX * @param resultSetConcurrency a concurrency type; see ResultSet.CONCUR_XXX * @return a new PreparedStatement object containing the * pre-compiled SQL statement * @exception SQLException if a database access error occurs * @since 1.2 * @see What Is in the JDBC 2.0 API */ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return prepareStatement(sql, resultSetType, resultSetConcurrency, this.resultSetHoldability); } /** * Creates a PreparedStatement object that will generate * ResultSet objects with the given type, concurrency, * and holdability. *

* This method is the same as the prepareStatement method * above, but it allows the default result set * type, concurrency, and holdability to be overridden. * * @param sql a String object that is the SQL statement to * be sent to the database; may contain one or more '?' IN * parameters * @param resultSetType one of the following ResultSet * constants: * ResultSet.TYPE_FORWARD_ONLY, * ResultSet.TYPE_SCROLL_INSENSITIVE, or * ResultSet.TYPE_SCROLL_SENSITIVE * @param resultSetConcurrency one of the following ResultSet * constants: * ResultSet.CONCUR_READ_ONLY or * ResultSet.CONCUR_UPDATABLE * @param resultSetHoldability one of the following ResultSet * constants: * ResultSet.HOLD_CURSORS_OVER_COMMIT or * ResultSet.CLOSE_CURSORS_AT_COMMIT * @return a new PreparedStatement object, containing the * pre-compiled SQL statement, that will generate * ResultSet objects with the given type, * concurrency, and holdability * @exception SQLException if a database access error occurs, this * method is called on a closed connection * or the given parameters are not ResultSet * constants indicating type, concurrency, and holdability * @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method or this method is not supported for the specified result * set type, result set holdability and result set concurrency. * @see ResultSet * @since 1.4 */ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability, false, false); } protected PreparedStatement prepareMetaDataStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability, true, false); } /** * Creates a default PreparedStatement object that has * the capability to retrieve auto-generated keys. The given constant * tells the driver whether it should make auto-generated keys * available for retrieval. This parameter is ignored if the SQL statement * is not an INSERT statement, or an SQL statement able to return * auto-generated keys (the list of such statements is vendor-specific). *

* Note: This method is optimized for handling * parametric SQL statements that benefit from precompilation. If * the driver supports precompilation, * the method prepareStatement will send * the statement to the database for precompilation. Some drivers * may not support precompilation. In this case, the statement may * not be sent to the database until the PreparedStatement * object is executed. This has no direct effect on users; however, it does * affect which methods throw certain SQLExceptions. *

* Result sets created using the returned PreparedStatement * object will by default be type TYPE_FORWARD_ONLY * and have a concurrency level of CONCUR_READ_ONLY. * The holdability of the created result sets can be determined by * calling {@link #getHoldability}. * * @param sql an SQL statement that may contain one or more '?' IN * parameter placeholders * @param autoGeneratedKeys a flag indicating whether auto-generated keys * should be returned; one of * Statement.RETURN_GENERATED_KEYS or * Statement.NO_GENERATED_KEYS * @return a new PreparedStatement object, containing the * pre-compiled SQL statement, that will have the capability of * returning auto-generated keys * @exception SQLException if a database access error occurs, this * method is called on a closed connection * or the given parameter is not a Statement * constant indicating whether auto-generated keys should be * returned * @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method with a constant of Statement.RETURN_GENERATED_KEYS * @since 1.4 */ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { checkValidity(); if (autoGeneratedKeys == Statement.RETURN_GENERATED_KEYS) { checkAutoGeneratedKeysSupport(); } GeneratedKeysQuery query = new GeneratedKeysQuery(sql, autoGeneratedKeys); return prepareStatement(query); } /** * Creates a default PreparedStatement object capable * of returning the auto-generated keys designated by the given array. * This array contains the indexes of the columns in the target * table that contain the auto-generated keys that should be made * available. The driver will ignore the array if the SQL statement * is not an INSERT statement, or an SQL statement able to return * auto-generated keys (the list of such statements is vendor-specific). *

* An SQL statement with or without IN parameters can be * pre-compiled and stored in a PreparedStatement object. This * object can then be used to efficiently execute this statement * multiple times. *

* Note: This method is optimized for handling * parametric SQL statements that benefit from precompilation. If * the driver supports precompilation, * the method prepareStatement will send * the statement to the database for precompilation. Some drivers * may not support precompilation. In this case, the statement may * not be sent to the database until the PreparedStatement * object is executed. This has no direct effect on users; however, it does * affect which methods throw certain SQLExceptions. *

* Result sets created using the returned PreparedStatement * object will by default be type TYPE_FORWARD_ONLY * and have a concurrency level of CONCUR_READ_ONLY. * The holdability of the created result sets can be determined by * calling {@link #getHoldability}. * * @param sql an SQL statement that may contain one or more '?' IN * parameter placeholders * @param columnIndexes an array of column indexes indicating the columns * that should be returned from the inserted row or rows * @return a new PreparedStatement object, containing the * pre-compiled statement, that is capable of returning the * auto-generated keys designated by the given array of column * indexes * @exception SQLException if a database access error occurs * or this method is called on a closed connection * @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * * @since 1.4 */ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { checkValidity(); checkAutoGeneratedKeysSupport(); GeneratedKeysQuery query = new GeneratedKeysQuery(sql, columnIndexes); return prepareStatement(query); } /** * Creates a default PreparedStatement object capable * of returning the auto-generated keys designated by the given array. * This array contains the names of the columns in the target * table that contain the auto-generated keys that should be returned. * The driver will ignore the array if the SQL statement * is not an INSERT statement, or an SQL statement able to return * auto-generated keys (the list of such statements is vendor-specific). *

* An SQL statement with or without IN parameters can be * pre-compiled and stored in a PreparedStatement object. This * object can then be used to efficiently execute this statement * multiple times. *

* Note: This method is optimized for handling * parametric SQL statements that benefit from precompilation. If * the driver supports precompilation, * the method prepareStatement will send * the statement to the database for precompilation. Some drivers * may not support precompilation. In this case, the statement may * not be sent to the database until the PreparedStatement * object is executed. This has no direct effect on users; however, it does * affect which methods throw certain SQLExceptions. *

* Result sets created using the returned PreparedStatement * object will by default be type TYPE_FORWARD_ONLY * and have a concurrency level of CONCUR_READ_ONLY. * The holdability of the created result sets can be determined by * calling {@link #getHoldability}. * * @param sql an SQL statement that may contain one or more '?' IN * parameter placeholders * @param columnNames an array of column names indicating the columns * that should be returned from the inserted row or rows * @return a new PreparedStatement object, containing the * pre-compiled statement, that is capable of returning the * auto-generated keys designated by the given array of column * names * @exception SQLException if a database access error occurs * or this method is called on a closed connection * @exception java.sql.SQLFeatureNotSupportedException if the JDBC driver does not support * this method * * @since 1.4 */ public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { checkValidity(); checkAutoGeneratedKeysSupport(); GeneratedKeysQuery query = new GeneratedKeysQuery(sql, columnNames); return prepareStatement(query); } /** * Prepares a statement for generated keys. * * @param query AbstractGeneratedKeysQuery instance * @return PreparedStatement object * @throws SQLException if a database access error occurs * or this method is called on a closed connection */ private PreparedStatement prepareStatement(AbstractGeneratedKeysQuery query) throws SQLException { if (query.generatesKeys()) { return prepareStatement(query.getQueryString(), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.CLOSE_CURSORS_AT_COMMIT, false, true); } else { return prepareStatement(query.getQueryString()); } } /** * Helper method to check support of autoGeneratedKeys * * @throws SQLException If the feature is not supported, or if an error occurred retrieving the server version. */ protected void checkAutoGeneratedKeysSupport() throws SQLException { GDSHelper gdsHelper = getGDSHelper(); if (gdsHelper.compareToVersion(2, 0) < 0) { throw new FBDriverNotCapableException("This version of Firebird does not support retrieving generated keys (support was added in Firebird 2.0)"); } } protected PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability, boolean metaData, boolean generatedKeys) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); if (resultSetHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT && resultSetType == ResultSet.TYPE_FORWARD_ONLY) { addWarning(FbExceptionBuilder .forWarning(JaybirdErrorCodes.jb_resultSetTypeUpgradeReasonHoldability) .toFlatSQLException(SQLWarning.class)); resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE; } else if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE) { addWarning(FbExceptionBuilder .forWarning(JaybirdErrorCodes.jb_resultSetTypeDowngradeReasonScrollSensitive) .toFlatSQLException(SQLWarning.class)); resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE; } checkHoldability(resultSetType, resultSetHoldability); FBObjectListener.StatementListener coordinator = txCoordinator; if (metaData) coordinator = new InternalTransactionCoordinator.MetaDataTransactionCoordinator(txCoordinator); FBObjectListener.BlobListener blobCoordinator = metaData ? null : txCoordinator; PreparedStatement stmt = new FBPreparedStatement(getGDSHelper(), sql, resultSetType, resultSetConcurrency, resultSetHoldability, coordinator, blobCoordinator, metaData, false, generatedKeys); activeStatements.add(stmt); return stmt; } } /** * * Creates a CallableStatement object that will generate * ResultSet objects with the given type and concurrency. * This method is the same as the prepareCall method * above, but it allows the default result set * type and result set concurrency type to be overridden. * * @param resultSetType a result set type; see ResultSet.TYPE_XXX * @param resultSetConcurrency a concurrency type; see ResultSet.CONCUR_XXX * @return a new CallableStatement object containing the * pre-compiled SQL statement * @exception SQLException if a database access error occurs * @since 1.2 * @see What Is in the JDBC 2.0 API */ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return prepareCall(sql, resultSetType, resultSetConcurrency, this.resultSetHoldability); } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); if (resultSetHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT && resultSetType == ResultSet.TYPE_FORWARD_ONLY) { addWarning(FbExceptionBuilder .forWarning(JaybirdErrorCodes.jb_resultSetTypeUpgradeReasonHoldability) .toFlatSQLException(SQLWarning.class)); resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE; } else if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE) { addWarning(FbExceptionBuilder .forWarning(JaybirdErrorCodes.jb_resultSetTypeDowngradeReasonScrollSensitive) .toFlatSQLException(SQLWarning.class)); resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE; } if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY) { addWarning(FbExceptionBuilder .forWarning(JaybirdErrorCodes.jb_concurrencyResetReadOnlyReasonStoredProcedure) .toSQLException(SQLWarning.class)); resultSetConcurrency = ResultSet.CONCUR_READ_ONLY; } checkHoldability(resultSetType, resultSetHoldability); if (storedProcedureMetaData == null) { storedProcedureMetaData = StoredProcedureMetaDataFactory.getInstance(this); } FBCallableStatement stmt = new FBCallableStatement(getGDSHelper(), sql, resultSetType, resultSetConcurrency, resultSetHoldability, storedProcedureMetaData, txCoordinator, txCoordinator); activeStatements.add(stmt); return stmt; } } /** * * Gets the type map object associated with this connection. * Unless the application has added an entry to the type map, * the map returned will be empty. * * @return the java.util.Map object associated * with this Connection object * @since 1.2 * @see What Is in the JDBC 2.0 API */ public Map> getTypeMap() throws SQLException { return new HashMap<>(); } public void setTypeMap(Map> map) throws SQLException { throw new FBDriverNotCapableException(); } private Set clientInfoPropNames = new HashSet<>(); /* * Savepoint stuff. */ private final AtomicInteger savepointCounter = new AtomicInteger(); private final List savepoints = new ArrayList<>(); private int getNextSavepointCounter() { return savepointCounter.getAndIncrement(); } public Savepoint setSavepoint() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); FBSavepoint savepoint = new FBSavepoint(getNextSavepointCounter()); setSavepoint(savepoint); return savepoint; } } /** * Set the savepoint on the server. * * @param savepoint savepoint to set. * * @throws SQLException if something went wrong. */ private void setSavepoint(FBSavepoint savepoint) throws SQLException { if (getAutoCommit()) { throw new SQLException("Connection.setSavepoint() method cannot be used in auto-commit mode.", SQLStateConstants.SQL_STATE_INVALID_TX_STATE); } if (mc.inDistributedTransaction()) { throw new SQLException("Connection enlisted in distributed transaction", SQLStateConstants.SQL_STATE_INVALID_TX_STATE); } txCoordinator.ensureTransaction(); getGDSHelper().executeImmediate("SAVEPOINT " + savepoint.getServerSavepointId()); savepoints.add(savepoint); } public Savepoint setSavepoint(String name) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); FBSavepoint savepoint = new FBSavepoint(name); setSavepoint(savepoint); return savepoint; } } public void rollback(Savepoint savepoint) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); if (getAutoCommit()) { throw new SQLException("Connection.rollback(Savepoint) method cannot be used in auto-commit mode.", SQLStateConstants.SQL_STATE_INVALID_TX_STATE); } // TODO The error message and actual condition do not match if (!(savepoint instanceof FBSavepoint)) { throw new SQLException("Specified savepoint was not obtained from this connection."); } if (mc.inDistributedTransaction()) { throw new SQLException("Connection enlisted in distributed transaction", SQLStateConstants.SQL_STATE_INVALID_TX_STATE); } FBSavepoint fbSavepoint = (FBSavepoint) savepoint; if (!fbSavepoint.isValid()) throw new SQLException("Savepoint is no longer valid."); getGDSHelper().executeImmediate("ROLLBACK TO " + fbSavepoint.getServerSavepointId()); } } public void releaseSavepoint(Savepoint savepoint) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); if (getAutoCommit()) { throw new SQLException("Connection.releaseSavepoint() method cannot be used in auto-commit mode.", SQLStateConstants.SQL_STATE_INVALID_TX_STATE); } // TODO The error message and actual condition do not match if (!(savepoint instanceof FBSavepoint)) { throw new SQLException("Specified savepoint was not obtained from this connection."); } FBSavepoint fbSavepoint = (FBSavepoint) savepoint; if (!fbSavepoint.isValid()) { throw new SQLException("Savepoint is no longer valid."); } getGDSHelper().executeImmediate("RELEASE SAVEPOINT " + fbSavepoint.getServerSavepointId() + " ONLY"); fbSavepoint.invalidate(); savepoints.remove(fbSavepoint); } } /** * Invalidate all savepoints. */ protected void invalidateSavepoints() { synchronized (getSynchronizationObject()) { for (FBSavepoint savepoint : savepoints) { savepoint.invalidate(); } savepoints.clear(); } } //------------------------------------------- //Borrowed from javax.resource.cci.Connection /** * Returns a FBLocalTransaction instance that enables a component to * demarcate resource manager local transactions on this connection. */ public FirebirdLocalTransaction getLocalTransaction() { synchronized (getSynchronizationObject()) { if (localTransaction == null) localTransaction = new FBLocalTransaction(mc, this); return localTransaction; } } // java.sql.Wrapper interface public boolean isWrapperFor(Class iface) throws SQLException { return iface != null && iface.isAssignableFrom(FBConnection.class); } public T unwrap(Class iface) throws SQLException { if (!isWrapperFor(iface)) throw new SQLException("Unable to unwrap to class " + iface.getName()); return iface.cast(this); } public void setSchema(String schema) throws SQLException { // Ignore: no schema support checkValidity(); } public String getSchema() throws SQLException { checkValidity(); return null; } //package methods /** * Check if this connection is currently involved in a transaction */ public boolean inTransaction() throws SQLException { return getGDSHelper().inTransaction(); } /** * Get the encoding that is being used for this connection. * * @return The name of the encoding used */ public String getIscEncoding() throws SQLException { return getGDSHelper().getIscEncoding(); } public void addWarning(SQLWarning warning){ synchronized (getSynchronizationObject()) { // TODO: Find way so this method can be protected (or less visible) again. if (firstWarning == null) firstWarning = warning; else { firstWarning.setNextWarning(warning); } } } /** * {@inheritDoc} *

* Implementation note: This method behaves exactly the same as {@link #createClob()}. *

*/ public NClob createNClob() throws SQLException { return (NClob) createClob(); } public SQLXML createSQLXML() throws SQLException { checkValidity(); throw new FBDriverNotCapableException("Type SQLXML not supported"); } public GDSHelper getGDSHelper() throws SQLException { if (mc == null) // TODO Right error code? throw new FbExceptionBuilder().exception(ISCConstants.isc_req_no_trans).toSQLException(); return mc.getGDSHelper(); } @Override public boolean isUseFirebirdAutoCommit() { DatabaseParameterBuffer dpb = getDatabaseParameterBuffer(); return dpb != null && dpb.hasArgument(USE_FIREBIRD_AUTOCOMMIT); } protected void finalize() throws Throwable { try { close(); } finally { super.finalize(); } } /** * Checks if client info is supported. * * @throws SQLException If the client info is not supported, or if there is no database connection. */ protected void checkClientInfoSupport() throws SQLException { if (!getFbDatabase().getServerVersion().isEqualOrAbove(2, 0)) { throw new FBDriverNotCapableException( "Required functionality (RDB$SET_CONTEXT()) only available in Firebird 2.0 or higher"); } } public Properties getClientInfo() throws SQLException { checkValidity(); checkClientInfoSupport(); Properties result = new Properties(); try (PreparedStatement stmt = prepareStatement(GET_CLIENT_INFO_SQL)) { for (String propName : clientInfoPropNames) { result.put(propName, getClientInfo(stmt, propName)); } } return result; } public String getClientInfo(String name) throws SQLException { checkValidity(); checkClientInfoSupport(); try (PreparedStatement stmt = prepareStatement(GET_CLIENT_INFO_SQL)) { return getClientInfo(stmt, name); } } protected String getClientInfo(PreparedStatement stmt, String name) throws SQLException { stmt.clearParameters(); stmt.setString(1, name); stmt.setString(2, name); try (ResultSet rs = stmt.executeQuery()) { if (!rs.next()) return null; String sessionContext = rs.getString(1); String transactionContext = rs.getString(2); if (transactionContext != null) return transactionContext; else if (sessionContext != null) return sessionContext; else return null; } } public void setClientInfo(Properties properties) throws SQLClientInfoException { SQLExceptionChainBuilder chain = new SQLExceptionChainBuilder<>(); try { checkValidity(); checkClientInfoSupport(); try (PreparedStatement stmt = prepareStatement(SET_CLIENT_INFO_SQL)) { for (String propName : properties.stringPropertyNames()) { String propValue = properties.getProperty(propName); try { setClientInfo(stmt, propName, propValue); } catch (SQLClientInfoException ex) { chain.append(ex); } } } } catch (SQLException ex) { throw new SQLClientInfoException(ex.getMessage(), ex.getSQLState(), null, ex); } if (chain.hasException()) throw chain.getException(); } public void setClientInfo(String name, String value) throws SQLClientInfoException { try { checkValidity(); checkClientInfoSupport(); try (PreparedStatement stmt = prepareStatement(SET_CLIENT_INFO_SQL)) { setClientInfo(stmt, name, value); } } catch (SQLException ex) { throw new SQLClientInfoException(ex.getMessage(), ex.getSQLState(), null, ex); } } protected void setClientInfo(PreparedStatement stmt, String name, String value) throws SQLException { try { stmt.clearParameters(); stmt.setString(1, name); stmt.setString(2, value); ResultSet rs = stmt.executeQuery(); if (!rs.next()) throw new FBDriverConsistencyCheckException( "Expected result from RDB$SET_CONTEXT call"); // needed, since the value is set on fetch!!! rs.getInt(1); } catch (SQLException ex) { throw new SQLClientInfoException(null, ex); } } public void abort(Executor executor) throws SQLException { // TODO Write implementation checkValidity(); throw new FBDriverNotCapableException(); } public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { // TODO Write implementation checkValidity(); throw new FBDriverNotCapableException(); } public int getNetworkTimeout() throws SQLException { // TODO Write implementation checkValidity(); return 0; } @Override public final Object getSynchronizationObject() { final FBManagedConnection managedConnection = mc; if (managedConnection != null) { return managedConnection.getSynchronizationObject(); } else { return this; } } protected class GeneratedKeysQuery extends AbstractGeneratedKeysQuery { protected GeneratedKeysQuery(String sql, int autoGeneratedKeys) throws SQLException { super(sql, autoGeneratedKeys); } protected GeneratedKeysQuery(String sql, int[] columnIndexes) throws SQLException { super(sql, columnIndexes); } protected GeneratedKeysQuery(String sql, String[] columnNames) throws SQLException { super(sql, columnNames); } @Override DatabaseMetaData getDatabaseMetaData() throws SQLException { return getMetaData(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy