/******************************************************************************* | |
* 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 | |
* 04/01/2011-2.3 Guy Pelletier | |
* - 337323: Multi-tenant with shared schema support (part 2) | |
* 09/09/2011-2.3.1 Guy Pelletier | |
* - 356197: Add new VPD type to MultitenantType | |
******************************************************************************/ | |
package org.eclipse.persistence.queries; | |
import java.util.*; | |
import java.sql.*; | |
import org.eclipse.persistence.internal.databaseaccess.*; | |
import org.eclipse.persistence.internal.helper.*; | |
import org.eclipse.persistence.internal.descriptors.ObjectBuilder; | |
import org.eclipse.persistence.internal.queries.*; | |
import org.eclipse.persistence.internal.sessions.remote.*; | |
import org.eclipse.persistence.internal.sessions.AbstractRecord; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.internal.sessions.ResultSetRecord; | |
import org.eclipse.persistence.internal.sessions.SimpleResultSetRecord; | |
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; | |
import org.eclipse.persistence.exceptions.*; | |
import org.eclipse.persistence.expressions.*; | |
import org.eclipse.persistence.sessions.DatabaseRecord; | |
import org.eclipse.persistence.sessions.SessionProfiler; | |
import org.eclipse.persistence.sessions.remote.*; | |
import org.eclipse.persistence.tools.profiler.QueryMonitor; | |
/** | |
* <p><b>Purpose</b>: | |
* Concrete class for all read queries involving a collection of objects. | |
* <p> | |
* <p><b>Responsibilities</b>: | |
* Return a container of the objects generated by the query. | |
* Implements the inheritance feature when dealing with abstract descriptors | |
* | |
* @author Yvon Lavoie | |
* @since TOPLink/Java 1.0 | |
*/ | |
public class ReadAllQuery extends ObjectLevelReadQuery { | |
/** Used for collection and stream support. */ | |
protected ContainerPolicy containerPolicy; | |
/** Used for Oracle HierarchicalQuery support */ | |
protected Expression startWithExpression; | |
protected Expression connectByExpression; | |
protected List<Expression> orderSiblingsByExpressions; | |
/** | |
* PUBLIC: | |
* Return a new read all query. | |
* A reference class must be specified before execution. | |
* It is better to provide the class and expression builder on construction to ensure a single expression builder is used. | |
* If no selection criteria is specified this will read all objects of the class from the database. | |
*/ | |
public ReadAllQuery() { | |
super(); | |
setContainerPolicy(ContainerPolicy.buildDefaultPolicy()); | |
} | |
/** | |
* PUBLIC: | |
* Return a new read all query. | |
* It is better to provide the class and expression builder on construction to ensure a single expression builder is used. | |
* If no selection criteria is specified this will read all objects of the class from the database. | |
*/ | |
public ReadAllQuery(Class classToRead) { | |
this(); | |
this.referenceClass = classToRead; | |
} | |
/** | |
* PUBLIC: | |
* Return a new read all query for the class and the selection criteria. | |
*/ | |
public ReadAllQuery(Class classToRead, Expression selectionCriteria) { | |
this(); | |
this.referenceClass = classToRead; | |
setSelectionCriteria(selectionCriteria); | |
} | |
/** | |
* PUBLIC: | |
* Return a new read all query for the class. | |
* The expression builder must be used for all associated expressions used with the query. | |
*/ | |
public ReadAllQuery(Class classToRead, ExpressionBuilder builder) { | |
this(); | |
this.defaultBuilder = builder; | |
this.referenceClass = classToRead; | |
} | |
/** | |
* PUBLIC: | |
* Return a new read all query. | |
* The call represents a database interaction such as SQL, Stored Procedure. | |
*/ | |
public ReadAllQuery(Class classToRead, Call call) { | |
this(); | |
this.referenceClass = classToRead; | |
setCall(call); | |
} | |
/** | |
* PUBLIC: | |
* Return a query by example query to find all objects matching the attributes of the example object. | |
*/ | |
public ReadAllQuery(Object exampleObject, QueryByExamplePolicy policy) { | |
this(); | |
setExampleObject(exampleObject); | |
setQueryByExamplePolicy(policy); | |
} | |
/** | |
* PUBLIC: | |
* The expression builder should be provide on creation to ensure only one is used. | |
*/ | |
public ReadAllQuery(ExpressionBuilder builder) { | |
this(); | |
this.defaultBuilder = builder; | |
} | |
/** | |
* PUBLIC: | |
* Create a read all query with the database call. | |
*/ | |
public ReadAllQuery(Call call) { | |
this(); | |
setCall(call); | |
} | |
/** | |
* PUBLIC: | |
* Order the query results by the object's attribute or query key name. | |
*/ | |
public void addAscendingOrdering(String queryKeyName) { | |
addOrdering(getExpressionBuilder().get(queryKeyName).ascending()); | |
} | |
/** | |
* INTERNAL: | |
* <P> This method is called by the object builder when building an original. | |
* It will cause the original to be cached in the query results if the query | |
* is set to do so. | |
*/ | |
@Override | |
public void cacheResult(Object unwrappedOriginal) { | |
Collection container = (Collection)getTemporaryCachedQueryResults(); | |
if (container == null) { | |
container = (Collection)getContainerPolicy().containerInstance(); | |
setTemporaryCachedQueryResults(container); | |
} | |
getContainerPolicy().addInto(unwrappedOriginal, container, getSession()); | |
} | |
/** | |
* INTERNAL: | |
* The cache check is done before the prepare as a hit will not require the work to be done. | |
*/ | |
@Override | |
protected Object checkEarlyReturnLocal(AbstractSession session, AbstractRecord translationRow) { | |
// Check for in-memory only query. | |
if (shouldCheckCacheOnly()) { | |
// assert !isReportQuery(); | |
if (shouldUseWrapperPolicy()) { | |
getContainerPolicy().setElementDescriptor(this.descriptor); | |
} | |
// PERF: Fixed to not query each unit of work cache (is not conforming), | |
// avoid hashtable and primary key indexing. | |
// At some point we may need to support some kind of in-memory with conforming option, | |
// but we do not currently allow this. | |
AbstractSession rootSession = session; | |
while (rootSession.isUnitOfWork()) { | |
rootSession = ((UnitOfWorkImpl)rootSession).getParent(); | |
} | |
Vector allCachedVector = rootSession.getIdentityMapAccessor().getAllFromIdentityMap(getSelectionCriteria(), getReferenceClass(), translationRow, getInMemoryQueryIndirectionPolicyState(), false); | |
// Must ensure that all of the objects returned are correctly registered in the unit of work. | |
if (session.isUnitOfWork()) { | |
allCachedVector = ((UnitOfWorkImpl)session).registerAllObjects(allCachedVector); | |
} | |
this.isCacheCheckComplete = true; | |
return getContainerPolicy().buildContainerFromVector(allCachedVector, session); | |
} else { | |
return null; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Check to see if a custom query should be used for this query. | |
* This is done before the query is copied and prepared/executed. | |
* null means there is none. | |
*/ | |
@Override | |
protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) { | |
checkDescriptor(session); | |
// Check if user defined a custom query. | |
if (isCustomQueryUsed() == null) { | |
setIsCustomQueryUsed((!isUserDefined()) && isExpressionQuery() && (getSelectionCriteria() == null) && isDefaultPropertiesQuery() && (!hasOrderByExpressions()) && (this.descriptor.getQueryManager().hasReadAllQuery())); | |
} | |
if (isCustomQueryUsed().booleanValue()) { | |
ReadAllQuery customQuery = this.descriptor.getQueryManager().getReadAllQuery(); | |
if (this.accessors != null) { | |
customQuery = (ReadAllQuery) customQuery.clone(); | |
customQuery.setIsExecutionClone(true); | |
customQuery.setAccessors(this.accessors); | |
} | |
return customQuery; | |
} else { | |
return null; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Clone the query. | |
*/ | |
@Override | |
public Object clone() { | |
ReadAllQuery cloneQuery = (ReadAllQuery)super.clone(); | |
// Don't use setters as that will trigger unprepare | |
cloneQuery.containerPolicy = this.containerPolicy.clone(cloneQuery); | |
return cloneQuery; | |
} | |
/** | |
* INTERNAL: | |
* Conform the result if specified. | |
*/ | |
protected Object conformResult(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) { | |
if (getSelectionCriteria() != null) { | |
ExpressionBuilder builder = getSelectionCriteria().getBuilder(); | |
builder.setSession(unitOfWork.getRootSession(null)); | |
builder.setQueryClass(getReferenceClass()); | |
} | |
// If the query is redirected then the collection returned might no longer | |
// correspond to the original container policy. CR#2342-S.M. | |
ContainerPolicy cp; | |
if (getRedirector() != null) { | |
cp = ContainerPolicy.buildPolicyFor(result.getClass()); | |
} else { | |
cp = getContainerPolicy(); | |
} | |
// This code is now a great deal different... For one, registration is done | |
// as part of conforming. Also, this should only be called if one actually | |
// is conforming. | |
// First scan the UnitOfWork for conforming instances. | |
// This will walk through the entire cache of registered objects. | |
// Let p be objects from result not in the cache. | |
// Let c be objects from cache. | |
// Presently p intersect c = empty set, but later p subset c. | |
// By checking cache now doesConform will be called p fewer times. | |
Map<Object, Object> indexedInterimResult = unitOfWork.scanForConformingInstances(getSelectionCriteria(), getReferenceClass(), arguments, this); | |
Cursor cursor = null; | |
// In the case of cursors just conform/register the initially read collection. | |
if (cp.isCursorPolicy()) { | |
cursor = (Cursor)result; | |
cp = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class); | |
// In nested UnitOfWork session might have been session of the parent. | |
cursor.setSession(unitOfWork); | |
result = cursor.getObjectCollection(); | |
// for later incremental conforming... | |
cursor.setInitiallyConformingIndex(indexedInterimResult); | |
cursor.setSelectionCriteriaClone(getSelectionCriteria()); | |
cursor.setTranslationRow(arguments); | |
} | |
// Now conform the result from the database. | |
// Remove any deleted or changed objects that no longer conform. | |
// Deletes will only work for simple queries, queries with or's or anyof's may not return | |
// correct results when untriggered indirection is in the model. | |
List fromDatabase = null; | |
// When building directly from rows, one of the performance benefits | |
// is that we no longer have to wrap and then unwrap the originals. | |
// result is just a vector, not a container of wrapped originals. | |
if (buildDirectlyFromRows) { | |
List<AbstractRecord> rows = (List<AbstractRecord>)result; | |
int size = rows.size(); | |
fromDatabase = new ArrayList(size); | |
for (int index = 0; index < size; index++) { | |
AbstractRecord row = rows.get(index); | |
// null is placed in the row collection for 1-m joining to filter duplicate rows. | |
if (row != null) { | |
Object clone = conformIndividualResult(buildObject(row), unitOfWork, arguments, getSelectionCriteria(), indexedInterimResult); | |
if (clone != null) { | |
fromDatabase.add(clone); | |
} | |
} | |
} | |
} else { | |
fromDatabase = new ArrayList(cp.sizeFor(result)); | |
AbstractSession sessionToUse = unitOfWork.getParent(); | |
for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) { | |
Object object = cp.next(iter, sessionToUse); | |
Object clone = conformIndividualResult(registerIndividualResult(object, null, unitOfWork, null, null), unitOfWork, arguments, getSelectionCriteria(), indexedInterimResult); | |
if (clone != null) { | |
fromDatabase.add(clone); | |
} | |
} | |
} | |
// Now add the unwrapped conforming instances into an appropriate container. | |
// Wrapping is done automatically. | |
// Make sure a vector of exactly the right size is returned. | |
Object conformedResult = cp.containerInstance(indexedInterimResult.size() + fromDatabase.size()); | |
for (Iterator enumtr = indexedInterimResult.values().iterator(); enumtr.hasNext();) { | |
Object eachClone = enumtr.next(); | |
cp.addInto(eachClone, conformedResult, unitOfWork); | |
} | |
int size = fromDatabase.size(); | |
for (int index = 0; index < size; index++) { | |
Object eachClone = fromDatabase.get(index); | |
cp.addInto(eachClone, conformedResult, unitOfWork); | |
} | |
if (cursor != null) { | |
cursor.setObjectCollection((List)conformedResult); | |
// For nested UOW must copy all in object collection to | |
// initiallyConformingIndex, as some of these could have been from | |
// the parent UnitOfWork. | |
if (unitOfWork.isNestedUnitOfWork()) { | |
for (Object clone : cursor.getObjectCollection()) { | |
indexedInterimResult.put(clone, clone); | |
} | |
} | |
return cursor; | |
} else { | |
return conformedResult; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Execute the query. If there are cached results return those. | |
* This must override the super to support result caching. | |
* | |
* @param session - the session in which the receiver will be executed. | |
* @return An object or vector, the result of executing the query. | |
* @exception DatabaseException - an error has occurred on the database | |
*/ | |
@Override | |
public Object execute(AbstractSession session, AbstractRecord row) throws DatabaseException { | |
if (shouldCacheQueryResults()) { | |
if (getContainerPolicy().overridesRead()) { | |
throw QueryException.cannotCacheCursorResultsOnQuery(this); | |
} | |
if (shouldConformResultsInUnitOfWork()) { | |
throw QueryException.cannotConformAndCacheQueryResults(this); | |
} | |
if (isPrepared()) {// only prepared queries can have cached results. | |
Object queryResults = getQueryResults(session, row, true); | |
if (queryResults != null) { | |
if (QueryMonitor.shouldMonitor()) { | |
QueryMonitor.incrementReadAllHits(this); | |
} | |
session.incrementProfile(SessionProfiler.CacheHits, this); | |
// bug6138532 - check for "cached no results" (InvalidObject singleton) in query | |
// results, and return an empty container instance as configured | |
if (queryResults == InvalidObject.instance) { | |
return getContainerPolicy().containerInstance(0); | |
} | |
Collection results = (Collection)queryResults; | |
if (session.isUnitOfWork()) { | |
ContainerPolicy policy = getContainerPolicy(); | |
Object resultCollection = policy.containerInstance(results.size()); | |
Object iterator = policy.iteratorFor(results); | |
while (policy.hasNext(iterator)) { | |
Object result = ((UnitOfWorkImpl)session).registerExistingObject(policy.next(iterator, session), this.descriptor, null, true); | |
policy.addInto(result, resultCollection, session); | |
} | |
return resultCollection; | |
} | |
return results; | |
} | |
} | |
session.incrementProfile(SessionProfiler.CacheMisses, this); | |
} | |
if (QueryMonitor.shouldMonitor()) { | |
QueryMonitor.incrementReadAllMisses(this); | |
} | |
return super.execute(session, row); | |
} | |
/** | |
* INTERNAL: | |
* Execute the query. | |
* Get the rows and build the object from the rows. | |
* @exception DatabaseException - an error has occurred on the database | |
* @return java.lang.Object collection of objects resulting from execution of query. | |
*/ | |
@Override | |
protected Object executeObjectLevelReadQuery() throws DatabaseException { | |
Object result = null; | |
if (this.containerPolicy.overridesRead()) { | |
this.executionTime = System.currentTimeMillis(); | |
return this.containerPolicy.execute(); | |
} | |
if (this.descriptor.isDescriptorForInterface()) { | |
Object returnValue = this.descriptor.getInterfacePolicy().selectAllObjectsUsingMultipleTableSubclassRead(this); | |
this.executionTime = System.currentTimeMillis(); | |
return returnValue; | |
} | |
if (this.descriptor.hasTablePerClassPolicy() && this.descriptor.isAbstract()) { | |
result = this.containerPolicy.containerInstance(); | |
} else { | |
Object sopObject = getTranslationRow().getSopObject(); | |
boolean useOptimization = false; | |
if (sopObject == null) { | |
useOptimization = usesResultSetAccessOptimization(); | |
} | |
if (useOptimization) { | |
DatabaseCall call = ((DatasourceCallQueryMechanism)this.queryMechanism).selectResultSet(); | |
this.executionTime = System.currentTimeMillis(); | |
Statement statement = call.getStatement(); | |
ResultSet resultSet = call.getResult(); | |
DatabaseAccessor dbAccessor = (DatabaseAccessor)getAccessor(); | |
boolean exceptionOccured = false; | |
try { | |
if (this.session.isUnitOfWork()) { | |
result = registerResultSetInUnitOfWork(resultSet, call.getFields(), call.getFieldsArray(), (UnitOfWorkImpl)this.session, this.translationRow); | |
} else { | |
result = this.containerPolicy.containerInstance(); | |
this.descriptor.getObjectBuilder().buildObjectsFromResultSetInto(this, resultSet, call.getFields(), call.getFieldsArray(), result); | |
} | |
} catch (SQLException exception) { | |
exceptionOccured = true; | |
DatabaseException commException = dbAccessor.processExceptionForCommError(this.session, exception, call); | |
if (commException != null) { | |
throw commException; | |
} | |
throw DatabaseException.sqlException(exception, call, dbAccessor, this.session, false); | |
} finally { | |
try { | |
if (resultSet != null) { | |
resultSet.close(); | |
} | |
if (dbAccessor != null) { | |
if (statement != null) { | |
dbAccessor.releaseStatement(statement, call.getSQLString(), call, this.session); | |
} | |
} | |
if (call.hasAllocatedConnection()) { | |
getExecutionSession().releaseConnectionAfterCall(this); | |
} | |
} catch (RuntimeException cleanupException) { | |
if (!exceptionOccured) { | |
throw cleanupException; | |
} | |
} catch (SQLException cleanupSQLException) { | |
if (!exceptionOccured) { | |
throw DatabaseException.sqlException(cleanupSQLException, call, dbAccessor, this.session, false); | |
} | |
} | |
} | |
} else { | |
List<AbstractRecord> rows; | |
if (sopObject != null) { | |
Object valuesIterator = this.containerPolicy.iteratorFor(getTranslationRow().getSopObject()); | |
int size = this.containerPolicy.sizeFor(sopObject); | |
rows = new ArrayList<AbstractRecord>(size); | |
while (this.containerPolicy.hasNext(valuesIterator)) { | |
Object memberSopObject = this.containerPolicy.next(valuesIterator, this.session); | |
DatabaseRecord memberRow = new DatabaseRecord(0); | |
memberRow.setSopObject(memberSopObject); | |
rows.add(memberRow); | |
} | |
this.executionTime = System.currentTimeMillis(); | |
} else { | |
rows = getQueryMechanism().selectAllRows(); | |
this.executionTime = System.currentTimeMillis(); | |
// If using 1-m joins, must set all rows. | |
if (hasJoining() && this.joinedAttributeManager.isToManyJoin()) { | |
this.joinedAttributeManager.setDataResults(rows, this.session); | |
} | |
// Batch fetching in IN requires access to the rows to build the id array. | |
if ((this.batchFetchPolicy != null) && this.batchFetchPolicy.isIN()) { | |
this.batchFetchPolicy.setDataResults(rows); | |
} | |
} | |
if (this.session.isUnitOfWork()) { | |
result = registerResultInUnitOfWork(rows, (UnitOfWorkImpl)this.session, this.translationRow, true);// | |
} else { | |
if (rows instanceof ThreadCursoredList) { | |
result = this.containerPolicy.containerInstance(); | |
} else { | |
result = this.containerPolicy.containerInstance(rows.size()); | |
} | |
this.descriptor.getObjectBuilder().buildObjectsInto(this, rows, result); | |
} | |
if (sopObject != null) { | |
if (!this.descriptor.getObjectBuilder().isSimple()) { | |
// remove sopObject so it's not stuck in any value holder. | |
for (AbstractRecord row : rows) { | |
row.setSopObject(null); | |
} | |
} | |
} else { | |
if (this.shouldIncludeData) { | |
ComplexQueryResult complexResult = new ComplexQueryResult(); | |
complexResult.setResult(result); | |
complexResult.setData(rows); | |
result = complexResult; | |
} | |
} | |
} | |
} | |
// Add the other (already registered) results and return them. | |
if (this.descriptor.hasTablePerClassPolicy()) { | |
result = this.containerPolicy.concatenateContainers( | |
result, this.descriptor.getTablePerClassPolicy().selectAllObjectsUsingMultipleTableSubclassRead(this), this.session); | |
} | |
// If the results were empty, then ensure they get cached still. | |
if (shouldCacheQueryResults() && this.containerPolicy.isEmpty(result)) { | |
this.temporaryCachedQueryResults = InvalidObject.instance(); | |
} | |
return result; | |
} | |
/** | |
* INTERNAL: | |
* Execute the query building the objects directly from the database result-set. | |
* @exception DatabaseException - an error has occurred on the database | |
* @return an ArrayList of the resulting objects. | |
*/ | |
@Override | |
protected Object executeObjectLevelReadQueryFromResultSet() throws DatabaseException { | |
AbstractSession session = this.session; | |
DatabasePlatform platform = session.getPlatform(); | |
DatabaseCall call = ((DatasourceCallQueryMechanism)this.queryMechanism).selectResultSet(); | |
Statement statement = call.getStatement(); | |
ResultSet resultSet = call.getResult(); | |
DatabaseAccessor accessor = (DatabaseAccessor)getAccessor(); | |
boolean exceptionOccured = false; | |
try { | |
ResultSetMetaData metaData = resultSet.getMetaData(); | |
List results = new ArrayList(); | |
ObjectBuilder builder = this.descriptor.getObjectBuilder(); | |
while (resultSet.next()) { | |
results.add(builder.buildObjectFromResultSet(this, this.joinedAttributeManager, resultSet, session, accessor, metaData, platform, call.getFields(), call.getFieldsArray())); | |
} | |
return results; | |
} catch (SQLException exception) { | |
exceptionOccured = true; | |
DatabaseException commException = accessor.processExceptionForCommError(session, exception, call); | |
if (commException != null) { | |
throw commException; | |
} | |
throw DatabaseException.sqlException(exception, call, accessor, session, false); | |
} finally { | |
try { | |
if (resultSet != null) { | |
resultSet.close(); | |
} | |
if (statement != null) { | |
accessor.releaseStatement(statement, call.getSQLString(), call, session); | |
} | |
if (accessor != null) { | |
session.releaseReadConnection(accessor); | |
} | |
} catch (SQLException exception) { | |
if (!exceptionOccured) { | |
//in the case of an external connection pool the connection may be null after the statement release | |
// if it is null we will be unable to check the connection for a comm error and | |
//therefore must return as if it was not a comm error. | |
DatabaseException commException = accessor.processExceptionForCommError(session, exception, call); | |
if (commException != null) { | |
throw commException; | |
} | |
throw DatabaseException.sqlException(exception, call, accessor, session, false); | |
} | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Extract the correct query result from the transporter. | |
*/ | |
@Override | |
public Object extractRemoteResult(Transporter transporter) { | |
return ((DistributedSession)getSession()).getObjectsCorrespondingToAll(transporter.getObject(), transporter.getObjectDescriptors(), new IdentityHashMap(), this, getContainerPolicy()); | |
} | |
/** | |
* INTERNAL: | |
* Return the query's container policy. | |
*/ | |
public ContainerPolicy getContainerPolicy() { | |
return containerPolicy; | |
} | |
/** | |
* INTERNAL: | |
* Returns the specific default redirector for this query type. There are numerous default query redirectors. | |
* See ClassDescriptor for their types. | |
*/ | |
@Override | |
protected QueryRedirector getDefaultRedirector() { | |
return descriptor.getDefaultReadAllQueryRedirector(); | |
} | |
/** | |
* PUBLIC: | |
* @return Expression - the start with expression used to generated the hierarchical query clause in | |
* Oracle | |
*/ | |
public Expression getStartWithExpression() { | |
return startWithExpression; | |
} | |
/** | |
* PUBLIC: | |
* @return Expression - the connect by expression used to generate the hierarchical query caluse in Oracle | |
*/ | |
public Expression getConnectByExpression() { | |
return connectByExpression; | |
} | |
/** | |
* PUBLIC: | |
* @return List<Expression> - the ordering expressions used to generate the hierarchical query clause in Oracle | |
*/ | |
public List<Expression> getOrderSiblingsByExpressions() { | |
return orderSiblingsByExpressions; | |
} | |
/** | |
* INTERNAL: | |
* Verify that we have hierarchical query expressions | |
*/ | |
public boolean hasHierarchicalExpressions() { | |
return ((this.startWithExpression != null) || (this.connectByExpression != null) || (this.orderSiblingsByExpressions != null)); | |
} | |
/** | |
* INTERNAL: | |
* Return true if the query uses default properties. | |
* This is used to determine if this query is cacheable. | |
* i.e. does not use any properties that may conflict with another query | |
* with the same JPQL or selection criteria. | |
*/ | |
@Override | |
public boolean isDefaultPropertiesQuery() { | |
return super.isDefaultPropertiesQuery() | |
&& (!hasBatchReadAttributes()) | |
&& (!hasHierarchicalExpressions()) | |
&& (!this.containerPolicy.isCursorPolicy()); | |
} | |
/** | |
* INTERNAL: | |
* Return if the query is equal to the other. | |
* This is used to allow dynamic expression query SQL to be cached. | |
*/ | |
@Override | |
public boolean equals(Object object) { | |
if (this == object) { | |
return true; | |
} | |
if (!super.equals(object)) { | |
return false; | |
} | |
ReadAllQuery query = (ReadAllQuery) object; | |
if (!this.containerPolicy.equals(query.containerPolicy)) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* PUBLIC: | |
* Return if this is a read all query. | |
*/ | |
@Override | |
public boolean isReadAllQuery() { | |
return true; | |
} | |
/** | |
* INTERNAL: | |
* Prepare the receiver for execution in a session. | |
*/ | |
@Override | |
protected void prepare() throws QueryException { | |
if ((!isReportQuery()) && prepareFromCachedQuery()) { | |
return; | |
} | |
super.prepare(); | |
this.containerPolicy.prepare(this, getSession()); | |
if (hasJoining() && isExpressionQuery()) { | |
// 1-m join fetching with pagination requires an order by. | |
if (this.joinedAttributeManager.isToManyJoin() | |
&& ((this.maxRows > 0) || (this.firstResult > 0) || this.containerPolicy.isCursorPolicy())) { | |
if (!hasOrderByExpressions()) { | |
for (DatabaseField primaryKey : this.descriptor.getPrimaryKeyFields()) { | |
addOrdering(getExpressionBuilder().getField(primaryKey)); | |
} | |
} | |
} | |
} | |
if (this.containerPolicy.overridesRead()) { | |
return; | |
} | |
if (this.descriptor.isDescriptorForInterface()) { | |
return; | |
} | |
prepareSelectAllRows(); | |
if (!isReportQuery()) { | |
// should be called after prepareSelectRow so that the call knows whether it returns ResultSet | |
prepareResultSetAccessOptimization(); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Prepare the query from the prepared query. | |
* This allows a dynamic query to prepare itself directly from a prepared query instance. | |
* This is used in the JPQL parse cache to allow preparsed queries to be used to prepare | |
* dynamic queries. | |
* This only copies over properties that are configured through JPQL. | |
*/ | |
@Override | |
public void prepareFromQuery(DatabaseQuery query) { | |
super.prepareFromQuery(query); | |
if (query.isReadAllQuery()) { | |
ReadAllQuery readQuery = (ReadAllQuery)query; | |
this.containerPolicy = readQuery.containerPolicy; | |
if (readQuery.hasHierarchicalExpressions()) { | |
this.orderSiblingsByExpressions = readQuery.orderSiblingsByExpressions; | |
this.connectByExpression = readQuery.connectByExpression; | |
this.startWithExpression = readQuery.startWithExpression; | |
} | |
} | |
} | |
/** | |
* INTERNAL: | |
* Set the properties needed to be cascaded into the custom query. | |
*/ | |
@Override | |
protected void prepareCustomQuery(DatabaseQuery customQuery) { | |
super.prepareCustomQuery(customQuery); | |
ReadAllQuery customReadQuery = (ReadAllQuery)customQuery; | |
customReadQuery.containerPolicy = this.containerPolicy; | |
customReadQuery.cascadePolicy = this.cascadePolicy; | |
customReadQuery.shouldRefreshIdentityMapResult = this.shouldRefreshIdentityMapResult; | |
customReadQuery.shouldMaintainCache = this.shouldMaintainCache; | |
customReadQuery.shouldUseWrapperPolicy = this.shouldUseWrapperPolicy; | |
} | |
/** | |
* INTERNAL: | |
* Prepare the receiver for execution in a session. | |
*/ | |
@Override | |
public void prepareForExecution() throws QueryException { | |
super.prepareForExecution(); | |
this.containerPolicy.prepareForExecution(); | |
// Modifying the translation row here will modify it on the original | |
// query which is not good. So we have to clone the translation row if | |
// we are going to append tenant discriminator fields to it. | |
if (descriptor.hasMultitenantPolicy()) { | |
translationRow = translationRow.clone(); | |
descriptor.getMultitenantPolicy().addFieldsToRow(translationRow, getSession()); | |
} | |
} | |
/** | |
* INTERNAL: | |
* Prepare the mechanism. | |
*/ | |
protected void prepareSelectAllRows() { | |
getQueryMechanism().prepareSelectAllRows(); | |
} | |
/** | |
* INTERNAL: | |
* All objects queried via a UnitOfWork get registered here. If the query | |
* went to the database. | |
* <p> | |
* Involves registering the query result individually and in totality, and | |
* hence refreshing / conforming is done here. | |
* | |
* @param result may be collection (read all) or an object (read one), | |
* or even a cursor. If in transaction the shared cache will | |
* be bypassed, meaning the result may not be originals from the parent | |
* but raw database rows. | |
* @param unitOfWork the unitOfWork the result is being registered in. | |
* @param arguments the original arguments/parameters passed to the query | |
* execution. Used by conforming | |
* @param buildDirectlyFromRows If in transaction must construct | |
* a registered result from raw database rows. | |
* | |
* @return the final (conformed, refreshed, wrapped) UnitOfWork query result | |
*/ | |
@Override | |
public Object registerResultInUnitOfWork(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) { | |
// For bug 2612366: Conforming results in UOW extremely slow. | |
// Replacing results with registered versions in the UOW is a part of | |
// conforming and is now done while conforming to maximize performance. | |
if (unitOfWork.hasCloneMapping() // PERF: Avoid conforming empty uow. | |
&& (shouldConformResultsInUnitOfWork() || this.descriptor.shouldAlwaysConformResultsInUnitOfWork())) { | |
return conformResult(result, unitOfWork, arguments, buildDirectlyFromRows); | |
} | |
// When building directly from rows, one of the performance benefits | |
// is that we no longer have to wrap and then unwrap the originals. | |
// result is just a vector, not a collection of wrapped originals. | |
// Also for cursors the initial connection is automatically registered. | |
if (buildDirectlyFromRows) { | |
List<AbstractRecord> rows = (List<AbstractRecord>)result; | |
ContainerPolicy cp = this.containerPolicy; | |
int size = rows.size(); | |
Object clones = cp.containerInstance(size); | |
if(cp.shouldAddAll()) { | |
List clonesIn = new ArrayList(size); | |
List<AbstractRecord> rowsIn = new ArrayList(size); | |
for (int index = 0; index < size; index++) { | |
AbstractRecord row = rows.get(index); | |
// null is placed in the row collection for 1-m joining to filter duplicate rows. | |
if (row != null) { | |
Object clone = buildObject(row); | |
clonesIn.add(clone); | |
rowsIn.add(row); | |
} | |
} | |
cp.addAll(clonesIn, clones, unitOfWork, rowsIn, this, null, true); | |
} else { | |
boolean quickAdd = (clones instanceof Collection) && !this.descriptor.getObjectBuilder().hasWrapperPolicy(); | |
if (this.descriptor.getCachePolicy().shouldPrefetchCacheKeys() | |
&& shouldMaintainCache() | |
&& ! shouldRetrieveBypassCache() | |
&& ((!(unitOfWork.hasCommitManager() && unitOfWork.getCommitManager().isActive()) | |
&& ! unitOfWork.wasTransactionBegunPrematurely() | |
&& !this.descriptor.getCachePolicy().shouldIsolateObjectsInUnitOfWork() | |
&& ! this.descriptor.getCachePolicy().shouldIsolateProtectedObjectsInUnitOfWork()) | |
|| (unitOfWork.isClassReadOnly(this.getReferenceClass(), this.getDescriptor())))){ | |
Object[] pkList = new Object[size]; | |
for (int i = 0; i< size; ++i){ | |
pkList[i] = getDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(rows.get(i), session); | |
} | |
setPrefetchedCacheKeys(unitOfWork.getParentIdentityMapSession(this).getIdentityMapAccessorInstance().getAllCacheKeysFromIdentityMapWithEntityPK(pkList, descriptor)); | |
} | |
for (int index = 0; index < size; index++) { | |
AbstractRecord row = rows.get(index); | |
// null is placed in the row collection for 1-m joining to filter duplicate rows. | |
if (row != null) { | |
Object clone = buildObject(row); | |
if (quickAdd) { | |
((Collection)clones).add(clone); | |
} else { | |
cp.addInto(clone, clones, unitOfWork, row, this, null, true); | |
} | |
} | |
} | |
} | |
return clones; | |
} | |
ContainerPolicy cp; | |
Cursor cursor = null; | |
// If the query is redirected then the collection returned might no longer | |
// correspond to the original container policy. CR#2342-S.M. | |
if (getRedirector() != null) { | |
cp = ContainerPolicy.buildPolicyFor(result.getClass()); | |
} else { | |
cp = this.containerPolicy; | |
} | |
// In the case of cursors just register the initially read collection. | |
if (cp.isCursorPolicy()) { | |
cursor = (Cursor)result; | |
// In nested UnitOfWork session might have been session of the parent. | |
cursor.setSession(unitOfWork); | |
cp = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class); | |
result = cursor.getObjectCollection(); | |
} | |
Object clones = cp.containerInstance(cp.sizeFor(result)); | |
AbstractSession sessionToUse = unitOfWork.getParent(); | |
for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) { | |
Object object = cp.next(iter, sessionToUse); | |
Object clone = registerIndividualResult(object, null, unitOfWork, this.joinedAttributeManager, null); | |
cp.addInto(clone, clones, unitOfWork); | |
} | |
if (cursor != null) { | |
cursor.setObjectCollection((Vector)clones); | |
return cursor; | |
} else { | |
return clones; | |
} | |
} | |
/** | |
* INTERNAL: | |
* Version of the previous method for ResultSet optimization. | |
* | |
* @return the final (conformed, refreshed, wrapped) UnitOfWork query result | |
*/ | |
public Object registerResultSetInUnitOfWork(ResultSet resultSet, Vector fields, DatabaseField[] fieldsArray, UnitOfWorkImpl unitOfWork, AbstractRecord arguments) throws SQLException { | |
// TODO: add support for Conforming results in UOW - currently conforming in uow is not compatible with ResultSet optimization. | |
ContainerPolicy cp = this.containerPolicy; | |
Object clones = cp.containerInstance(); | |
ResultSetMetaData metaData = resultSet.getMetaData(); | |
boolean hasNext = resultSet.next(); | |
if (hasNext) { | |
// TODO: possibly add support for SortedListContainerPolicy (cp.shouldAddAll() == true) - this policy currently is not compatible with ResultSet optimization | |
boolean quickAdd = (clones instanceof Collection) && !this.descriptor.getObjectBuilder().hasWrapperPolicy(); | |
DatabaseAccessor dbAccessor = (DatabaseAccessor)getAccessor(); | |
boolean useSimple = this.descriptor.getObjectBuilder().isSimple(); | |
AbstractSession executionSession = getExecutionSession(); | |
DatabasePlatform platform = dbAccessor.getPlatform(); | |
boolean optimizeData = platform.shouldOptimizeDataConversion(); | |
if (useSimple) { | |
// None of the fields are relational - the row could be reused, just clear all the values. | |
SimpleResultSetRecord row = new SimpleResultSetRecord(fields, fieldsArray, resultSet, metaData, dbAccessor, executionSession, platform, optimizeData); | |
if (this.descriptor.isDescriptorTypeAggregate()) { | |
// Aggregate Collection may have an unmapped primary key referencing the owner, the corresponding field will not be used when the object is populated and therefore may not be cleared. | |
row.setShouldKeepValues(true); | |
} | |
while (hasNext) { | |
Object clone = buildObject(row); | |
if (quickAdd) { | |
((Collection)clones).add(clone); | |
} else { | |
// TODO: investigate is it possible to support MappedKeyMapPolicy - this policy currently is not compatible with ResultSet optimization | |
cp.addInto(clone, clones, unitOfWork); | |
} | |
row.reset(); | |
hasNext = resultSet.next(); | |
} | |
} else { | |
boolean shouldKeepRow = this.descriptor.getObjectBuilder().shouldKeepRow(); | |
while (hasNext) { | |
ResultSetRecord row = new ResultSetRecord(fields, fieldsArray, resultSet, metaData, dbAccessor, executionSession, platform, optimizeData); | |
Object clone = buildObject(row); | |
if (quickAdd) { | |
((Collection)clones).add(clone); | |
} else { | |
// TODO: investigate is it possible to support MappedKeyMapPolicy - this policy currently is not compatible with ResultSet optimization | |
cp.addInto(clone, clones, unitOfWork); | |
} | |
if (shouldKeepRow) { | |
if (row.hasResultSet()) { | |
// ResultSet has not been fully triggered - that means the cached object was used. | |
// Yet the row still may be cached in a value holder (see loadBatchReadAttributes and loadJoinedAttributes methods). | |
// Remove ResultSet to avoid attempt to trigger it (already closed) when pk or fk values (already extracted) accessed when the value holder is instantiated. | |
row.removeResultSet(); | |
} else { | |
row.removeNonIndirectionValues(); | |
} | |
} | |
hasNext = resultSet.next(); | |
} | |
} | |
} | |
return clones; | |
} | |
/** | |
* INTERNAL: | |
* Execute the query through remote session. | |
*/ | |
@Override | |
public Object remoteExecute() { | |
if (this.containerPolicy.overridesRead()) { | |
return this.containerPolicy.remoteExecute(); | |
} | |
Object cacheHit = checkEarlyReturn(this.session, this.translationRow); | |
if (cacheHit != null) { | |
return cacheHit; | |
} | |
return super.remoteExecute(); | |
} | |
/** | |
* INTERNAL: | |
* replace the value holders in the specified result object(s) | |
*/ | |
@Override | |
public Map replaceValueHoldersIn(Object object, RemoteSessionController controller) { | |
return controller.replaceValueHoldersInAll(object, getContainerPolicy()); | |
} | |
/** | |
* PUBLIC: | |
* Set the container policy. Used to support different containers | |
* (e.g. Collections, Maps). | |
*/ | |
public void setContainerPolicy(ContainerPolicy containerPolicy) { | |
// CR#... a container policy is always required, default is vector, | |
// required for deployment XML. | |
if (containerPolicy == null) { | |
return; | |
} | |
this.containerPolicy = containerPolicy; | |
setIsPrepared(false); | |
} | |
/** | |
* PUBLIC: | |
* Set the Hierarchical Query Clause for the query | |
* <p>Example: | |
* <p>Expression startWith = builder.get("id").equal(new Integer(100)); //can be any expression which identifies a set of employees | |
* <p>Expression connectBy = builder.get("managedEmployees"); //indicated the relationship that the hierarchy is based on, must be self-referential | |
* <p>Vector orderBy = new Vector(); | |
* <p>orderBy.addElement(builder.get("startDate")); | |
* <p>readAllQuery.setHierarchicalQueryClause(startWith, connectBy, orderBy); | |
* | |
* <p>This query would generate SQL like this: | |
* <p>SELECT * FROM EMPLOYEE START WITH ID=100 CONNECT BY PRIOR ID = MANAGER_ID ORDER SIBLINGS BY START_DATE | |
* | |
* @param startWith Describes the START WITH clause of the query - null if not needed | |
* @param connectBy This should be a query key expression which indicates an attribute who's mapping describes the hierarchy | |
* @param orderSiblingsExpressions Contains expressions which indicate fields to be included in the ORDER SIBLINGS BY clause - null if not required | |
*/ | |
public void setHierarchicalQueryClause(Expression startWith, Expression connectBy, List<Expression> orderSiblingsExpressions) { | |
this.startWithExpression = startWith; | |
this.connectByExpression = connectBy; | |
this.orderSiblingsByExpressions = orderSiblingsExpressions; | |
setIsPrepared(false); | |
} | |
/** | |
* PUBLIC: | |
* Configure the mapping to use an instance of the specified container class | |
* to hold the target objects. | |
* <p>jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface. | |
* <p>jdk1.1.x: The container class must be a subclass of Vector. | |
*/ | |
public void useCollectionClass(Class concreteClass) { | |
// Set container policy. | |
setContainerPolicy(ContainerPolicy.buildPolicyFor(concreteClass)); | |
} | |
/** | |
* PUBLIC: | |
* Use a CursoredStream as the result collection. | |
* The initial read size is 10 and page size is 5. | |
*/ | |
public void useCursoredStream() { | |
useCursoredStream(10, 5); | |
} | |
/** | |
* PUBLIC: | |
* Use a CursoredStream as the result collection. | |
* @param initialReadSize the initial number of objects to read | |
* @param pageSize the number of objects to read when more objects | |
* are needed from the database | |
*/ | |
public void useCursoredStream(int initialReadSize, int pageSize) { | |
setContainerPolicy(new CursoredStreamPolicy(this, initialReadSize, pageSize)); | |
} | |
/** | |
* PUBLIC: | |
* Use a CursoredStream as the result collection. | |
* @param initialReadSize the initial number of objects to read | |
* @param pageSize the number of objects to read when more objects | |
* are needed from the database | |
* @param sizeQuery a query that will return the size of the result set; | |
* this must be set if an expression is not used (i.e. custom SQL) | |
*/ | |
public void useCursoredStream(int initialReadSize, int pageSize, ValueReadQuery sizeQuery) { | |
setContainerPolicy(new CursoredStreamPolicy(this, initialReadSize, pageSize, sizeQuery)); | |
} | |
/** | |
* PUBLIC: | |
* Configure the query to use an instance of the specified container class | |
* to hold the result objects. The key used to index the value in the Map | |
* is the value returned by a call to the specified zero-argument method. | |
* The method must be implemented by the class (or a superclass) of the | |
* value to be inserted into the Map. | |
* <p>jdk1.2.x: The container class must implement (directly or indirectly) the Map interface. | |
* <p>jdk1.1.x: The container class must be a subclass of Hashtable. | |
* <p>The referenceClass must set before calling this method. | |
*/ | |
public void useMapClass(Class concreteClass, String methodName) { | |
// the reference class has to be specified before coming here | |
if (getReferenceClass() == null) { | |
throw QueryException.referenceClassMissing(this); | |
} | |
ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass); | |
policy.setKeyName(methodName, getReferenceClass().getName()); | |
setContainerPolicy(policy); | |
} | |
/** | |
* PUBLIC: | |
* Use a ScrollableCursor as the result collection. | |
*/ | |
public void useScrollableCursor() { | |
useScrollableCursor(10); | |
} | |
/** | |
* PUBLIC: | |
* Use a ScrollableCursor as the result collection. | |
* @param pageSize the number of elements to be read into a the cursor | |
* when more elements are needed from the database. | |
*/ | |
public void useScrollableCursor(int pageSize) { | |
setContainerPolicy(new ScrollableCursorPolicy(this, pageSize)); | |
} | |
/** | |
* PUBLIC: | |
* Use a ScrollableCursor as the result collection. | |
* @param policy the scrollable cursor policy allows for additional result set options. | |
* Example:<p> | |
* ScrollableCursorPolicy policy = new ScrollableCursorPolicy()<p> | |
* policy.setResultSetType(ScrollableCursorPolicy.TYPE_SCROLL_INSENSITIVE);<p> | |
* query.useScrollableCursor(policy);<p> | |
*/ | |
public void useScrollableCursor(ScrollableCursorPolicy policy) { | |
policy.setQuery(this); | |
setContainerPolicy(policy); | |
} | |
/** | |
* INTERNAL: | |
* Indicates whether the query can use ResultSet optimization. | |
* The method is called when the query is prepared, | |
* so it should refer only to the attributes that cannot be altered without re-preparing the query. | |
* If the query is a clone and the original has been already prepared | |
* this method will be called to set a (transient and therefore set to null) usesResultSetOptimization attribute. | |
*/ | |
@Override | |
public boolean supportsResultSetAccessOptimizationOnPrepare() { | |
if (!super.supportsResultSetAccessOptimizationOnPrepare()) { | |
return false; | |
} | |
return !this.containerPolicy.isMappedKeyMapPolicy() && !this.containerPolicy.shouldAddAll() && // MappedKeyMapPolicy requires the whole row, OrderListContainerPolicy requires all rows. | |
!this.descriptor.shouldAlwaysConformResultsInUnitOfWork(); // will be supported when conformResult method is adapted to use ResultSet; | |
} | |
/** | |
* INTERNAL: | |
* Indicates whether the query can use ResultSet optimization. | |
* Note that the session must be already set. | |
* The method is called when the query is executed, | |
* so it should refer only to the attributes that can be altered without re-preparing the query. | |
*/ | |
public boolean supportsResultSetAccessOptimizationOnExecute() { | |
if (!super.supportsResultSetAccessOptimizationOnExecute()) { | |
return false; | |
} | |
return !shouldConformResultsInUnitOfWork(); // could be supported if conformResult method is adapted to use ResultSetAccessOptimization | |
} | |
} |