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: 6.0.0-beta-1
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.lang.ref.WeakReference;
import java.sql.*;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

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

/**
 * The class FBConnection is a handle to a 
 * {@link FBManagedConnection} and implements {@link Connection}.
 *
 * @author David Jencks
 * @author Mark Rotteveel
 */
@SuppressWarnings("RedundantThrows")
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";

    private static final String PERMISSION_SET_NETWORK_TIMEOUT = "setNetworkTimeout";

    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;
    private GeneratedKeysSupport generatedKeysSupport;
	 
    /**
     * 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;
    }

    @Override
    public int getHoldability() throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            return resultSetHoldability;
        }
    }

    @Override
    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)) {
            if (stmt instanceof FBPreparedStatement && ((FBPreparedStatement) stmt).isInitialized()) {
                // Close was likely triggered by finalizer of a prepared statement that failed on prepare in
                // the constructor: Do not log warning
                return;
            }
            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;
        }
    }

    @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
    @Override
	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);
        }
	}

    @Override
    public TransactionParameterBuffer getTransactionParameters(int isolationLevel) throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            return mc.getTransactionParameters(isolationLevel);
        }
    }

    @Override
    public TransactionParameterBuffer createTransactionParameterBuffer() throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            return getFbDatabase().createTransactionParameterBuffer();
        }
    }

    @Override
    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);
        }
    }

    @Override
    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);
            }
        }
    }

    @Override
    public Statement createStatement() throws SQLException {
        return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, resultSetHoldability);
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    }

    @Override
    public Blob createBlob() throws SQLException {
        synchronized (getSynchronizationObject()) {
            checkValidity();
            return new FBBlob(getGDSHelper(), txCoordinator);
        }
    }

    @Override
    public Clob createClob() throws SQLException {
        FBBlob blob = (FBBlob)createBlob();
        return new FBClob(blob);
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        checkValidity();
        throw new FBDriverNotCapableException("Type STRUCT not supported");
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        checkValidity();
        throw new FBDriverNotCapableException("Type ARRAY not yet supported");
    }

    @Override
    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;
    }

    @Override
    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);
        }
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        synchronized (getSynchronizationObject()) {
            if (isClosed()) {
                throw new FBSQLException("You cannot getAutoCommit on an unassociated closed connection.");
            }
            return txCoordinator.getAutoCommit();
        }
    }

    @Override
    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();
        }
    }

    @Override
    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;
    }

    /**
     * {@inheritDoc}
     * 

* Implementation note: Certain fatal errors also result in a closed Connection. *

*/ @Override 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(); } } @Override public boolean isClosed() { return mc == null; } @Override 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; } } } @Override public DatabaseMetaData getMetaData() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); if (metaData == null) metaData = new FBDatabaseMetaData(this); return metaData; } } @Override 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); } } } @Override public boolean isReadOnly() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); return mc.isReadOnly(); } } /** * {@inheritDoc} *

* Implementation ignores calls to this method as catalogs are not supported. *

*/ @Override public void setCatalog(String catalog) throws SQLException { checkValidity(); } /** * {@inheritDoc} * * @return Always {@code null} as catalogs are not supported. */ @Override public String getCatalog() throws SQLException { checkValidity(); return null; } @Override 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); } } } @Override public int getTransactionIsolation() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); try { return mc.getTransactionIsolation(); } catch (ResourceException e) { throw new FBSQLException(e); } } } @Override public SQLWarning getWarnings() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); return firstWarning; } } @Override public void clearWarnings() throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); firstWarning = null; } } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return createStatement(resultSetType, resultSetConcurrency, this.resultSetHoldability); } @Override 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."); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return prepareStatement(sql, resultSetType, resultSetConcurrency, this.resultSetHoldability); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability, false, false); } // TODO Why unused? Remove? protected PreparedStatement prepareMetaDataStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability, true, false); } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); GeneratedKeysSupport.Query query = getGeneratedKeysSupport() .buildQuery(sql, autoGeneratedKeys); return prepareStatement(query); } } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); GeneratedKeysSupport.Query query = getGeneratedKeysSupport() .buildQuery(sql, columnIndexes); return prepareStatement(query); } } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); GeneratedKeysSupport.Query query = getGeneratedKeysSupport() .buildQuery(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(GeneratedKeysSupport.Query 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()); } } 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; } } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return prepareCall(sql, resultSetType, resultSetConcurrency, this.resultSetHoldability); } @Override 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; } } @Override public Map> getTypeMap() throws SQLException { return new HashMap<>(); } @Override public void setTypeMap(Map> map) throws SQLException { throw new FBDriverNotCapableException(); } private Set clientInfoPropNames = new HashSet<>(); private static final AtomicIntegerFieldUpdater SAVEPOINT_COUNTER_UPDATE = AtomicIntegerFieldUpdater.newUpdater(FBConnection.class, "savepointCounter"); private volatile int savepointCounter; private final List savepoints = new ArrayList<>(); private int getNextSavepointCounter() { return SAVEPOINT_COUNTER_UPDATE.getAndIncrement(this); } @Override 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(); StringBuilder setSavepoint = new StringBuilder("SAVEPOINT "); getQuoteStrategy().appendQuoted(savepoint.getServerSavepointId(), setSavepoint); getGDSHelper().executeImmediate(setSavepoint.toString()); savepoints.add(savepoint); } /** * Creates a named savepoint. *

