blob: 2a65e35206d4b2cd19d424ee150bd7d1f932170a [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. 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
package org.eclipse.persistence.queries;
import java.util.Set;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.CommitManager;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.DatabaseMapping.WriteType;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventManager;
import org.eclipse.persistence.descriptors.DescriptorQueryManager;
import org.eclipse.persistence.descriptors.FetchGroupManager;
import org.eclipse.persistence.tools.profiler.QueryMonitor;
/**
* <p><b>Purpose</b>: Used for deleting objects.
*
* <p><b>Responsibilities</b>:
* Extract primary key from object and delete it.
*
* @author Yvon Lavoie
* @since TOPLink/Java 1.0
*/
public class DeleteObjectQuery extends ObjectLevelModifyQuery {
/** PERF: By default only the translation row is used for deletes, the full row can be requested for custom deletes. */
protected boolean isFullRowRequired = false;
/** Indicates whether the query should use optimistic locking. */
protected boolean usesOptimisticLocking;
public DeleteObjectQuery() {
super();
}
public DeleteObjectQuery(Object objectToDelete) {
this();
setObject(objectToDelete);
}
public DeleteObjectQuery(Call call) {
this();
setCall(call);
}
/**
* ADVANCED:
* Return if the full row is required by the delete query.
* This can be set on custom delete queries if more than the objects primary key and version is required.
*/
public boolean isFullRowRequired()
{
return isFullRowRequired;
}
/**
* ADVANCED:
* Set if the full row is required by the delete query.
* This can be set on custom delete queries if more than the objects primary key and version is required.
*/
public void setIsFullRowRequired(boolean isFullRowRequired)
{
this.isFullRowRequired = isFullRowRequired;
}
/**
* INTERNAL:
* Check to see if a custom query should be used for this query.
* This is done before the query is copied and prepared/executed.
* null means there is none.
*/
@Override
protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) {
checkDescriptor(session);
// check if user defined a custom query
DescriptorQueryManager queryManager = this.descriptor.getQueryManager();
if ((!isCallQuery())// this is not a hand-coded (custom SQL, SDK etc.) call
&& (!isUserDefined())// and this is not a user-defined query (in the query manager)
&& queryManager.hasDeleteQuery()) {// and there is a user-defined query (in the query manager)
return queryManager.getDeleteQuery();
}
return null;
}
/**
* INTERNAL:
* Code was moved from UnitOfWork.internalExecuteQuery
*
*/
@Override
protected Object executeInUnitOfWorkObjectLevelModifyQuery(UnitOfWorkImpl unitOfWork, AbstractRecord translationRow) throws DatabaseException, OptimisticLockException {
Object result = unitOfWork.processDeleteObjectQuery(this);
if (result != null) {
// if the above method returned something then the unit of work
//was not writing so the object has been stored to delete later
//so return the object. See the above method for the cases
//where this object will be returned.
return result;
}
return super.executeInUnitOfWorkObjectLevelModifyQuery(unitOfWork, translationRow);
}
/**
* INTERNAL:
* Returns the specific default redirector for this query type. There are numerous default query redirectors.
* See ClassDescriptor for their types.
*/
@Override
protected QueryRedirector getDefaultRedirector(){
return descriptor.getDefaultDeleteObjectQueryRedirector();
}
/**
* INTERNAL:
* Perform the work to delete an object.
* @return object - the object being deleted.
*/
@Override
public Object executeDatabaseQuery() throws DatabaseException, OptimisticLockException {
AbstractSession session = getSession();
CommitManager commitManager = session.getCommitManager();
Object object = getObject();
boolean isUnitOfWork = session.isUnitOfWork();
try {
// Check if the object has already been deleted, then no work is required.
if (commitManager.isCommitCompletedInPostOrIgnore(object)) {
return object;
}
ClassDescriptor descriptor = this.descriptor;
// Check whether the object is already being deleted,
// if it is, then there is a cycle, and the foreign keys must be nulled.
if (commitManager.isCommitInPreModify(object)) {
if (!commitManager.isShallowCommitted(object)) {
getQueryMechanism().updateForeignKeyFieldBeforeDelete();
if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) {
descriptor.getHistoryPolicy().postUpdate(this);
}
commitManager.markShallowCommit(object);
}
return object;
}
commitManager.markPreModifyCommitInProgress(getObject());
if (!isUnitOfWork) {
session.beginTransaction();
}
DescriptorEventManager eventManager = descriptor.getEventManager();
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
// Need to run pre-delete selector if available
eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PreDeleteEvent, this));
}
// Verify if deep shallow modify is turned on
if (shouldCascadeParts()) {
descriptor.getQueryManager().preDelete(this);
}
// Check for deletion dependencies.
if (isUnitOfWork) {
Set dependencies = ((UnitOfWorkImpl)session).getDeletionDependencies(object);
if (dependencies != null) {
for (Object dependency : dependencies) {
if (!commitManager.isCommitCompletedInPostOrIgnore(dependency)) {
ClassDescriptor dependencyDescriptor = session.getDescriptor(dependency);
// PERF: Get the descriptor query, to avoid extra query creation.
DeleteObjectQuery deleteQuery = dependencyDescriptor.getQueryManager().getDeleteQuery();
if (deleteQuery == null) {
deleteQuery = new DeleteObjectQuery();
deleteQuery.setDescriptor(dependencyDescriptor);
} else {
// Ensure original query has been prepared.
deleteQuery.checkPrepare(session, deleteQuery.getTranslationRow());
deleteQuery = (DeleteObjectQuery)deleteQuery.clone();
}
deleteQuery.setIsExecutionClone(true);
deleteQuery.setObject(dependency);
session.executeQuery(deleteQuery);
}
}
}
}
// CR#2660080 missing aboutToDelete event.
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
DescriptorEvent event = new DescriptorEvent(DescriptorEventManager.AboutToDeleteEvent, this);
event.setRecord(getModifyRow());
eventManager.executeEvent(event);
}
if (QueryMonitor.shouldMonitor()) {
QueryMonitor.incrementDelete(this);
}
int rowCount = 0;
// If the object was/will be deleted from a cascade delete constraint, ignore it.
if (isUnitOfWork && ((UnitOfWorkImpl)session).hasCascadeDeleteObjects()
&& ((UnitOfWorkImpl)session).getCascadeDeleteObjects().contains(object)) {
// Cascade delete does not check optimistic lock, assume ok.
rowCount = 1;
} else {
rowCount = getQueryMechanism().deleteObject();
}
if (rowCount < 1) {
if (session.hasEventManager()) {
session.getEventManager().noRowsModified(this, object);
}
}
if (this.usesOptimisticLocking) {
descriptor.getOptimisticLockingPolicy().validateDelete(rowCount, object, this);
}
commitManager.markPostModifyCommitInProgress(getObject());
// Verify if deep shallow modify is turned on
if (shouldCascadeParts()) {
descriptor.getQueryManager().postDelete(this);
}
if ((descriptor.getHistoryPolicy() != null) && descriptor.getHistoryPolicy().shouldHandleWrites()) {
descriptor.getHistoryPolicy().postDelete(this);
}
// PERF: Avoid events if no listeners.
if (eventManager.hasAnyEventListeners()) {
// Need to run post-delete selector if available
eventManager.executeEvent(new DescriptorEvent(DescriptorEventManager.PostDeleteEvent, this));
}
if (!isUnitOfWork) {
session.commitTransaction();
}
commitManager.markCommitCompleted(object);
// CR3510313, avoid removing aggregate collections from cache (maintain cache is false).
if (shouldMaintainCache()) {
if (isUnitOfWork) {
((UnitOfWorkImpl)session).addObjectDeletedDuringCommit(object, descriptor);
} else {
session.getIdentityMapAccessorInstance().removeFromIdentityMap(getPrimaryKey(), descriptor.getJavaClass(), descriptor, object);
}
}
return object;
} catch (RuntimeException exception) {
if (!isUnitOfWork) {
session.rollbackTransaction();
}
commitManager.markCommitCompleted(object);
throw exception;
}
}
/**
* PUBLIC:
* Return if this is a delete object query.
*/
@Override
public boolean isDeleteObjectQuery() {
return true;
}
/**
* PUBLIC: (REQUIRED)
* Set the object required for modification.
*/
@Override
public void setObject(Object object) {
if (isPrepared()) {
if (this.usesOptimisticLocking != shouldUseOptimisticLocking(object)) {
setIsPrepared(false);
}
}
super.setObject(object);
}
/**
* INTERNAL:
* Determines whether the query should use optimistic locking with the passed object.
*/
protected boolean shouldUseOptimisticLocking(Object object) {
if (this.descriptor.usesOptimisticLocking()) {
if (object != null) {
FetchGroupManager fetchGroupManager = this.descriptor.getFetchGroupManager();
if (fetchGroupManager != null) {
FetchGroup fetchGroup = fetchGroupManager.getObjectFetchGroup(object);
if (fetchGroup == fetchGroupManager.getIdEntityFetchGroup()) {
return false;
}
}
}
return true;
} else {
return false;
}
}
/**
* INTERNAL:
* Indicating whether the query should use optimistic locking.
*/
public boolean usesOptimisticLocking() {
return this.usesOptimisticLocking;
}
/**
* INTERNAL:
* Prepare the receiver for execution in a session.
*/
@Override
protected void prepare() {
super.prepare();
this.usesOptimisticLocking = shouldUseOptimisticLocking(this.object);
if (this.name == null) {
this.name = "delete" + this.descriptor.getJavaClass().getSimpleName();
}
getQueryMechanism().prepareDeleteObject();
}
/**
* INTERNAL:
* Set the properties needed to be cascaded into the custom query.
* A custom query is set by the user, or used by default to allow caching of the prepared query.
* In a unit of work the custom query is used directly, so this step is bypassed.
*/
@Override
protected void prepareCustomQuery(DatabaseQuery customQuery) {
DeleteObjectQuery customDeleteQuery = (DeleteObjectQuery)customQuery;
customDeleteQuery.setObject(getObject());
customDeleteQuery.setObjectChangeSet(getObjectChangeSet());
customDeleteQuery.setCascadePolicy(getCascadePolicy());
customDeleteQuery.setShouldMaintainCache(shouldMaintainCache());
}
/**
* INTERNAL:
* Prepare the receiver for execution in a session. In particular,
* verify that the object is not null and contains a valid primary key.
*/
@Override
public void prepareForExecution() throws QueryException {
super.prepareForExecution();
// Set the translation row
if ((this.translationRow == null) || this.translationRow.isEmpty()) {
if (this.isFullRowRequired) {
this.translationRow = this.descriptor.getObjectBuilder().buildRow(this.object, this.session, WriteType.UNDEFINED);
} else {
this.translationRow = this.descriptor.getObjectBuilder().buildRowForTranslation(this.object, this.session);
}
}
// Add the write lock field if required
if (this.usesOptimisticLocking) {
this.descriptor.getOptimisticLockingPolicy().addLockValuesToTranslationRow(this);
}
}
}