blob: 58b8a8d28cca6f305f333fd235a1d41b145bceef [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.testing.framework;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.OneToOneMapping;
import org.eclipse.persistence.queries.UpdateAllQuery;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.queries.ReportQuery;
import org.eclipse.persistence.sessions.UnitOfWork;
import org.eclipse.persistence.queries.ReportQueryResult;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.expressions.DataExpression;
public class UpdateAllQueryTestHelper {
public static String execute(Session mainSession, Class referenceClass, HashMap updateClauses, Expression selectionExpression) {
return execute(mainSession, referenceClass, updateClauses, selectionExpression, true);
}
public static String execute(Session mainSession, Class referenceClass, HashMap updateClauses, Expression selectionExpression, boolean handleChildren) {
return execute(mainSession, createUpdateAllQuery(referenceClass, updateClauses, selectionExpression), true);
}
public static String execute(Session mainSession, UpdateAllQuery uq) {
return execute(mainSession, uq, true);
}
// Compares results of updating multiple objects yusing traditionalTopLink update one-by-one approach
// with updating using UpdateAllQuery. If results differ retuns a non-null error message.
// To use this method:
// 1. Populate db with the objects on which update should be performed;
// 2. In case handleChildren == true the similar query will also be tested with all subclasses:
// example: for uq.referenceClass()==Project, the test will also run for SmallProject and LargeProject.
// 3. After the test is completed it leaves the db in the original state (uses rollbacks).
public static String execute(Session mainSession, UpdateAllQuery uq, boolean handleChildren) {
// Find the inheritance root class - the test will compare all instances of this class
// after traditional TopLink update (one-by-one) with all instances of this class
// after UpdateAllQuery. The test succeeds if the two collections are equal.
Class rootClass = uq.getReferenceClass();
ClassDescriptor descriptor = mainSession.getClassDescriptor(uq.getReferenceClass());
if(descriptor.hasInheritance()) {
ClassDescriptor parentDescriptor = descriptor;
while(!parentDescriptor.getInheritancePolicy().isRootParentDescriptor()) {
parentDescriptor = parentDescriptor.getInheritancePolicy().getParentDescriptor();
}
rootClass = parentDescriptor.getJavaClass();
}
String errorMsg = execute(mainSession, uq, handleChildren, rootClass);
if(errorMsg.length() == 0) {
return null;
} else {
return errorMsg;
}
}
protected static String execute(Session mainSession, UpdateAllQuery uq, boolean handleChildren,
Class rootClass) {
String errorMsg = "";
ClassDescriptor descriptor = mainSession.getDescriptor(uq.getReferenceClass());
clearCache(mainSession);
// original objects
Vector objects = mainSession.readAllObjects(rootClass);
// first update using the original TopLink approach - one by one.
// That will be done using report query - it will use the same selection criteria
// as UpdateAllQuery and each attribute will correspond to an update item.
ReportQuery rq = new ReportQuery(uq.getReferenceClass(), uq.getExpressionBuilder());
rq.setSelectionCriteria(uq.getSelectionCriteria());
rq.setShouldRetrievePrimaryKeys(true);
// some db platforms don't allow nulls in select clause - so add the fields with null values to the query result.
Vector fieldsWithNullValues = new Vector();
Iterator itEntrySets = uq.getUpdateClauses().entrySet().iterator();
while(itEntrySets.hasNext()) {
Map.Entry entry = (Map.Entry)itEntrySets.next();
Expression valueExpression;
String keyString = getQualifiedFieldNameFromKey(entry.getKey(), rq.getReferenceClass(), descriptor, mainSession);
Object value = entry.getValue();
DatabaseMapping mapping = descriptor.getObjectBuilder().getMappingForField(new DatabaseField(keyString));
if(mapping != null && mapping.isOneToOneMapping() && value != null) {
// Note that this only works in case the reference PK is not compound
if(((OneToOneMapping)mapping).getSourceToTargetKeyFields().size() > 1) {
errorMsg = "Attribute "+ mapping.getAttributeName() + " mapped with 1to1 mapping that has more than one targetKeyField. UpdateAllQueryTestHelper currently doesn't support that.";
}
DatabaseField targetField = ((OneToOneMapping)mapping).getSourceToTargetKeyFields().get(new DatabaseField(keyString));
if(value instanceof Expression) {
valueExpression = ((Expression)(((Expression)value).clone())).getField(targetField);
} else {
ClassDescriptor targetDescriptor = mapping.getReferenceDescriptor();
Object fieldValue = targetDescriptor.getObjectBuilder().extractValueFromObjectForField(value, targetField, (org.eclipse.persistence.internal.sessions.AbstractSession)mainSession);
valueExpression = rq.getExpressionBuilder().value(fieldValue);
}
} else {
if(value instanceof Expression) {
valueExpression = (Expression)value;
} else {
valueExpression = rq.getExpressionBuilder().value(value);
}
}
if(value == null) {
fieldsWithNullValues.add(keyString);
} else {
rq.addAttribute(keyString, valueExpression);
}
}
UnitOfWork uow = mainSession.acquireUnitOfWork();
// mainSession could be a ServerSession
AbstractSession session = uow.getParent();
// report query results contain the values to be assigned for each object to be updated.
Vector result = (Vector)session.executeQuery(rq);
Vector objectsAfterOneByOneUpdate = new Vector(objects.size());
session.beginTransaction();
try {
for (int i = 0; i < result.size(); i++) {
// read through uow the object(clone) to be updated
ReportQueryResult reportResult = (ReportQueryResult)result.elementAt(i);
// hammer into the object the updated values
Object obj = reportResult.readObject(rq.getReferenceClass(), uow);
DatabaseRecord row = new DatabaseRecord();
for (int j = 0; j < reportResult.getNames().size(); j++) {
String name = reportResult.getNames().get(j);
DatabaseField field = new DatabaseField(name);
Object value = reportResult.getResults().get(j);
row.add(field, value);
}
// some db platforms don't allow nulls in select clause - so add the fields with null values to the query result
for (int j = 0; j < fieldsWithNullValues.size(); j++) {
String name = (String)fieldsWithNullValues.elementAt(j);
DatabaseField field = new DatabaseField(name);
row.add(field, null);
}
rq.getDescriptor().getObjectBuilder().assignReturnRow(obj, (AbstractSession)uow, row, null);
}
// uow committed - objects updated.
uow.commit();
// Because the transaction will be rolled back (to return to the original state to execute UpdateAllQuery)
// objects are copied into another vector - later it will be compared with UpdateAllQuery result.
for(int i=0; i < objects.size(); i++) {
Object original = objects.elementAt(i);
Object copy = buildCopy(descriptor, original, uow);
objectsAfterOneByOneUpdate.add(copy);
}
} finally {
// transaction rolled back - objects back to the original state in the db.
session.rollbackTransaction();
}
clearCache(mainSession);
// now use UpdateAllQuery
uow = mainSession.acquireUnitOfWork();
// mainSession could be a ServerSession
session = uow.getParent();
Vector objectsAfterUpdateAll = new Vector(objects.size());
session.beginTransaction();
try {
uow.executeQuery(uq);
// uow committed - objects updated.
uow.commit();
// Because the transaction will be rolled back (to return to the original state)
// objects are copied into another vector - it will be compared with update one-by-one result.
for(int i=0; i < objects.size(); i++) {
Object original = objects.elementAt(i);
Object copy = buildCopy(descriptor, original, uow);
objectsAfterUpdateAll.add(copy);
}
} finally {
// transaction rolled back - objects back to the original state in the db.
session.rollbackTransaction();
}
clearCache(mainSession);
// verify
String classErrorMsg = "";
for(int i=0; i < objects.size(); i++) {
Object obj = objects.elementAt(i);
Object obj1 = objectsAfterOneByOneUpdate.elementAt(i);
Object obj2 = objectsAfterUpdateAll.elementAt(i);
boolean equal = rq.getDescriptor().getObjectBuilder().compareObjects(obj, obj2, session);
if(!equal) {
classErrorMsg = classErrorMsg + "Difference: original = " + obj.toString() + "; afterOneByOneUpdate = " + obj1.toString() +"; afterUpdateAll = " + obj2.toString() + ";";
}
}
if(classErrorMsg.length() > 0) {
errorMsg = errorMsg + classErrorMsg;
}
if(handleChildren) {
if(descriptor.hasInheritance() && descriptor.getInheritancePolicy().hasChildren()) {
Iterator<ClassDescriptor> it = descriptor.getInheritancePolicy().getChildDescriptors().iterator();
while(it.hasNext()) {
ClassDescriptor childDescriptor = it.next();
Class childReferenceClass = childDescriptor.getJavaClass();
UpdateAllQuery childUq = (UpdateAllQuery)uq.clone();
childUq.setReferenceClass(childReferenceClass);
childUq.setIsPrepared(false);
errorMsg += execute(mainSession, childUq, handleChildren, rootClass);
}
}
}
return errorMsg;
}
protected static void clearCache(Session mainSession) {
mainSession.getIdentityMapAccessor().initializeAllIdentityMaps();
}
public static UpdateAllQuery createUpdateAllQuery(Class referenceClass, HashMap updateClauses, Expression selectionExpression) {
// Construct UpdateAllQuery
UpdateAllQuery uq = new UpdateAllQuery(referenceClass, selectionExpression);
Iterator itEntrySets = updateClauses.entrySet().iterator();
while(itEntrySets.hasNext()) {
Map.Entry entry = (Map.Entry)itEntrySets.next();
uq.addUpdate((String)entry.getKey(), entry.getValue());
}
return uq;
}
static protected Object buildCopy(ClassDescriptor descriptor, Object original, UnitOfWork uow) {
Object copy = descriptor.getCopyPolicy().buildClone(original, uow);
descriptor.getObjectBuilder().copyInto(original, copy, true);
return copy;
}
static protected String getQualifiedFieldNameFromKey(Object key, Class referenceClass, ClassDescriptor descriptor, Session session) {
DatabaseField field = null;
if(key instanceof String) {
// attribute name
String name = (String)key;
DatabaseMapping mapping = descriptor.getObjectBuilder().getMappingForAttributeName(name);
if(mapping != null) {
field = mapping.getFields().firstElement();
}
} else if(key instanceof DataExpression) {
DataExpression fieldExpression = (DataExpression)key;
field = descriptor.getObjectBuilder().getFieldForQueryKeyName(fieldExpression.getName());
if(field == null) {
DataExpression fieldExpressionClone = (DataExpression)fieldExpression.clone();
fieldExpressionClone.getBuilder().setQueryClass(referenceClass);
fieldExpressionClone.getBuilder().setSession((org.eclipse.persistence.internal.sessions.AbstractSession)session);
field = fieldExpressionClone.getField();
}
}
if(field != null) {
return field.getQualifiedName();
}
// should never happen
return null;
}
}