org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// Vikram Bhatia - bug fix for releasing temporary LOBs after conversion
// 02/08/2012-2.4 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 07/13/2012-2.5 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 08/24/2012-2.5 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 11/05/2012-2.5 Guy Pelletier
// - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls
// 01/08/2012-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support
// 02/19/2015 - Rick Curtis
// - 458877 : Add national character support
// 13/01/2022-4.0.0 Tomas Kraus
// - 1391: JSON support in JPA
package org.eclipse.persistence.internal.databaseaccess;
// javase imports
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Types;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.helper.LOBValueWriter;
import org.eclipse.persistence.internal.helper.NonSynchronizedVector;
import org.eclipse.persistence.internal.helper.ThreadCursoredList;
import org.eclipse.persistence.internal.localization.ExceptionLocalization;
import org.eclipse.persistence.internal.localization.ToStringLocalization;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.ArrayRecord;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.mappings.structures.ObjectRelationalDataTypeDescriptor;
import org.eclipse.persistence.queries.Call;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.sessions.DatabaseLogin;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.Login;
import org.eclipse.persistence.sessions.SessionProfiler;
import static org.eclipse.persistence.internal.helper.DatabaseField.NULL_SQL_TYPE;
/**
* INTERNAL:
* DatabaseAccessor is private to EclipseLink. It encapsulates low level database operations (such as executing
* SQL and reading data by row). Database accessor defines a protocol by which EclipseLink may invoke these
* operations.
* DatabaseAccessor also defines a single reference through which all configuration dependent behavior may
* be invoked.
*
* DabaseAccessor implements the following behavior.
* - Connect and disconnect from the database.
*
- Execute SQL statements on the database, returning results.
*
- Handle auto-commit and transactions.
*
* DatabaseAccessor dispatches the following protocols to its platform reference.
* - Provision of database platform specific type names.
*
* DatabaseAccessor dispatches the following protocols to the schema object.
* - Creation and deletion of schema objects.
*
* @see DatabasePlatform
* @since TOPLink/Java 1.0
*/
public class DatabaseAccessor extends DatasourceAccessor {
/** PERF: Backdoor to disabling dynamic statements. Reverts to old prepared statement usage if set. */
public static boolean shouldUseDynamicStatements = true;
/** Stores statement handles for common used prepared statements. */
protected Map statementCache;
/** Cache of the connection's java.sql.DatabaseMetaData */
protected DatabaseMetaData metaData;
/** This attribute will be used to store the currently active Batch Mechanism */
protected BatchWritingMechanism activeBatchWritingMechanism;
/**
* These two attributes store the available BatchWritingMechanisms. We sacrifice a little space to
* prevent the work involved in recreating these objects each time a different type of SQL statement is
* executed. Depending on user behavior we may want to review this.
*/
protected DynamicSQLBatchWritingMechanism dynamicSQLMechanism;
protected ParameterizedSQLBatchWritingMechanism parameterizedMechanism;
// Bug 2804663 - Each DatabaseAccessor holds on to its own LOBValueWriter instance
protected LOBValueWriter lobWriter;
/** PERF: Cache the statement object for dynamic SQL execution. */
protected Statement dynamicStatement;
protected boolean isDynamicStatementInUse;
public DatabaseAccessor() {
super();
this.lobWriter = null;
this.isDynamicStatementInUse = false;
}
/**
* Create a database accessor with the given connection.
*/
public DatabaseAccessor(Object connection) {
this();
this.datasourceConnection = connection;
}
/**
* Lazy init the dynamic SQL mechanism.
*/
protected DynamicSQLBatchWritingMechanism getDynamicSQLMechanism() {
if (this.dynamicSQLMechanism == null) {
this.dynamicSQLMechanism = new DynamicSQLBatchWritingMechanism(this);
}
return this.dynamicSQLMechanism;
}
/**
* Lazy init the parameterized SQL mechanism.
*/
protected ParameterizedSQLBatchWritingMechanism getParameterizedMechanism() {
if (this.parameterizedMechanism == null) {
this.parameterizedMechanism = new ParameterizedSQLBatchWritingMechanism(this);
}
return this.parameterizedMechanism;
}
/**
* Execute any deferred select calls stored in the LOBValueWriter instance.
* This method will typically be called by the CallQueryMechanism object.
* Bug 2804663.
*
* @see org.eclipse.persistence.internal.helper.LOBValueWriter
* @see org.eclipse.persistence.internal.queries.CallQueryMechanism#insertObject()
*/
@Override
public void flushSelectCalls(AbstractSession session) {
if (lobWriter != null) {
lobWriter.buildAndExecuteSelectCalls(session);
}
}
/**
* Return the LOBValueWriter instance. Lazily initialize the instance.
* Bug 2804663.
*
* @see org.eclipse.persistence.internal.helper.LOBValueWriter
*/
public LOBValueWriter getLOBWriter() {
if (lobWriter == null) {
lobWriter = new LOBValueWriter(this);
}
return lobWriter;
}
/**
* Allocate a statement for dynamic SQL execution.
* Either return the cached dynamic statement, or a new statement.
* This statement must be released after execution.
*/
public synchronized Statement allocateDynamicStatement(Connection connection) throws SQLException {
if (dynamicStatement == null) {
dynamicStatement = connection.createStatement();
}
if (isDynamicStatementInUse()) {
return connection.createStatement();
}
setIsDynamicStatementInUse(true);
return dynamicStatement;
}
/**
* Return the cached statement for dynamic SQL execution is in use.
* Used to handle concurrency for the dynamic statement, this
* method must only be called from within a synchronized method/block.
*/
public boolean isDynamicStatementInUse() {
return isDynamicStatementInUse;
}
/**
* Set the platform.
* This should be set to the session's platform, not the connections
* which may not be configured correctly.
*/
@Override
public void setDatasourcePlatform(DatasourcePlatform platform) {
super.setDatasourcePlatform(platform);
// lobWriter may have been left from a different platform type.
this.lobWriter = null;
}
/**
* Set if the cached statement for dynamic SQL execution is in use.
* Used to handle concurrency for the dynamic statement.
*/
public synchronized void setIsDynamicStatementInUse(boolean isDynamicStatementInUse) {
this.isDynamicStatementInUse = isDynamicStatementInUse;
}
/**
* Begin a transaction on the database. This means toggling the auto-commit option.
*/
@Override
public void basicBeginTransaction(AbstractSession session) throws DatabaseException {
try {
if (getPlatform().supportsAutoCommit()) {
getConnection().setAutoCommit(false);
} else {
getPlatform().beginTransaction(this);
}
} catch (SQLException exception) {
DatabaseException commException = processExceptionForCommError(session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
}
/**
* If logging is turned on and the JDBC implementation supports meta data then display connection info.
*/
@Override
protected void buildConnectLog(AbstractSession session) {
try {
// Log connection information.
if (session.shouldLog(SessionLog.CONFIG, SessionLog.CONNECTION)) {// Avoid printing if no logging required.
DatabaseMetaData metaData = getConnectionMetaData();
Object[] args = { metaData.getURL(), metaData.getUserName(), metaData.getDatabaseProductName(), metaData.getDatabaseProductVersion(), metaData.getDriverName(), metaData.getDriverVersion(), Helper.cr() + "\t" };
session.log(SessionLog.CONFIG, SessionLog.CONNECTION, "connected_user_database_driver", args, this);
}
} catch (Exception exception) {
// Some databases do not support metadata, ignore exception.
session.warning("JDBC_driver_does_not_support_meta_data", SessionLog.CONNECTION);
}
}
/**
* Build a row from the output parameters of a sp call.
*/
public AbstractRecord buildOutputRow(CallableStatement statement, DatabaseCall call, AbstractSession session) throws DatabaseException {
try {
return call.buildOutputRow(statement, this, session);
} catch (SQLException exception) {
DatabaseException commException = processExceptionForCommError(session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
}
/**
* Return the field sorted in the correct order corresponding to the result set.
* This is used for cursored selects where custom sql was provided.
* If the fields passed in are null, this means that the field are not known and should be
* built from the column names. This case occurs for DataReadQuery's.
*/
public Vector buildSortedFields(Vector fields, ResultSet resultSet, AbstractSession session) throws DatabaseException {
Vector sortedFields;
try {
Vector columnNames = getColumnNames(resultSet, session);
if (fields == null) {// Means fields not known.
sortedFields = columnNames;
} else {
sortedFields = sortFields(fields, columnNames);
}
} catch (SQLException exception) {
DatabaseException commException = processExceptionForCommError(session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
return sortedFields;
}
/**
* Connect to the database.
* Exceptions are caught and re-thrown as EclipseLink exceptions.
* Must set the transaction isolation.
*/
@Override
protected void connectInternal(Login login, AbstractSession session) throws DatabaseException {
super.connectInternal(login, session);
checkTransactionIsolation(session);
try {
session.getPlatform().initializeConnectionData(getConnection());
} catch (java.sql.SQLException sqlEx) {
DatabaseException commException = processExceptionForCommError(session, sqlEx, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(sqlEx, this, session, false);
}
}
/**
* Check to see if the transaction isolation needs to
* be set for the newly created connection. This must
* be done outside of a transaction.
* Exceptions are caught and re-thrown as EclipseLink exceptions.
*/
protected void checkTransactionIsolation(AbstractSession session) throws DatabaseException {
if ((!this.isInTransaction) && (this.login != null) && (((DatabaseLogin)this.login).getTransactionIsolation() != -1)) {
try {
getConnection().setTransactionIsolation(((DatabaseLogin)this.login).getTransactionIsolation());
} catch (java.sql.SQLException sqlEx) {
DatabaseException commException = processExceptionForCommError(session, sqlEx, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(sqlEx, this, session, false);
}
}
}
/**
* Flush the statement cache.
* Each statement must first be closed.
*/
public void clearStatementCache(AbstractSession session) {
if (hasStatementCache()) {
for (Statement statement : getStatementCache().values()) {
try {
statement.close();
} catch (SQLException exception) {
// an exception can be raised if
// a statement is closed twice.
}
}
this.statementCache = null;
}
// Close cached dynamic statement.
if (this.dynamicStatement != null) {
try {
this.dynamicStatement.close();
} catch (SQLException exception) {
// an exception can be raised if
// a statement is closed twice.
}
this.dynamicStatement = null;
this.setIsDynamicStatementInUse(false);
}
}
/**
* Clone the accessor.
*/
@Override
public Object clone() {
DatabaseAccessor accessor = (DatabaseAccessor)super.clone();
accessor.dynamicSQLMechanism = null;
if (this.activeBatchWritingMechanism != null) {
accessor.activeBatchWritingMechanism = this.activeBatchWritingMechanism.clone();
}
accessor.parameterizedMechanism = null;
accessor.statementCache = null;
return accessor;
}
/**
* Close the result set of the cursored stream.
*/
public void closeCursor(ResultSet resultSet, AbstractSession session) throws DatabaseException {
try {
resultSet.close();
} catch (SQLException exception) {
DatabaseException commException = processExceptionForCommError(session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
}
/**
* INTERNAL:
* Closes a PreparedStatement (which is supposed to close it's current resultSet).
* Factored out to simplify coding and handle exceptions.
*/
public void closeStatement(Statement statement, AbstractSession session, DatabaseCall call) throws SQLException {
if (statement == null) {
decrementCallCount();
return;
}
DatabaseQuery query = ((call == null)? null : call.getQuery());
try {
session.startOperationProfile(SessionProfiler.StatementExecute, query, SessionProfiler.ALL);
statement.close();
} finally {
session.endOperationProfile(SessionProfiler.StatementExecute, query, SessionProfiler.ALL);
decrementCallCount();
// If this is the cached dynamic statement, release it.
if (statement == this.dynamicStatement) {
this.dynamicStatement = null;
// The dynamic statement is cached and only closed on disconnect.
setIsDynamicStatementInUse(false);
}
}
}
/**
* Commit a transaction on the database. First flush any batched statements.
*/
@Override
public void commitTransaction(AbstractSession session) throws DatabaseException {
this.writesCompleted(session);
super.commitTransaction(session);
}
/**
* Commit a transaction on the database. This means toggling the auto-commit option.
*/
@Override
public void basicCommitTransaction(AbstractSession session) throws DatabaseException {
try {
if (getPlatform().supportsAutoCommit()) {
getConnection().commit();
getConnection().setAutoCommit(true);
} else {
getPlatform().commitTransaction(this);
}
} catch (SQLException exception) {
DatabaseException commException = processExceptionForCommError(session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
}
/**
* Advance the result set and return a Record populated
* with values from the next valid row in the result set. Intended solely
* for cursored stream support.
*/
public AbstractRecord cursorRetrieveNextRow(Vector fields, ResultSet resultSet, AbstractSession session) throws DatabaseException {
try {
if (resultSet.next()) {
return fetchRow(fields, resultSet, resultSet.getMetaData(), session);
} else {
return null;
}
} catch (SQLException exception) {
DatabaseException commException = processExceptionForCommError(session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
}
/**
* Advance the result set and return a DatabaseRow populated
* with values from the next valid row in the result set. Intended solely
* for scrollable cursor support.
*/
public AbstractRecord cursorRetrievePreviousRow(Vector fields, ResultSet resultSet, AbstractSession session) throws DatabaseException {
try {
if (resultSet.previous()) {
return fetchRow(fields, resultSet, resultSet.getMetaData(), session);
} else {
return null;
}
} catch (SQLException exception) {
DatabaseException commException = processExceptionForCommError(session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
}
/**
* Close the connection.
*/
@Override
public void closeDatasourceConnection() throws DatabaseException {
try {
getConnection().close();
} catch (SQLException exception) {
throw DatabaseException.sqlException(exception, this, null, false);
}
}
/**
* Disconnect from the datasource.
* Added for bug 3046465 to ensure the statement cache is cleared.
*/
@Override
public void disconnect(AbstractSession session) throws DatabaseException {
clearStatementCache(session);
super.disconnect(session);
}
/**
* Close the accessor's connection.
* This is used only for external connection pooling
* when it is intended for the connection to be reconnected in the future.
*/
@Override
public void closeConnection() {
// Unfortunately do not have the session to pass, fortunately it is not used.
clearStatementCache(null);
super.closeConnection();
}
/**
* Execute the EclipseLink dynamically batched/concatenated statement.
*/
protected void executeBatchedStatement(PreparedStatement statement, AbstractSession session) throws DatabaseException {
try {
executeDirectNoSelect(statement, null, session);
} catch (RuntimeException exception) {
try {// Ensure that the statement is closed, but still ensure that the real exception is thrown.
closeStatement(statement, session, null);
} catch (SQLException closeException) {
}
throw exception;
}
// This is in a separate try block to ensure that the real exception is not masked by the close exception.
try {
closeStatement(statement, session, null);
} catch (SQLException exception) {
//With an external connection pool the connection may be null after this call, if it is we will
//be unable to determine if it is a connection based exception so treat it as if it wasn't.
DatabaseException commException = processExceptionForCommError(session, exception, null);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
}
/**
* Execute the call.
* The execution can differ slightly depending on the type of call.
* The call may be parameterized where the arguments are in the translation row.
* The row will be empty if there are no parameters.
* @return depending of the type either the row count, row or vector of rows.
*/
@Override
public Object executeCall(Call call, AbstractRecord translationRow, AbstractSession session) throws DatabaseException {
// Keep complete implementation.
return basicExecuteCall(call, translationRow, session, true);
}
/**
* Execute the call.
* The execution can differ slightly depending on the type of call.
* The call may be parameterized where the arguments are in the translation row.
* The row will be empty if there are no parameters.
* @return depending of the type either the row count, row or vector of rows.
*/
@Override
public Object basicExecuteCall(Call call, AbstractRecord translationRow, AbstractSession session) throws DatabaseException {
return basicExecuteCall(call, translationRow, session, true);
}
/**
* Execute the call.
* The execution can differ slightly depending on the type of call.
* The call may be parameterized where the arguments are in the translation row.
* The row will be empty if there are no parameters.
* @return depending of the type either the row count, row or vector of rows.
*/
public Object basicExecuteCall(Call call, AbstractRecord translationRow, AbstractSession session, boolean batch) throws DatabaseException {
Statement statement = null;
Object result = null;
DatabaseCall dbCall = null;
ResultSet resultSet = null;// only used if this is a read query
try {
dbCall = (DatabaseCall)call;
} catch (ClassCastException e) {
throw QueryException.invalidDatabaseCall(call);
}
// If the login is null, then this accessor has never been connected.
if (this.login == null) {
throw DatabaseException.databaseAccessorNotConnected();
}
if (batch && isInBatchWritingMode(session)) {
// if there is nothing returned and we are not using optimistic locking then batch
//if it is a StoredProcedure with in/out or out parameters then do not batch
//logic may be weird but we must not batch if we are not using JDBC batchwriting and we have parameters
// we may want to refactor this some day
if (dbCall.isBatchExecutionSupported()) {
// this will handle executing batched statements, or switching mechanisms if required
getActiveBatchWritingMechanism(session).appendCall(session, dbCall);
//bug 4241441: passing 1 back to avoid optimistic lock exceptions since there
// is no way to know if it succeeded on the DB at this point.
return 1;
} else {
getActiveBatchWritingMechanism(session).executeBatchedStatements(session);
}
}
try {
incrementCallCount(session);
if (session.shouldLog(SessionLog.FINE, SessionLog.SQL)) {// Avoid printing if no logging required.
session.log(SessionLog.FINE, SessionLog.SQL, dbCall.getLogString(this), null, this, false);
}
session.startOperationProfile(SessionProfiler.SqlPrepare, dbCall.getQuery(), SessionProfiler.ALL);
try {
statement = dbCall.prepareStatement(this, translationRow, session);
} finally {
session.endOperationProfile(SessionProfiler.SqlPrepare, dbCall.getQuery(), SessionProfiler.ALL);
}
// effectively this means that someone is executing an update type query.
if (dbCall.isExecuteUpdate()) {
dbCall.setExecuteReturnValue(execute(dbCall, statement, session));
dbCall.setStatement(statement);
this.possibleFailure = false;
return dbCall;
} else if (dbCall.isNothingReturned()) {
result = executeNoSelect(dbCall, statement, session);
this.writeStatementsCount++;
if (dbCall.isLOBLocatorNeeded()) {
// add original (insert or update) call to the LOB locator
// Bug 2804663 - LOBValueWriter is no longer a singleton
getLOBWriter().addCall(dbCall);
}
if(dbCall.shouldReturnGeneratedKeys()) {
resultSet = statement.getGeneratedKeys();
dbCall.setStatement(statement);
dbCall.setGeneratedKeys(resultSet);
this.possibleFailure = false;
return dbCall;
}
} else if ((!dbCall.getReturnsResultSet() || (dbCall.getReturnsResultSet() && dbCall.shouldBuildOutputRow()))) {
result = session.getPlatform().executeStoredProcedure(dbCall, (PreparedStatement)statement, this, session);
this.storedProcedureStatementsCount++;
} else {
resultSet = executeSelect(dbCall, statement, session);
this.readStatementsCount++;
if (!dbCall.shouldIgnoreFirstRowSetting() && dbCall.getFirstResult() != 0) {
resultSet.absolute(dbCall.getFirstResult());
}
dbCall.matchFieldOrder(resultSet, this, session);
if (dbCall.isCursorReturned()) {
dbCall.setStatement(statement);
dbCall.setResult(resultSet);
this.possibleFailure = false;
return dbCall;
}
result = processResultSet(resultSet, dbCall, statement, session);
}
if (result instanceof ThreadCursoredList) {
this.possibleFailure = false;
return result;
}
// Log any warnings on finest.
if (session.shouldLog(SessionLog.FINEST, SessionLog.SQL)) {// Avoid printing if no logging required.
SQLWarning warning = statement.getWarnings();
while (warning != null) {
String message = warning.getMessage() + ":" + warning.getSQLState() + " - " + warning.getCause();
// 325605: This log will not be tracked by QuerySQLTracker
session.log(SessionLog.FINEST, SessionLog.SQL, message, null, this, false);
warning = warning.getNextWarning();
}
}
} catch (SQLException exception) {
//If this is a connection from an external pool then closeStatement will close the connection.
//we must test the connection before that happens.
RuntimeException exceptionToThrow = processExceptionForCommError(session, exception, dbCall);
try {// Ensure that the statement is closed, but still ensure that the real exception is thrown.
closeStatement(statement, session, dbCall);
} catch (Exception closeException) {
}
if (exceptionToThrow == null){
//not a comm failure :
throw DatabaseException.sqlException(exception, dbCall, this, session, false);
}
throw exceptionToThrow;
} catch (RuntimeException exception) {
try {// Ensure that the statement is closed, but still ensure that the real exception is thrown.
closeStatement(statement, session, dbCall);
} catch (Exception closeException) {
}
if (exception instanceof DatabaseException) {
((DatabaseException)exception).setCall(dbCall);
if(((DatabaseException)exception).getAccessor() == null) {
((DatabaseException)exception).setAccessor(this);
}
}
throw exception;
}
// This is in a separate try block to ensure that the real exception is not masked by the close exception.
try {
// Allow for caching of statement, forced closes are not cache as they failed execution so are most likely bad.
releaseStatement(statement, dbCall.getSQLString(), dbCall, session);
} catch (SQLException exception) {
//With an external connection pool the connection may be null after this call, if it is we will
//be unable to determine if it is a connection based exception so treat it as if it wasn't.
DatabaseException commException = processExceptionForCommError(session, exception, null);
if (commException != null) {
throw commException;
}
throw DatabaseException.sqlException(exception, this, session, false);
}
this.possibleFailure = false;
return result;
}
/**
* Fetch all the rows from the result set.
*/
public Object processResultSet(ResultSet resultSet, DatabaseCall call, Statement statement, AbstractSession session) throws SQLException {
Object result = null;
ResultSetMetaData metaData = resultSet.getMetaData();
// If there are no columns (and only an update count) throw an exception.
if (metaData.getColumnCount() == 0 && statement.getUpdateCount() > -1) {
resultSet.close();
throw new IllegalStateException(ExceptionLocalization.buildMessage("jpa21_invalid_call_with_no_result_sets_returned"));
}
session.startOperationProfile(SessionProfiler.RowFetch, call.getQuery(), SessionProfiler.ALL);
try {
if (call.isOneRowReturned()) {
if (resultSet.next()) {
if (call.isLOBLocatorNeeded()) {
//if Oracle BLOB/CLOB field is being written, and the thin driver is used, the driver 4k
//limit bug prevent the call from directly writing to the table if the LOB value size exceeds 4k.
//Instead, a LOB locator is retrieved and value is then piped into the table through the locator.
// Bug 2804663 - LOBValueWriter is no longer a singleton
getLOBWriter().fetchLocatorAndWriteValue(call, resultSet);
} else {
result = fetchRow(call.getFields(), call.getFieldsArray(), resultSet, metaData, session);
}
if (resultSet.next()) {
// Raise more rows event, some apps may interpret as error or warning.
if (session.hasEventManager()) {
session.getEventManager().moreRowsDetected(call);
}
}
}
} else {
boolean hasMultipleResultsSets = call.hasMultipleResultSets();
Vector results = null;
boolean hasMoreResultsSets = true;
while (hasMoreResultsSets) {
boolean hasNext = resultSet.next();
// PERF: Optimize out simple empty case.
if (hasNext) {
if (session.isConcurrent()) {
// If using threading return the cursored list,
// do not close the result or statement as the rows are being fetched by the thread.
return buildThreadCursoredResult(call, resultSet, statement, metaData, session);
} else {
results = new Vector<>(16);
while (hasNext) {
results.add(fetchRow(call.getFields(), call.getFieldsArray(), resultSet, metaData, session));
hasNext = resultSet.next();
}
}
} else {
results = new Vector<>(0);
}
if (result == null) {
if (call.returnMultipleResultSetCollections()) {
result = new Vector();
((List) result).add(results);
} else {
result = results;
}
} else {
if (call.returnMultipleResultSetCollections()) {
((List)result).add(results);
} else {
((List)result).addAll(results);
}
}
if (hasMultipleResultsSets) {
hasMoreResultsSets = statement.getMoreResults();
if (hasMoreResultsSets) {
resultSet = statement.getResultSet();
metaData = resultSet.getMetaData();
call.setFields(null);
call.matchFieldOrder(resultSet, this, session);
}
} else {
hasMoreResultsSets = false;
}
}
}
resultSet.close();// This must be closed in case the statement is cached and not closed.
} finally {
session.endOperationProfile(SessionProfiler.RowFetch, call.getQuery(), SessionProfiler.ALL);
}
return result;
}
/**
* This allows for the rows to be fetched concurrently to the objects being built.
* This code is not currently publicly supported.
*/
protected Vector buildThreadCursoredResult(final DatabaseCall dbCall, final ResultSet resultSet, final Statement statement, final ResultSetMetaData metaData, final AbstractSession session) {
final ThreadCursoredList results = new ThreadCursoredList<>(20);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
session.startOperationProfile(SessionProfiler.RowFetch, dbCall.getQuery(), SessionProfiler.ALL);
try {
// Initial next was already validated before this method is called.
boolean hasNext = true;
while (hasNext) {
results.add(fetchRow(dbCall.getFields(), dbCall.getFieldsArray(), resultSet, metaData, session));
hasNext = resultSet.next();
}
resultSet.close();// This must be closed in case the statement is cached and not closed.
} catch (SQLException exception) {
//If this is a connection from an external pool then closeStatement will close the connection.
//we must test the connection before that happens.
RuntimeException exceptionToThrow = processExceptionForCommError(session, exception, dbCall);
try {// Ensure that the statement is closed, but still ensure that the real exception is thrown.
closeStatement(statement, session, dbCall);
} catch (Exception closeException) {
}
if (exceptionToThrow == null){
results.throwException(DatabaseException.sqlException(exception, dbCall, DatabaseAccessor.this, session, false));
}
results.throwException(exceptionToThrow);
} catch (RuntimeException exception) {
try {// Ensure that the statement is closed, but still ensure that the real exception is thrown.
closeStatement(statement, session, dbCall);
} catch (Exception closeException) {
}
if (exception instanceof DatabaseException) {
((DatabaseException)exception).setCall(dbCall);
}
results.throwException(exception);
} finally {
session.endOperationProfile(SessionProfiler.RowFetch, dbCall.getQuery(), SessionProfiler.ALL);
}
// This is in a separate try block to ensure that the real exception is not masked by the close exception.
try {
// Allow for caching of statement, forced closes are not cache as they failed execution so are most likely bad.
DatabaseAccessor.this.releaseStatement(statement, dbCall.getSQLString(), dbCall, session);
} catch (SQLException exception) {
//With an external connection pool the connection may be null after this call, if it is we will
//be unable to determine if it is a connection based exception so treat it as if it wasn't.
DatabaseException commException = processExceptionForCommError(session, exception, dbCall);
if (commException != null) results.throwException(commException);
results.throwException(DatabaseException.sqlException(exception, DatabaseAccessor.this, session, false));
}
} finally {
results.setIsComplete(true);
session.releaseReadConnection(DatabaseAccessor.this);
}
}
};
dbCall.returnCursor();
session.getServerPlatform().launchContainerRunnable(runnable);
return results;
}
/**
* Execute the statement.
*/
public Object executeDirectNoSelect(Statement statement, DatabaseCall call, AbstractSession session) throws DatabaseException {
int rowCount = 0;
try {
if (call != null) {
session.startOperationProfile(SessionProfiler.StatementExecute, call.getQuery(), SessionProfiler.ALL);
} else {
session.startOperationProfile(SessionProfiler.StatementExecute, null, SessionProfiler.ALL);
}
if ((call != null) && call.isDynamicCall(session)) {
rowCount = statement.executeUpdate(call.getSQLString());
} else {
rowCount = ((PreparedStatement)statement).executeUpdate();
}
if ((!getPlatform().supportsAutoCommit()) && (!this.isInTransaction)) {
getPlatform().autoCommit(this);
}
} catch (SQLException exception) {
if (!getPlatform().shouldIgnoreException(exception)) {
DatabaseException commException = processExceptionForCommError(session, exception, call);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
} finally {
if (call != null) {
session.endOperationProfile(SessionProfiler.StatementExecute, call.getQuery(), SessionProfiler.ALL);
} else {
session.endOperationProfile(SessionProfiler.StatementExecute, null, SessionProfiler.ALL);
}
}
return rowCount;
}
/**
* Execute the batched statement through the JDBC2 API.
*/
protected int executeJDK12BatchStatement(Statement statement, DatabaseCall dbCall, AbstractSession session, boolean isStatementPrepared) throws DatabaseException {
int returnValue =0;
try {
//bug 4241441: executeBatch moved to the platform, and result returned to batch mechanism
returnValue = this.getPlatform().executeBatch(statement, isStatementPrepared);
} catch (SQLException exception) {
//If this is a connection from an external pool then closeStatement will close the connection.
//we must test the connection before that happens.
DatabaseException commException = processExceptionForCommError(session, exception, dbCall);
if (commException != null) throw commException;
try {// Ensure that the statement is closed, but still ensure that the real exception is thrown.
closeStatement(statement, session, dbCall);
} catch (SQLException closeException) {
}
throw DatabaseException.sqlException(exception, this, session, false);
} catch (RuntimeException exception) {
try {// Ensure that the statement is closed, but still ensure that the real exception is thrown.
closeStatement(statement, session, dbCall);
} catch (SQLException closeException) {
}
throw exception;
}
// This is in a separate try block to ensure that the real exception is not masked by the close exception.
try {
// if we are called from the ParameterizedBatchWritingMechanism then dbCall will not be null
//and we should try an release the statement
if (dbCall != null) {
releaseStatement(statement, dbCall.getSQLString(), dbCall, session);
} else {
closeStatement(statement, session, null);
}
} catch (SQLException exception) {
DatabaseException commException = processExceptionForCommError(session, exception, dbCall);
if (commException != null) throw commException;
throw DatabaseException.sqlException(exception, this, session, false);
}
return returnValue;
}
/**
* Execute the statement.
*/
protected Object executeNoSelect(DatabaseCall call, Statement statement, AbstractSession session) throws DatabaseException {
Object rowCount = executeDirectNoSelect(statement, call, session);
// Allow for procs with outputs to be raised as events for error handling.
if (call.shouldBuildOutputRow()) {
AbstractRecord outputRow = buildOutputRow((CallableStatement)statement, call, session);
call.getQuery().setProperty("output", outputRow);
if (session.hasEventManager()) {
session.getEventManager().outputParametersDetected(outputRow, call);
}
}
return rowCount;
}
/**
* Execute the statement.
*/
public boolean execute(DatabaseCall call, Statement statement, AbstractSession session) throws SQLException {
boolean result;
session.startOperationProfile(SessionProfiler.StatementExecute, call.getQuery(), SessionProfiler.ALL);
try {
if (call.isDynamicCall(session)) {
result = statement.execute(call.getSQLString());
} else {
result = ((PreparedStatement)statement).execute();
}
} finally {
session.endOperationProfile(SessionProfiler.StatementExecute, call.getQuery(), SessionProfiler.ALL);
}
return result;
}
/**
* Execute the statement.
*/
public ResultSet executeSelect(DatabaseCall call, Statement statement, AbstractSession session) throws SQLException {
ResultSet resultSet;
session.startOperationProfile(SessionProfiler.StatementExecute, call.getQuery(), SessionProfiler.ALL);
try {
if (call.isDynamicCall(session)) {
resultSet = statement.executeQuery(call.getSQLString());
} else {
resultSet = ((PreparedStatement)statement).executeQuery();
}
} finally {
session.endOperationProfile(SessionProfiler.StatementExecute, call.getQuery(), SessionProfiler.ALL);
}
// Allow for procs with outputs to be raised as events for error handling.
if (call.shouldBuildOutputRow() && getPlatform().isOutputAllowWithResultSet()) {
AbstractRecord outputRow = buildOutputRow((CallableStatement)statement, call, session);
call.getQuery().setProperty("output", outputRow);
if (session.hasEventManager()) {
session.getEventManager().outputParametersDetected(outputRow, call);
}
}
return resultSet;
}
/**
* Return a new DatabaseRow.
* Populate the row from the data in cursor. The fields representing the results
* and the order of the results are stored in fields.
*
NOTE:
* Make sure that the field name is set. An empty field name placeholder is
* used in the sortFields() method when the number of fields defined does not
* match the number of column names available on the database.
* PERF: This method must be highly optimized.
*/
protected AbstractRecord fetchRow(Vector fields, ResultSet resultSet, ResultSetMetaData metaData, AbstractSession session) throws DatabaseException {
int size = fields.size();
Vector