org.firebirdsql.jdbc.FBConnection Maven / Gradle / Ivy
Show all versions of jaybird Show documentation
/*
* Firebird Open Source 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.ISCConstants;
import org.firebirdsql.gds.JaybirdErrorCodes;
import org.firebirdsql.gds.TransactionParameterBuffer;
import org.firebirdsql.gds.impl.GDSHelper;
import org.firebirdsql.gds.ng.FbDatabase;
import org.firebirdsql.gds.ng.FbExceptionBuilder;
import org.firebirdsql.gds.ng.IConnectionProperties;
import org.firebirdsql.gds.ng.LockCloseable;
import org.firebirdsql.jaybird.props.DatabaseConnectionProperties;
import org.firebirdsql.jaybird.props.PropertyConstants;
import org.firebirdsql.jaybird.xca.FBLocalTransaction;
import org.firebirdsql.jaybird.xca.FBManagedConnection;
import org.firebirdsql.jdbc.escape.FBEscapedParser;
import org.firebirdsql.logging.Logger;
import org.firebirdsql.logging.LoggerFactory;
import org.firebirdsql.util.SQLExceptionChainBuilder;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import static org.firebirdsql.jdbc.SQLStateConstants.SQL_STATE_CONNECTION_CLOSED;
/**
* The class {@code 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 {
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 volatile 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;
private StoredProcedureMetaData storedProcedureMetaData;
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);
IConnectionProperties props = mc.getConnectionRequestInfo().asIConnectionProperties();
resultSetHoldability = props.isDefaultResultSetHoldable()
? ResultSet.HOLD_CURSORS_OVER_COMMIT
: ResultSet.CLOSE_CURSORS_AT_COMMIT;
}
/**
* @deprecated will be removed in Jaybird 6 without replacement
*/
@Deprecated
public FBObjectListener.StatementListener getStatementListener() {
return txCoordinator;
}
@Override
public int getHoldability() throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
return resultSetHoldability;
}
}
@Override
public void setHoldability(int holdability) throws SQLException {
try (LockCloseable ignored = withLock()) {
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 {@code 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.warnf("Specified statement was not created by this connection: %s", 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) {
try (LockCloseable ignored = withLock()) {
//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() {
try (LockCloseable ignored = withLock()) {
return mc;
}
}
@Override
public FbDatabase getFbDatabase() throws SQLException {
return getGDSHelper().getCurrentDatabase();
}
/**
* Get database connection properties for this connection.
*
* @return immutable instance of {@link DatabaseConnectionProperties}.
*/
public DatabaseConnectionProperties connectionProperties() {
// TODO Do we need mutability?
// TODO Obtain elsewhere than from getConnectionRequestInfo()
return mc != null ? mc.getConnectionRequestInfo().asIConnectionProperties().asImmutable() : null;
}
@Deprecated
@Override
public void setTransactionParameters(int isolationLevel, int[] parameters) throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
TransactionParameterBuffer tpbParams = createTransactionParameterBuffer();
for (int parameter : parameters) {
tpbParams.addArgument(parameter);
}
setTransactionParameters(isolationLevel, tpbParams);
}
}
@Override
public TransactionParameterBuffer getTransactionParameters(int isolationLevel) throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
return mc.getTransactionParameters(isolationLevel);
}
}
@Override
public TransactionParameterBuffer createTransactionParameterBuffer() throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
return getFbDatabase().createTransactionParameterBuffer();
}
}
@Override
public void setTransactionParameters(int isolationLevel, TransactionParameterBuffer tpb) throws SQLException {
try (LockCloseable ignored = withLock()) {
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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
if (getLocalTransaction().inTransaction()) {
// TODO More specific exception, jaybird error code
throw new FBSQLException("Cannot set transaction parameters when transaction is already started.");
}
mc.setTransactionParameters(tpb);
}
}
@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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
return createBlob(FBBlob.createConfig(ISCConstants.BLOB_SUB_TYPE_BINARY, connectionProperties(),
getFbDatabase().getDatatypeCoder()));
}
}
private FBBlob createBlob(FBBlob.Config blobConfig) throws SQLException {
return new FBBlob(getGDSHelper(), txCoordinator, blobConfig);
}
@Override
public Clob createClob() throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
FBBlob blob = createBlob(FBBlob.createConfig(ISCConstants.BLOB_SUB_TYPE_TEXT, connectionProperties(),
getFbDatabase().getDatatypeCoder()));
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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
return FBEscapedParser.toNativeSql(sql);
}
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
try (LockCloseable ignored = withLock()) {
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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
txCoordinator.setTransactionCoordinator(managedConnection, autoCommit);
}
}
public void setManagedEnvironment(boolean managedConnection) throws SQLException {
try (LockCloseable ignored = withLock()) {
setTransactionCoordinator(managedConnection, true);
}
}
@Override
public boolean getAutoCommit() throws SQLException {
try (LockCloseable ignored = withLock()) {
if (isClosed()) {
throw new FBSQLException("You cannot getAutoCommit on an unassociated closed connection.");
}
return txCoordinator.getAutoCommit();
}
}
@Override
public void commit() throws SQLException {
try (LockCloseable ignored = withLock()) {
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 {
try (LockCloseable ignored = withLock()) {
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 {
if (isClosed()) return;
if (log.isTraceEnabled()) {
log.trace("Connection closed requested at", new RuntimeException("Connection close logging"));
}
SQLExceptionChainBuilder chainBuilder = new SQLExceptionChainBuilder<>();
try (LockCloseable ignored = withLock()) {
try {
if (metaData != null) metaData.close();
freeStatements();
} catch (SQLException e) {
chainBuilder.append(e);
} finally {
metaData = null;
closeMc(chainBuilder);
}
}
if (chainBuilder.hasException()) {
throw chainBuilder.getException();
}
}
private void closeMc(SQLExceptionChainBuilder chainBuilder) {
FBManagedConnection mc = this.mc;
if (mc == null) return;
// 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 (!SQL_STATE_CONNECTION_CLOSED.equals(e.getSQLState())) {
chainBuilder.append(e);
}
}
}
}
mc.close(this);
}
@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);
}
if (isLockedByCurrentThread()) {
// Trying to async check validity will not work (this shouldn't normally happen, except maybe when Jaybird
// internals call isValid(int) or user code locks on result of withLock())
return isValidImpl(timeout);
}
return isValidAsync(timeout);
}
private boolean isValidAsync(int timeout) {
Future isValidFuture = ForkJoinPool.commonPool().submit(() -> isValidImpl(timeout));
try {
return timeout != 0 ? isValidFuture.get(timeout, TimeUnit.SECONDS) : isValidFuture.get();
} catch (ExecutionException e) {
log.debug("isValidImpl produced an exception", e);
return false;
} catch (InterruptedException e) {
isValidFuture.cancel(true);
// restore interrupted state
Thread.currentThread().interrupt();
return false;
} catch (TimeoutException e) {
isValidFuture.cancel(true);
return false;
}
}
private boolean isValidImpl(int timeout) {
try (LockCloseable ignored = withLock()) {
if (isClosed()) {
return false;
}
int originalNetworkTimeout = -1;
boolean networkTimeoutChanged = false;
try {
FbDatabase db = getFbDatabase();
if (timeout != 0) {
try {
originalNetworkTimeout = db.getNetworkTimeout();
db.setNetworkTimeout((int) TimeUnit.SECONDS.toMillis(timeout));
networkTimeoutChanged = true;
} catch (SQLFeatureNotSupportedException ignored2) {
// Implementation doesn't support network timeout
}
}
db.getDatabaseInfo(new byte[] { ISCConstants.isc_info_ods_version, ISCConstants.isc_info_end }, 10);
return true;
} catch (SQLException ex) {
log.debug("Exception while checking connection validity", ex);
return false;
} finally {
if (networkTimeoutChanged) {
try {
getFbDatabase().setNetworkTimeout(originalNetworkTimeout);
} catch (SQLException e) {
log.debug("Exception while resetting connection network timeout", e);
// We're interpreting this as an indication the connection is no longer valid
//noinspection ReturnInsideFinallyBlock
return false;
}
}
}
}
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
if (metaData == null)
metaData = new FBDatabaseMetaData(this);
return metaData;
}
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
if (getLocalTransaction().inTransaction() && !mc.isManagedEnvironment()) {
// TODO More specific exception, jaybird error code
throw new FBSQLException("Calling setReadOnly(boolean) method " +
"is not allowed when transaction is already started.");
}
mc.setReadOnly(readOnly);
}
}
@Override
public boolean isReadOnly() throws SQLException {
try (LockCloseable ignored = withLock()) {
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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
if (!getAutoCommit() && !mc.isManagedEnvironment()) {
txCoordinator.commit();
}
mc.setTransactionIsolation(level);
}
}
@SuppressWarnings("MagicConstant")
@Override
public int getTransactionIsolation() throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
return mc.getTransactionIsolation();
}
}
@Override
public SQLWarning getWarnings() throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
return firstWarning;
}
}
@Override
public void clearWarnings() throws SQLException {
try (LockCloseable ignored = withLock()) {
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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
if (resultSetHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT &&
resultSetType == ResultSet.TYPE_FORWARD_ONLY) {
addWarning(FbExceptionBuilder
.forWarning(JaybirdErrorCodes.jb_resultSetTypeUpgradeReasonHoldability)
.toSQLException(SQLWarning.class));
resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE;
}
if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE) {
addWarning(FbExceptionBuilder
.forWarning(JaybirdErrorCodes.jb_resultSetTypeDowngradeReasonScrollSensitive)
.toSQLException(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 compatible.
*/
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) {
// TODO jaybird error code
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);
}
/**
* @deprecated will be removed in Jaybird 6, use {@link #prepareStatement(String, int, int, int, boolean, boolean)}
*/
@Deprecated
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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
GeneratedKeysSupport.Query query = getGeneratedKeysSupport()
.buildQuery(sql, autoGeneratedKeys);
return prepareStatement(query);
}
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
try (LockCloseable ignored = withLock()) {
checkValidity();
GeneratedKeysSupport.Query query = getGeneratedKeysSupport()
.buildQuery(sql, columnIndexes);
return prepareStatement(query);
}
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
try (LockCloseable ignored = withLock()) {
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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
if (resultSetHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT
&& resultSetType == ResultSet.TYPE_FORWARD_ONLY) {
addWarning(FbExceptionBuilder
.forWarning(JaybirdErrorCodes.jb_resultSetTypeUpgradeReasonHoldability)
.toSQLException(SQLWarning.class));
resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE;
} else if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE) {
addWarning(FbExceptionBuilder
.forWarning(JaybirdErrorCodes.jb_resultSetTypeDowngradeReasonScrollSensitive)
.toSQLException(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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
if (resultSetHoldability == ResultSet.HOLD_CURSORS_OVER_COMMIT
&& resultSetType == ResultSet.TYPE_FORWARD_ONLY) {
addWarning(FbExceptionBuilder
.forWarning(JaybirdErrorCodes.jb_resultSetTypeUpgradeReasonHoldability)
.toSQLException(SQLWarning.class));
resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE;
} else if (resultSetType == ResultSet.TYPE_SCROLL_SENSITIVE) {
addWarning(FbExceptionBuilder
.forWarning(JaybirdErrorCodes.jb_resultSetTypeDowngradeReasonScrollSensitive)
.toSQLException(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 final 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 {
try (LockCloseable ignored = withLock()) {
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 {
try (LockCloseable ignored = withLock()) {
checkValidity();
FBSavepoint savepoint = new FBSavepoint(name);
setSavepoint(savepoint);
return savepoint;
}
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
try (LockCloseable ignored = withLock()) {
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 {
try (LockCloseable ignored = withLock()) {
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() {
try (LockCloseable ignored = withLock()) {
for (FBSavepoint savepoint : savepoints) {
savepoint.invalidate();
}
savepoints.clear();
}
}
/**
* Returns a FBLocalTransaction instance that enables a component to
* demarcate resource manager local transactions on this connection.
*/
public FBLocalTransaction getLocalTransaction() {
try (LockCloseable ignored = withLock()) {
if (localTransaction == null) {
localTransaction = mc.getLocalTransaction();
}
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.
*
* @deprecated will be removed in Jaybird 6 without replacement
*/
@Deprecated
public boolean inTransaction() throws SQLException {
return getGDSHelper().inTransaction();
}
@Deprecated
@Override
public String getIscEncoding() throws SQLException {
return getGDSHelper().getIscEncoding();
}
public void addWarning(SQLWarning warning) {
try (LockCloseable ignored = withLock()) {
// 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).toSQLException();
return mc.getGDSHelper();
}
@Override
public boolean isUseFirebirdAutoCommit() {
DatabaseConnectionProperties props = connectionProperties();
return props != null && props.isUseFirebirdAutocommit();
}
/**
* Checks if client info is supported.
*
* @throws SQLException
* If the client info is not supported, or if there is no database connection.
* @deprecated will be removed in Jaybird 6 without replacement
*/
@Deprecated
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 {
return sessionContext;
}
}
}
@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);
try (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).toSQLException();
}
if (milliseconds < 0) {
throw FbExceptionBuilder.forException(JaybirdErrorCodes.jb_invalidTimeout).toSQLException();
}
try (LockCloseable ignored = withLock()) {
checkValidity();
getFbDatabase().setNetworkTimeout(milliseconds);
}
}
@Override
public int getNetworkTimeout() throws SQLException {
return getFbDatabase().getNetworkTimeout();
}
/**
* @see org.firebirdsql.gds.ng.FbAttachment#withLock()
*/
protected final LockCloseable withLock() {
FBManagedConnection mc = this.mc;
if (mc != null) {
return mc.withLock();
}
return LockCloseable.NO_OP;
}
protected final boolean isLockedByCurrentThread() {
FBManagedConnection mc = this.mc;
if (mc != null) {
return mc.isLockedByCurrentThread();
}
return false;
}
/**
* 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() {
DatabaseConnectionProperties props = connectionProperties();
return props != null ? props.getGeneratedKeysEnabled() : null;
}
boolean isIgnoreProcedureType() {
DatabaseConnectionProperties props = connectionProperties();
return props != null && props.isIgnoreProcedureType();
}
/**
* Checks (case-insensitive) value of the {@code scrollableCursor} connection property.
*
* Important: this does not verify actual server support for the requested feature, just the value of
* the connection property.
*
*
* @param scrollableCursor
* Value to check (case-insensitive)
* @return {@code true} if the {@code scrollableCursor} connection property matches the specified value,
* {@code false} otherwise.
*/
boolean isScrollableCursor(String scrollableCursor) {
DatabaseConnectionProperties props = connectionProperties();
return props != null && scrollableCursor != null
&& scrollableCursor.equalsIgnoreCase(props.getScrollableCursor());
}
boolean isUseServerBatch() {
DatabaseConnectionProperties props = connectionProperties();
return props != null && props.isUseServerBatch();
}
int getServerBatchBufferSize() {
DatabaseConnectionProperties props = connectionProperties();
return props != null ? props.getServerBatchBufferSize() : PropertyConstants.DEFAULT_SERVER_BATCH_BUFFER_SIZE;
}
boolean isExtendedMetadata() {
DatabaseConnectionProperties props = connectionProperties();
return props != null && props.isExtendedMetadata();
}
}