org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* 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 <= 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 <= 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 = 1;
} else {
rowCount = (Integer)result;
}
if ((index == 0) || (rowCount <= 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 = 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 <= 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);
}
}
}