blob: 010949c73bd1bbad4b11f76049c113f7ad11f1aa [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.tests.queries;
import java.util.Vector;
import org.eclipse.persistence.queries.*;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.mappings.*;
import org.eclipse.persistence.testing.framework.*;
import org.eclipse.persistence.testing.models.employee.domain.*;
/**
* <b>Purpose</b>: Test for bug 2782991: Find by Primary Key with
* conforming does Linear Search.
* <b>Responsibilities</b>:
* <ul><li>Find a way to tell if a cache check takes constant or linear time
* relative to the size of the cache.
* <li>For most reads by primary key the cache check should take constant time.
* </ul>
* <p>Cases:
* <ul><li>Existing Object: Should take constant time.
* <li>New Object (new objects not cached): Should take time relative to the
* size of the new objects cache only.
* <li>New Object (new objects cached): Not tested. Should take constant
* time.
* <li>Deleted Object: Should take constant time. Even though the query should
* return null, the cache hit should succeed.
* <li>Not Existing Object: Should take constant time, for if can't find by
* exact primary key the object is not there.
* </ul>
* In the case of find by inexact primary key, the new and not existing cases
* take a linear number of extra calls.
* <p><b>Future testing:</b>
* <ul><li>Set conforming on the descriptor also (regression).
* <li>Set conforming on the descriptor only (regression).
* <li>Test all the other non conforming options, such as check cache by exact
* primary key.
* <li>Test case where no selection criteria is specified, and the first object
* returned happens to be deleted.
* <li>Test case where selection object is specified. (See {@link ConformResultsWithSelectionObjectTest}).
* <li>A true not existing case: the object does not exist on the database either.
* </ul>
* @author Stephen McRitchie
* @since 9.0.4.0
*/
public class ConformResultsWithPrimaryKeyExpressionTest extends ConformResultsInUnitOfWorkTest {
public static final int CASE_NEW = 0;
public static final int CASE_DELETED = 1;
public static final int CASE_EXISTING = 2;
public static final int CASE_NOTEXISTING = 3;
public final int testCase;
public final boolean checkCacheByExactPrimaryKey;
public int expectedGetIdCallCount;
public int actualGetIdCallCount;
Employee selectionObject;
AttributeAccessor overwrittenAccessor;
public ConformResultsWithPrimaryKeyExpressionTest(int testCase, boolean checkCacheByExactPrimaryKey) {
this.testCase = testCase;
this.checkCacheByExactPrimaryKey = checkCacheByExactPrimaryKey;
String modifier = null;
switch (testCase) {
case CASE_NEW:
modifier = "NEW";
break;
case CASE_DELETED:
modifier = "DELETED";
break;
case CASE_EXISTING:
modifier = "EXISTING";
break;
case CASE_NOTEXISTING:
modifier = "NOTEXISTING";
break;
}
if (shouldCheckCacheByExactPrimaryKey()) {
setName("ConformResultsWithExactPrimaryKeyExpressionTest:" + modifier);
} else {
setName("ConformResultsWithInexactPrimaryKeyExpressionTest:" + modifier);
}
}
@Override
public void buildConformQuery() {
conformedQuery = new ReadObjectQuery(Employee.class);
ExpressionBuilder emp = new ExpressionBuilder();
Expression exactPrimaryKeyExpression = null;
if (!getSession().getPlatform().isOracle()) {
exactPrimaryKeyExpression = emp.get("id").equal(selectionObject.getId());
} else {
exactPrimaryKeyExpression = emp.get("id").equal("" + selectionObject.getId());
}
if (shouldCheckCacheByExactPrimaryKey()) {
conformedQuery.setSelectionCriteria(exactPrimaryKeyExpression);
} else {
Expression inexactPrimaryKeyExpression = exactPrimaryKeyExpression.and(emp.get("firstName").equal(selectionObject.getFirstName()));
conformedQuery.setSelectionCriteria(inexactPrimaryKeyExpression);
}
conformedQuery.conformResultsInUnitOfWork();
}
public static Vector buildTests() {
Vector tests = new Vector(4);
tests.add(new ConformResultsWithPrimaryKeyExpressionTest(CASE_DELETED, true));
tests.add(new ConformResultsWithPrimaryKeyExpressionTest(CASE_EXISTING, true));
tests.add(new ConformResultsWithPrimaryKeyExpressionTest(CASE_NEW, true));
tests.add(new ConformResultsWithPrimaryKeyExpressionTest(CASE_NOTEXISTING, true));
tests.add(new ConformResultsWithPrimaryKeyExpressionTest(CASE_DELETED, false));
tests.add(new ConformResultsWithPrimaryKeyExpressionTest(CASE_EXISTING, false));
tests.add(new ConformResultsWithPrimaryKeyExpressionTest(CASE_NEW, false));
tests.add(new ConformResultsWithPrimaryKeyExpressionTest(CASE_NOTEXISTING, false));
return tests;
}
protected Employee findWorstCaseEmployee() {
Vector searchOrder = unitOfWork.getIdentityMapAccessor().getAllFromIdentityMap(null, Employee.class, null, null);
return (Employee)searchOrder.lastElement();
}
/**
* prepareTest method comment.
*/
@Override
public void prepareTest() {
ReadAllQuery query = new ReadAllQuery(Employee.class);
Vector employees = (Vector)getSession().executeQuery(query);
for (int i = 0; i < (employees.size() - 1); i++) {
unitOfWork.registerExistingObject(employees.elementAt(i));
}
Employee unregisteredEmployee = (Employee)employees.elementAt(employees.size() - 1);
// Further tests the not exists case when the query goes to the session.
getSession().getIdentityMapAccessor().removeFromIdentityMap(unregisteredEmployee);
int n = employees.size() - 1;
Employee newEmployee = new Employee();
newEmployee.setFirstName("Bobert");
newEmployee.setLastName("Schmit");
newEmployee.setId(new java.math.BigDecimal(45));
unitOfWork.registerNewObject(newEmployee);
Employee registeredEmployee = findWorstCaseEmployee();
switch (testCase) {
case CASE_NEW: {
selectionObject = newEmployee;
if (shouldCheckCacheByExactPrimaryKey()) {
expectedGetIdCallCount = 1;
} else {
expectedGetIdCallCount = n + 1;
}
break;
}
case CASE_DELETED: {
selectionObject = registeredEmployee;
unitOfWork.deleteObject(selectionObject);
if (shouldCheckCacheByExactPrimaryKey()) {
expectedGetIdCallCount = 0;
} else {
// S.M changed from 3 - 4 from session read refactoring. In the
// old code, we would not go to the database if we got a
// cache hit on the session cache. Now we do. Gray area
expectedGetIdCallCount = 3;
}
break;
}
case CASE_EXISTING: {
selectionObject = registeredEmployee;
if (shouldCheckCacheByExactPrimaryKey()) {
expectedGetIdCallCount = 0;
} else {
expectedGetIdCallCount = 1;
}
break;
}
case CASE_NOTEXISTING: {
selectionObject = unregisteredEmployee;
if (shouldCheckCacheByExactPrimaryKey()) {
// S.M. This went from 5 calls to 4, which is good.
// When checking the one new object + registration +
// building clone + building backup clone.
expectedGetIdCallCount = 3;
} else {
expectedGetIdCallCount = n + 4;
}
break;
}
}
}
@Override
public void setup() {
// Change how the primary key attribute 'id' in Employee is accessed.
// Now everytime TopLink extracts the primary key from an Employee object it
// will call Employee.getId() reflectively, which will update a count.
DatabaseMapping mapping = getSession().getDescriptor(Employee.class).getMappingForAttributeName("id");
overwrittenAccessor = mapping.getAttributeAccessor();
mapping.setGetMethodName("getId");
mapping.setSetMethodName("setId");
mapping.getAttributeAccessor().initializeAttributes(Employee.class);
super.setup();
}
@Override
public void reset() {
DatabaseMapping mapping = getSession().getDescriptor(Employee.class).getMappingForAttributeName("id");
mapping.setAttributeAccessor(overwrittenAccessor);
super.reset();
}
/**
* Override test to count the calls to Employee.getId just for the query.
*/
@Override
public void test() {
int initialCount = Employee.getGetIdCallCount();
result = unitOfWork.executeQuery(conformedQuery);
actualGetIdCallCount = Employee.getGetIdCallCount() - initialCount;
unitOfWork.release();
}
/**
* verify method comment.
*/
@Override
public void verify() {
if ((result == null) && (testCase != CASE_DELETED)) {
throw new TestErrorException("object existed in unit of work but not returned in query.");
}
if ((result != null) && (testCase == CASE_DELETED)) {
throw new TestErrorException("object was deleted in unit of work but returned in query.");
}
if (actualGetIdCallCount != expectedGetIdCallCount) {
throw new TestErrorException("The performance of find by primary key has changed. Expected calls to getId: " + expectedGetIdCallCount + ". Actual calls: " + actualGetIdCallCount + ". As long as the algorithmic complexity does not change (linear/constant) this should be ok.");
}
}
public boolean shouldCheckCacheByExactPrimaryKey() {
return checkCacheByExactPrimaryKey;
}
}