/******************************************************************************* * Copyright (c) 1998, 2015 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; } }