org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism 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
// 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
// 01/31/2017-2.6 Will Dazey
// - 511426: Adding cloning support
// 04/11/2018 - Will Dazey
// - 533148 : Add the eclipselink.jpa.sql-call-deferral property
package org.eclipse.persistence.internal.queries;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.internal.databaseaccess.Accessor;
import org.eclipse.persistence.internal.databaseaccess.DatabaseCall;
import org.eclipse.persistence.internal.databaseaccess.DatasourceCall;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping.WriteType;
import org.eclipse.persistence.queries.ConstructorReportItem;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.DeleteAllQuery;
import org.eclipse.persistence.queries.ReportQuery;
import org.eclipse.persistence.queries.UpdateAllQuery;
import org.eclipse.persistence.queries.WriteObjectQuery;
/**
* Purpose:
* Mechanism used for call queries.
*
*
Responsibilities:
* Executes the appropriate call.
*
* @author James Sutherland
* @since OracleAS TopLink 10g (10.0.3)
*/
public class DatasourceCallQueryMechanism extends DatabaseQueryMechanism {
protected DatasourceCall call;
/** Normally only a single call is used, however multiple table may require multiple calls on write. */
protected Vector calls;
public DatasourceCallQueryMechanism() {
}
/**
* Initialize the state of the query
* @param query - owner of mechanism
*/
public DatasourceCallQueryMechanism(DatabaseQuery query) {
super(query);
}
/**
* Initialize the state of the query
* @param query - owner of mechanism
*/
public DatasourceCallQueryMechanism(DatabaseQuery query, DatasourceCall call) {
super(query);
this.call = call;
call.setQuery(query);
}
/**
* Add the call.
*/
public void addCall(DatasourceCall call) {
getCalls().addElement(call);
call.setQuery(getQuery());
}
/**
* Clone the DatasourceCall and {@code Vector}.
*/
@Override
public DatabaseQueryMechanism clone(DatabaseQuery queryClone) {
DatasourceCallQueryMechanism clone = (DatasourceCallQueryMechanism)super.clone(queryClone);
if(this.call != null) {
DatasourceCall callclone = (DatasourceCall)this.call.clone();
clone.setCall(callclone);
}
if(this.calls != null) {
Vector callsclone = (Vector)this.calls.clone();
clone.setCalls(callsclone);
}
return clone;
}
/**
* Read all rows from the database using a cursored stream.
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public DatabaseCall cursorSelectAllRows() throws DatabaseException {
try {
return (DatabaseCall)executeCall();
} catch (java.lang.ClassCastException e) {
throw QueryException.mustUseCursorStreamPolicy();
}
}
/**
* Read all rows from the database, return ResultSet
* @exception DatabaseException - an error has occurred on the database
*/
public DatabaseCall selectResultSet() throws DatabaseException {
try {
// For CR 2923 must move to session we will execute call on now
// so correct DatasourcePlatform used by translate.
AbstractSession sessionToUse = this.query.getExecutionSession();
DatabaseCall clonedCall = (DatabaseCall)this.call.clone();
clonedCall.setQuery(this.query);
clonedCall.translate(this.query.getTranslationRow(), getModifyRow(), sessionToUse);
clonedCall.returnCursor();
return (DatabaseCall)sessionToUse.executeCall(clonedCall, this.query.getTranslationRow(), this.query);
} catch (java.lang.ClassCastException e) {
throw QueryException.invalidDatabaseCall(this.call);
}
}
/**
* INTERNAL:
* Delete a collection of objects. Assume call is correct.
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public Integer deleteAll() throws DatabaseException {
if(((DeleteAllQuery)this.query).isPreparedUsingTempStorage()) {
return deleteAllUsingTempTables();
} else {
if (hasMultipleCalls()) {
Integer returnedRowCount = null;
// Deletion must occur in reverse order.
for (int index = getCalls().size() - 1; index >= 0; index--) {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(index);
returnedRowCount = (Integer)executeCall(databseCall);
}
// returns the number of rows removed from the first table in insert order
return returnedRowCount;
} else {
return (Integer)executeCall();
}
}
}
/**
* Execute deleteAll using temp tables
* @exception DatabaseException - an error has occurred on the database.
* @return the row count.
*/
public Integer deleteAllUsingTempTables() throws DatabaseException {
DatabaseException ex = null;
Integer returnedRowCount = null;
// Deletion must occur in reverse order.
// first call - crete temp table.
// may fail in case global temp table already exists.
try {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(getCalls().size() - 1);
executeCall(databseCall);
} catch (DatabaseException databaseEx) {
// ignore
}
// second call - populate temp table.
// if that fails save the exception and untill cleanup
try {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(getCalls().size() - 2);
executeCall(databseCall);
} catch (DatabaseException databaseEx) {
ex = databaseEx;
}
// third (a call per table) - delete from original tables calls.
// if that fails save the exception untill cleanup
for (int index = getCalls().size() - 3; index >= 1 && ex == null; index--) {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(index);
try {
// returns the number of rows removed from the first table in insert order
returnedRowCount = (Integer)executeCall(databseCall);
} catch (DatabaseException databaseEx) {
ex = databaseEx;
}
}
// last call - cleanup temp table.
// ignore exceptions here.
try {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(0);
executeCall(databseCall);
} catch (DatabaseException databaseEx) {
// ignore
}
if(ex != null) {
throw ex;
}
return returnedRowCount;
}
/**
* INTERNAL:
* Delete an object. Assume call is correct
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public Integer deleteObject() throws DatabaseException {
if (hasMultipleCalls()) {
Integer returnedRowCount = null;
// Deletion must occur in reverse order.
for (int index = getCalls().size() - 1; index >= 0; index--) {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(index);
Integer rowCount = (Integer)executeCall(databseCall);
if ((index == (getCalls().size() - 1)) || (rowCount.intValue() <= 0)) {// Row count returned must be from first table or zero if any are zero.
returnedRowCount = rowCount;
}
}
return returnedRowCount;
} else {
return (Integer)executeCall();
}
}
/**
* INTERNAL:
* Execute a call.
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public Object execute() throws DatabaseException {
return executeCall();
}
/**
* Execute the call. It is assumed the call has been fully prepared.
* @exception DatabaseException - an error has occurred on the database.
*/
protected Object executeCall() throws DatabaseException {
return executeCall(this.call);
}
/**
* Execute the call. It is assumed the call has been fully prepared.
* @exception DatabaseException - an error has occurred on the database.
*/
protected Object executeCall(DatasourceCall databaseCall) throws DatabaseException {
// For CR 2923 must move to session we will execute call on now
// so correct DatasourcePlatform used by translate.
AbstractSession sessionToUse = this.query.getExecutionSession();
DatasourceCall clonedCall = (DatasourceCall)databaseCall.clone();
clonedCall.setQuery(this.query);
clonedCall.translate(this.query.getTranslationRow(), getModifyRow(), sessionToUse);
return sessionToUse.executeCall(clonedCall, this.query.getTranslationRow(), this.query);
}
/**
* Execute a non selecting call.
* @exception DatabaseException - an error has occurred on the database.
* @return Returns either a {@link DatabaseCall} or Integer value,
* depending on if this INSERT call needs to return generated keys
*/
@Override
public Object executeNoSelect() throws DatabaseException {
if(((DatabaseCall)this.call).shouldReturnGeneratedKeys()) {
return generateKeysExecuteNoSelect();
}
return executeNoSelectCall();
}
/**
* Execute a non selecting call.
* @exception DatabaseException - an error has occurred on the database.
* @return the row count.
*/
public DatabaseCall generateKeysExecuteNoSelect() throws DatabaseException {
return (DatabaseCall)executeCall();
}
/**
* Execute a non selecting call.
* @exception DatabaseException - an error has occurred on the database.
* @return the row count.
*/
public Integer executeNoSelectCall() throws DatabaseException {
if (hasMultipleCalls()) {
Integer returnedRowCount = null;
for (int index = 0; index < getCalls().size(); index++) {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(index);
Integer rowCount = (Integer)executeCall(databseCall);
if ((index == 0) || (rowCount.intValue() <= 0)) {// Row count returned must be from first table or zero if any are zero.
returnedRowCount = rowCount;
}
}
return returnedRowCount;
} else {
return (Integer)executeCall();
}
}
/**
* INTERNAL:
* Execute a selecting call.
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public Vector executeSelect() throws DatabaseException {
return executeSelectCall();
}
/**
* INTERNAL:
* Execute a selecting call.
* @exception DatabaseException - an error has occurred on the database
*/
public Vector executeSelectCall() throws DatabaseException {
if (hasMultipleCalls()) {
Vector results = new Vector();
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
DatasourceCall databseCall = (DatasourceCall)callsEnum.nextElement();
Helper.addAllToVector(results, (Vector)executeCall(databseCall));
}
return results;
} else {
return (Vector)executeCall();
}
}
/**
* Return the call.
*/
public DatasourceCall getCall() {
return call;
}
/**
* Normally only a single call is used, however multiple table may require multiple calls on write.
* This is lazy initialised to conserve space.
*/
public Vector getCalls() {
if (calls == null) {
calls = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(3);
}
return calls;
}
/**
* Normally only a single call is used, however multiple table may require multiple calls on write.
* This is lazy initialised to conserve space.
*/
public boolean hasMultipleCalls() {
return (this.calls != null) && (!this.calls.isEmpty());
}
/**
* Insert the object. Assume the call is correct.
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public void insertObject() throws DatabaseException {
ClassDescriptor descriptor = getDescriptor();
boolean usesSequencing = descriptor.usesSequenceNumbers();
boolean shouldAcquireValueAfterInsert = false;
if (usesSequencing) {
shouldAcquireValueAfterInsert = descriptor.getSequence().shouldAcquireValueAfterInsert();
}
Collection returnFields = null;
if (descriptor.getReturnFieldsToMergeInsert() != null) {
returnFields = descriptor.getReturnFieldsToMergeInsert();
}
// Check to see if sequence number should be retrieved after insert
if (usesSequencing && !shouldAcquireValueAfterInsert) {
// PERF: Unit of work always assigns sequence, so only need to check it here for non unit of work/change set query.
if (getWriteObjectQuery().getObjectChangeSet() == null) {
// This is the normal case. Update object with sequence number before insert.
updateObjectAndRowWithSequenceNumber();
}
}
if (hasMultipleCalls()) {
int size = this.calls.size();
for (int index = 0; index < size; index++) {
DatasourceCall databseCall = (DatasourceCall)this.calls.get(index);
if ((index > 0) && isExpressionQueryMechanism()
&& this.query.shouldCascadeOnlyDependentParts() && !descriptor.hasMultipleTableConstraintDependecy()
&& this.query.getSession().getProject().allowSQLDeferral()) {
DatabaseTable table = descriptor.getMultipleTableInsertOrder().get(index);
this.query.getSession().getCommitManager().addDeferredCall(table, databseCall, this);
} else {
Object result = executeCall(databseCall);
// Set the return row if one was returned (Postgres).
if (result instanceof AbstractRecord) {
this.query.setProperty("output", result);
}
if (returnFields != null) {
updateObjectAndRowWithReturnRow(returnFields, index == 0);
}
if ((index == 0) && usesSequencing && shouldAcquireValueAfterInsert) {
if(result instanceof DatabaseCall) {
updateObjectAndRowWithSequenceNumber((DatabaseCall) result);
} else {
updateObjectAndRowWithSequenceNumber();
}
}
}
}
} else {
Object result = executeCall();
// Set the return row if one was returned (Postgres).
if (result instanceof AbstractRecord) {
this.query.setProperty("output", result);
}
if (returnFields != null) {
updateObjectAndRowWithReturnRow(returnFields, true);
}
if (usesSequencing && shouldAcquireValueAfterInsert) {
if(result instanceof DatabaseCall) {
updateObjectAndRowWithSequenceNumber((DatabaseCall) result);
} else {
updateObjectAndRowWithSequenceNumber();
}
}
}
// Bug 3110860: RETURNINGPOLICY-OBTAINED PK CAUSES LOB TO BE INSERTED INCORRECTLY
// The deferred locator SELECT calls should be generated and executed after ReturningPolicy
// merges PK obtained from the db into the object held by the query.
//
//Oracle thin driver handles LOB differently. During the insert, empty lob would be
//insert first, and then the LOb locator is retrieved and LOB data are written through
//the locator.
//
// Bug 2804663 - LOBValueWriter is no longer a singleton, so we execute any deferred
// select calls through the DatabaseAccessor which holds the writer instance
AbstractSession executionSession = this.query.getExecutionSession();
for (Accessor accessor : executionSession.getAccessors()) {
accessor.flushSelectCalls(executionSession);
}
}
/**
* Execute the call that was deferred to the commit manager.
* This is used to allow multiple table batching and deadlock avoidance.
*/
@Override
public void executeDeferredCall(DatasourceCall call) {
Object result = executeCall(call);
// Set the return row if one was returned (Postgres).
if (result instanceof AbstractRecord) {
this.query.setProperty("output", result);
}
Collection returnFields = null;
if (this.query.getDescriptor().hasReturningPolicy()) {
returnFields = this.query.getDescriptor().getReturningPolicy().getFieldsToMergeInsert();
}
if (returnFields != null) {
updateObjectAndRowWithReturnRow(returnFields, false);
}
}
/**
* Return true if this is a call query mechanism
*/
@Override
public boolean isCallQueryMechanism() {
return true;
}
/**
* INTERNAL:
* This is different from 'prepareForExecution' in that this is called on the original query,
* and the other is called on the copy of the query.
* This query is copied for concurrency so this prepare can only setup things that
* will apply to any future execution of this query.
*/
@Override
public void prepare() {
if ((!hasMultipleCalls()) && (getCall() == null)) {
throw QueryException.sqlStatementNotSetProperly(getQuery());
}
}
/**
* INTERNAL:
* This is different from 'prepareForExecution' in that this is called on the original query,
* and the other is called on the copy of the query.
* This query is copied for concurrency so this prepare can only setup things that
* will apply to any future execution of this query.
*/
public void prepareCall() throws QueryException {
DatabaseQuery query = getQuery();
AbstractSession executionSession = query.getExecutionSession();
if (hasMultipleCalls()) {
for (DatasourceCall call : (List)getCalls()) {
call.prepare(executionSession);
}
} else if (getCall() != null) {
getCall().prepare(executionSession);
}
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareCursorSelectAllRows() throws QueryException {
getCall().returnCursor();
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareDeleteAll() {
if (hasMultipleCalls()) {
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
DatasourceCall call = (DatasourceCall)callsEnum.nextElement();
call.returnNothing();
}
} else {
getCall().returnNothing();
}
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareDeleteObject() {
if (hasMultipleCalls()) {
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
DatasourceCall call = (DatasourceCall)callsEnum.nextElement();
call.returnNothing();
}
} else {
getCall().returnNothing();
}
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareDoesExist(DatabaseField field) {
if (hasMultipleCalls()) {
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
((DatasourceCall)callsEnum.nextElement()).returnOneRow();
}
} else {
getCall().returnOneRow();
}
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareExecuteNoSelect() {
if (hasMultipleCalls()) {
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
((DatasourceCall)callsEnum.nextElement()).returnNothing();
}
} else {
getCall().returnNothing();
}
prepareCall();
}
/**
* Pre-build configure the call. This method assumes the query was built
* using a stored procedure query which is a single call.
*
* The return type on the call will already be set and
*/
@Override
public void prepareExecute() {
getCall().setExecuteUpdate();
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareExecuteSelect() {
if (hasMultipleCalls()) {
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
DatasourceCall databseCall = (DatasourceCall)callsEnum.nextElement();
databseCall.returnManyRows();
}
} else {
getCall().returnManyRows();
}
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareInsertObject() {
if (hasMultipleCalls()) {
int size = this.calls.size();
for (int index = 0; index < size; index++) {
DatabaseCall call = (DatabaseCall)this.calls.get(index);
if (!call.isReturnSet()) {
call.returnNothing();
}
}
} else {
if (!this.call.isReturnSet()) {
this.call.returnNothing();
}
}
prepareCall();
}
/**
* Prepare the report items.
* Indexes of results need to be calculated.
*/
protected void prepareReportQueryItems(){
//calculate indexes after normalize to insure expressions are set up correctly
//take into account any field expressions added to the ReportQuery
ReportQuery query = (ReportQuery)getQuery();
computeAndSetItemOffset(query, query.getItems(), query.getQueryExpressions().size());
}
/**
* calculate indexes for given items, given the current Offset
*/
protected int computeAndSetItemOffset(ReportQuery query, List items, int itemOffset) {
for(ReportItem item : items) {
if (item.isConstructorItem()) {
List reportItems = ((ConstructorReportItem) item).getReportItems();
itemOffset = computeAndSetItemOffset(query, reportItems, itemOffset);
} else {
//Don't set the offset on the ConstructorItem
item.setResultIndex(itemOffset);
if (item.getAttributeExpression() != null) {
if (item.hasJoining()){
itemOffset = item.getJoinedAttributeManager().computeJoiningMappingIndexes(true, getSession(), itemOffset);
} else {
if (item.getDescriptor() != null) {
itemOffset += item.getDescriptor().getAllSelectionFields(query).size();
} else {
if (item.getMapping() != null && item.getMapping().isAggregateObjectMapping()) {
itemOffset += item.getMapping().getFields().size(); // Aggregate object may consist out of 1..n fields
} else {
++itemOffset; //only a single attribute can be selected
}
}
}
}
}
}
return itemOffset;
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareReportQuerySelectAllRows() {
prepareReportQueryItems();
prepareExecuteSelect();
}
/**
* Prepare for a sub select using a call.
*/
@Override
public void prepareReportQuerySubSelect() {
prepareReportQueryItems();
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareSelectAllRows() {
if (hasMultipleCalls()) {
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
DatasourceCall databseCall = (DatasourceCall)callsEnum.nextElement();
databseCall.returnManyRows();
}
} else {
getCall().returnManyRows();
}
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareSelectOneRow() {
if (hasMultipleCalls()) {
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
DatasourceCall databseCall = (DatasourceCall)callsEnum.nextElement();
databseCall.returnOneRow();
}
} else {
getCall().returnOneRow();
}
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareUpdateObject() {
if (hasMultipleCalls()) {
int size = this.calls.size();
for (int index = 0; index < size; index++) {
DatabaseCall call = (DatabaseCall)this.calls.get(index);
if (!call.isReturnSet()) {
call.returnNothing();
}
}
} else if (getCall() != null) {
if (!call.isReturnSet()) {
this.call.returnNothing();
}
}
prepareCall();
}
/**
* Pre-build configure the call.
*/
@Override
public void prepareUpdateAll() {
if (getCall() != null) {
getCall().returnNothing();
}
prepareCall();
}
/**
* Read all rows from the database. Assume call is correct returns the required fields.
* @return Vector containing the database rows
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public Vector selectAllReportQueryRows() throws DatabaseException {
return executeSelect();
}
/**
* Read all rows from the database. Assume call is correct returns the required fields.
* @return Vector containing the database rows
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public Vector selectAllRows() throws DatabaseException {
return executeSelectCall();
}
/**
* Read a single row from the database. Assume call is correct.
* @return row containing data
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public AbstractRecord selectOneRow() throws DatabaseException {
if (hasMultipleCalls()) {
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
DatasourceCall databaseCall = (DatasourceCall)callsEnum.nextElement();
AbstractRecord result = (AbstractRecord)executeCall(databaseCall);
if (result != null) {
return result;
}
}
return null;
} else {
return (AbstractRecord)executeCall();
}
}
/**
* Perform a does exist check
* @param field - the field used for does exist check
* @return the associated row from the database
* @exception DatabaseException - an error has occurred on the database
*/
@Override
public AbstractRecord selectRowForDoesExist(DatabaseField field) throws DatabaseException {
if (hasMultipleCalls()) {
for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) {
DatasourceCall databaseCall = (DatasourceCall)callsEnum.nextElement();
AbstractRecord result = (AbstractRecord)executeCall(databaseCall);
if (result != null) {
return result;
}
}
return null;
} else {
return (AbstractRecord)executeCall();
}
}
/**
* Set the call.
*/
public void setCall(DatasourceCall call) {
this.call = call;
if (call != null) {
call.setQuery(getQuery());
}
}
/**
* Normally only a single call is used, however multiple table may require multiple calls on write.
* This is lazy initialised to conserve space.
*/
protected void setCalls(Vector calls) {
this.calls = calls;
}
/**
* Update the object. Assume the call is correct.
* @exception DatabaseException - an error has occurred on the database.
* @return the row count.
*/
@Override
public Integer updateObject() throws DatabaseException {
ClassDescriptor descriptor = getDescriptor();
Collection returnFields = null;
if (descriptor.getReturnFieldsToMergeUpdate() != null) {
returnFields = descriptor.getReturnFieldsToMergeUpdate();
}
Integer returnedRowCount = null;
if (hasMultipleCalls()) {
int size = this.calls.size();
for (int index = 0; index < size; index++) {
DatasourceCall databseCall = (DatasourceCall)this.calls.get(index);
if ((index > 0) && isExpressionQueryMechanism()
&& this.query.shouldCascadeOnlyDependentParts() && !descriptor.hasMultipleTableConstraintDependecy()
&& this.query.getSession().getProject().allowSQLDeferral()) {
DatabaseTable table = descriptor.getMultipleTableInsertOrder().get(index);
this.query.getSession().getCommitManager().addDeferredCall(table, databseCall, this);
} else {
Object result = executeCall(databseCall);
// Set the return row if one was returned (Postgres).
Integer rowCount;
if (result instanceof AbstractRecord) {
this.query.setProperty("output", result);
rowCount = Integer.valueOf(1);
} else {
rowCount = (Integer)result;
}
if ((index == 0) || (rowCount.intValue() <= 0)) {// Row count returned must be from first table or zero if any are zero.
returnedRowCount = rowCount;
}
if (returnFields != null) {
updateObjectAndRowWithReturnRow(returnFields, false);
}
}
}
} else {
Object result = executeCall();
// Set the return row if one was returned (Postgres).
if (result instanceof AbstractRecord) {
this.query.setProperty("output", result);
returnedRowCount = Integer.valueOf(1);
} else {
returnedRowCount = (Integer)result;
}
if (returnFields != null) {
updateObjectAndRowWithReturnRow(returnFields, false);
}
}
//Oracle thin driver handles LOB differently. During the insert, empty lob would be
//insert first, and then the LOb locator is retrieved and LOB data are written through
//the locator.
//
// Bug 2804663 - LOBValueWriter is no longer a singleton, so we execute any deferred
// select calls through the DatabaseAccessor which holds the writer instance
//
// Building of SELECT statements is no longer done in DatabaseAccessor.basicExecuteCall
// because DatabaseCall.isUpdateCall() can't recognize update in case StoredProcedureCall
// is used.
AbstractSession executionSession = this.query.getExecutionSession();
for (Accessor accessor : executionSession.getAccessors()) {
accessor.flushSelectCalls(executionSession);
}
return returnedRowCount;
}
/**
* Update the rows on the database. Assume the call is correct.
* @exception DatabaseException - an error has occurred on the database.
*/
@Override
public Integer updateAll() throws DatabaseException {
if(((UpdateAllQuery)this.query).isPreparedUsingTempStorage() && getExecutionSession().getPlatform().supportsTempTables()) {
return updateAllUsingTempTables();
} else {
Integer rowCount = executeNoSelectCall();
if(((UpdateAllQuery)this.query).isPreparedUsingTempStorage()) {
// the query was prepared using Oracle anonymous block
AbstractRecord outputRow = (AbstractRecord)this.query.getProperty("output");
rowCount = (Integer)outputRow.get("ROW_COUNT");
}
return rowCount;
}
}
/**
* Execute updateAll using temp tables
* @exception DatabaseException - an error has occurred on the database.
* @return the row count.
*/
public Integer updateAllUsingTempTables() throws DatabaseException {
int nTables = getCalls().size() / 4;
DatabaseException ex = null;
Integer returnedRowCount = null;
// first quarter - crete temp tables calls.
// may fail in case global temp table already exists.
for (int index = 0; index < nTables; index++) {
try {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(index);
executeCall(databseCall);
} catch (DatabaseException databaseEx) {
// ignore
}
}
// second quarter - populate temp tables calls.
// if that fails save the exception and until cleanup
for (int index = nTables; index < nTables*2 && ex == null; index++) {
try {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(index);
executeCall(databseCall);
} catch (DatabaseException databaseEx) {
ex = databaseEx;
}
}
// third quarter - update original tables calls.
// if that fails save the exception and until cleanup
for (int index = nTables*2; index < nTables*3 && ex == null; index++) {
try {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(index);
Integer rowCount = (Integer)executeCall(databseCall);
if ((index == nTables*2) || (rowCount.intValue() <= 0)) {// Row count returned must be from first table or zero if any are zero.
returnedRowCount = rowCount;
}
} catch (DatabaseException databaseEx) {
ex = databaseEx;
}
}
// last quarter - cleanup temp tables calls.
// ignore exceptions here.
for (int index = nTables*3; index < nTables*4; index++) {
try {
DatasourceCall databseCall = (DatasourceCall)getCalls().elementAt(index);
executeCall(databseCall);
} catch (DatabaseException databaseEx) {
// ignore
}
}
if(ex != null) {
throw ex;
}
return returnedRowCount;
}
/**
* Update the foreign key fields when resolving a bi-directional reference in a UOW.
* This is rare to occur for non-relational, however if it does each of the calls must be re-executed.
*/
@Override
protected void updateForeignKeyFieldAfterInsert(WriteObjectQuery writeQuery) {
writeQuery.setModifyRow(this.getDescriptor().getObjectBuilder().buildRow(writeQuery.getObject(), this.getSession(), WriteType.INSERT));
// For CR 2923 must move to session we will execute call on now
// so correct DatasourcePlatform used by translate.
AbstractSession sessionToUse = this.query.getExecutionSession();
// yes - this is a bit ugly...
Vector calls = ((DatasourceCallQueryMechanism)this.getDescriptor().getQueryManager().getUpdateQuery().getQueryMechanism()).getCalls();
for (Enumeration stream = calls.elements(); stream.hasMoreElements();) {
DatasourceCall call = (DatasourceCall)((DatasourceCall)stream.nextElement()).clone();
call.setQuery(writeQuery);
sessionToUse.executeCall(call, this.getTranslationRow(), writeQuery);
}
}
}