/******************************************************************************* | |
* Copyright (c) 1998, 2013 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 v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* 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); | |
} | |
} | |
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()) { | |
((ReadObjectQuery)conformedQuery).setSelectionCriteria(exactPrimaryKeyExpression); | |
} else { | |
Expression inexactPrimaryKeyExpression = exactPrimaryKeyExpression.and(emp.get("firstName").equal(selectionObject.getFirstName())); | |
((ReadObjectQuery)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. | |
*/ | |
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; | |
} | |
} | |
} | |
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(); | |
} | |
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. | |
*/ | |
public void test() { | |
int initialCount = Employee.getGetIdCallCount(); | |
result = unitOfWork.executeQuery(conformedQuery); | |
actualGetIdCallCount = Employee.getGetIdCallCount() - initialCount; | |
unitOfWork.release(); | |
} | |
/** | |
* verify method comment. | |
*/ | |
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; | |
} | |
} |