| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 1998, 2019 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.helper.NonSynchronizedVector; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| 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; |
| |
| /** |
| * <p><b>Purpose</b>: |
| * Mechanism used for call queries. |
| * </p> |
| * <p><b>Responsibilities</b>: |
| * Executes the appropriate call. |
| * </p> |
| * @author James Sutherland |
| * @since OracleAS TopLink 10<i>g</i> (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<DatasourceCall>}. |
| */ |
| @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 the row count. |
| */ |
| @Override |
| public Integer executeNoSelect() throws DatabaseException { |
| return executeNoSelectCall(); |
| } |
| |
| /** |
| * 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) { |
| 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) { |
| 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<DatasourceCall>)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<ReportItem> items, int itemOffset) { |
| for(ReportItem item : items) { |
| if (item.isConstructorItem()) { |
| List<ReportItem> 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); |
| } |
| } |
| } |