| /* |
| * 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; |
| } |
| } |