blob: 733bb4ea1e6ff5b37848b1050f3628e7e3febdac [file] [log] [blame]
/*******************************************************************************
* 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) {
Boolean useCustomQuery = isCustomQueryUsed;
checkDescriptor(session);
// Check if user defined a custom query.
if (useCustomQuery == null) {
setIsCustomQueryUsed((!isUserDefined()) && isExpressionQuery() && (getSelectionCriteria() == null) && isDefaultPropertiesQuery() && (!hasOrderByExpressions()) && (this.descriptor.getQueryManager().hasReadAllQuery()));
// Value of isCustomQueryUsed is updated by setIsCustomQueryUsed method.
useCustomQuery = isCustomQueryUsed;
}
if (useCustomQuery != null && useCustomQuery.booleanValue()) {
ReadAllQuery customQuery = this.descriptor.getQueryManager().getReadAllQuery();
if (this.accessors != null) {
customQuery = (ReadAllQuery) customQuery.clone();
customQuery.setIsExecutionClone(true);
customQuery.setAccessors(this.accessors);
}
isCustomQueryUsed = useCustomQuery;
return customQuery;
}
isCustomQueryUsed = useCustomQuery;
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
}
}