* Savepoint names need to be valid Firebird identifiers, and the maximum length is restricted to the maximum * identifier length (see {@link DatabaseMetaData#getMaxColumnNameLength()}. The implementation will take care of * quoting the savepoint name appropriately for the connection dialect. The {@code name} should be passed unquoted. *

*

* With connection dialect 1, the name is restricted to the rules for unquoted identifier names, that is, its * characters are restricted to {@code A-Za-z0-9$_} and handled case insensitive. *

*

* For dialect 2 and 3, the name is restricted to the rules for Firebird quoted identifiers (essentially any * printable character and space is valid), and the name is handled case sensitive. *

* * @param name * Savepoint name * @return Savepoint object * @throws SQLException * if a database access error occurs, this method is called while participating in a distributed * transaction, this method is called on a closed connection or this {@code Connection} object is currently * in auto-commit mode */ @Override public Savepoint setSavepoint(String name) throws SQLException { synchronized (getSynchronizationObject()) { checkValidity(); FBSavepoint savepoint = new FBSavepoint(name); setSavepoint(savepoint); return savepoint; } } @Override 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."); } StringBuilder rollbackSavepoint = new StringBuilder("ROLLBACK TO "); getQuoteStrategy().appendQuoted(fbSavepoint.getServerSavepointId(), rollbackSavepoint); getGDSHelper().executeImmediate(rollbackSavepoint.toString()); } } @Override 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."); } StringBuilder rollbackSavepoint = new StringBuilder("RELEASE SAVEPOINT "); getQuoteStrategy().appendQuoted(fbSavepoint.getServerSavepointId(), rollbackSavepoint).append(" ONLY"); getGDSHelper().executeImmediate(rollbackSavepoint.toString()); 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; } } @Override public boolean isWrapperFor(Class iface) throws SQLException { return iface != null && iface.isAssignableFrom(FBConnection.class); } @Override public T unwrap(Class iface) throws SQLException { if (!isWrapperFor(iface)) throw new SQLException("Unable to unwrap to class " + iface.getName()); return iface.cast(this); } /** * {@inheritDoc} *

* Implementation ignores calls to this method as schemas are not supported. *

*/ @Override public void setSchema(String schema) throws SQLException { // Ignore: no schema support checkValidity(); } /** * {@inheritDoc} * * @return Always {@code null} as schemas ar not supported */ @Override public String getSchema() throws SQLException { checkValidity(); return null; } /** * Check if this connection is currently involved in a transaction */ public boolean inTransaction() throws SQLException { return getGDSHelper().inTransaction(); } @Override 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()}. *

*/ @Override public NClob createNClob() throws SQLException { return (NClob) createClob(); } @Override 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).toFlatSQLException(); return mc.getGDSHelper(); } @Override public boolean isUseFirebirdAutoCommit() { DatabaseParameterBuffer dpb = getDatabaseParameterBuffer(); return dpb != null && dpb.hasArgument(USE_FIREBIRD_AUTOCOMMIT); } @Override 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"); } } @Override 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; } @Override 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; } } @Override 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(); } @Override 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); } } @Override public void abort(Executor executor) throws SQLException { // TODO Write implementation checkValidity(); throw new FBDriverNotCapableException(); } @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { SecurityManager securityManager = System.getSecurityManager(); if (securityManager != null) { SQLPermission sqlPermission = new SQLPermission(PERMISSION_SET_NETWORK_TIMEOUT); securityManager.checkPermission(sqlPermission); } if (executor == null) { throw FbExceptionBuilder .forException(JaybirdErrorCodes.jb_invalidExecutor) .toFlatSQLException(); } if (milliseconds < 0) { throw FbExceptionBuilder .forException(JaybirdErrorCodes.jb_invalidTimeout) .toFlatSQLException(); } checkValidity(); executor.execute(new SetNetworkTimeoutCommand(this, milliseconds)); } private static class SetNetworkTimeoutCommand implements Runnable { private final WeakReference connectionReference; private final int timeoutMillis; SetNetworkTimeoutCommand(FBConnection connection, int timeoutMillis) { connectionReference = new WeakReference<>(connection); this.timeoutMillis = timeoutMillis; } @Override public void run() { FBConnection connection = connectionReference.get(); if (connection != null) { try { connection.getFbDatabase().setNetworkTimeout(timeoutMillis); } catch (SQLException e) { log.error("Exception during asynchronous handling of setNetworkTimeout", e); } } } } @Override public int getNetworkTimeout() throws SQLException { return getFbDatabase().getNetworkTimeout(); } @Override public final Object getSynchronizationObject() { final FBManagedConnection managedConnection = mc; if (managedConnection != null) { return managedConnection.getSynchronizationObject(); } else { return this; } } /** * Get the quote strategy appropriate for the dialect of this connection. * * @return Quote strategy * @throws SQLException * If the connection is closed */ QuoteStrategy getQuoteStrategy() throws SQLException { return QuoteStrategy.forDialect(getGDSHelper().getDialect()); } GeneratedKeysSupport getGeneratedKeysSupport() throws SQLException { if (generatedKeysSupport == null) { generatedKeysSupport = GeneratedKeysSupportFactory .createFor(getGeneratedKeysEnabled(), (FirebirdDatabaseMetaData) getMetaData()); } return generatedKeysSupport; } private String getGeneratedKeysEnabled() { DatabaseParameterBuffer dpb = getDatabaseParameterBuffer(); return dpb != null ? dpb.getArgumentAsString(GENERATED_KEYS_ENABLED) : null; } boolean isIgnoreProcedureType() { DatabaseParameterBuffer dpb = getDatabaseParameterBuffer(); return dpb != null && dpb.hasArgument(IGNORE_PROCEDURE_TYPE); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy