blob: dcee4046b3c3561f9523afa46a077ba9ed4420dd [file] [log] [blame]
/*
* 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.queries;
import java.util.List;
import java.util.Vector;
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform;
import org.eclipse.persistence.internal.helper.NonSynchronizedVector;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.internal.databaseaccess.DatabaseCall;
/**
* <p><b>Purpose</b>:
* Abstract class for all read queries.
*
* <p><b>Responsibilities</b>:
* <ul>
* <li> Caches result of query if flag is set.
* </ul>
*
* @author Yvon Lavoie
* @since TOPLink/Java 1.0
*/
public abstract class ReadQuery extends DatabaseQuery {
/** Used for retrieve limited rows through the query. */
protected int maxRows;
/** Used to start query results at a specific result */
protected int firstResult;
/* used on read queries to stamp the object to determine the last time it was refreshed to
* reduce work and prevent infinite recursion on Refreshes
*CR #4365 - used to prevent infinite recursion on refresh object cascade all
* CR #2698903 - fix for the previous fix. No longer using millis but ids now.
*/
protected long queryId;
/** Used to set statement fetch size */
protected int fetchSize;
/** Used to specify how query results are cached */
protected QueryResultsCachePolicy queryResultCachingPolicy = null;
/** Optimization: temporarily stores cached query results while they are being built in a cloned query */
protected transient Object temporaryCachedQueryResults = null;
/** Stores the JPA maxResult settings for a NamedQuery */
protected int maxResults = -1;
/**
* PUBLIC:
* Initialize the state of the query
*/
protected ReadQuery() {
this.maxRows = 0;
this.firstResult = 0;
this.fetchSize = 0;
this.queryId = 0;
}
/**
* INTERNAL:
* By default return the row.
* Used by cursored stream.
*/
public Object buildObject(AbstractRecord row) {
return row;
}
/**
* ADVANCED:
* <P>This method will instruct the query to cache the results returned by its
* next execution. All subsequent executions of this query will return this
* cached result set even if new query parameters are specified. This method
* provides a performance enhancement for queries known to always return the
* same result set. Oracle recommends that you use this method only for such
* queries.</P>
* <P>To disable this behavior, call {@link #doNotCacheQueryResults} or
* {@link #setQueryResultsCachePolicy} passing in null.</P>
*/
public void cacheQueryResults() {
setQueryResultsCachePolicy(new QueryResultsCachePolicy());
}
/**
* 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.
*/
public abstract void cacheResult(Object object);
/**
* INTERNAL
* Used to give the subclasses opportunity to copy aspects of the cloned query
* to the original query.
*/
@Override
protected void clonedQueryExecutionComplete(DatabaseQuery query, AbstractSession session) {
if (shouldCacheQueryResults()) {
Object result = ((ReadQuery)query).getTemporaryCachedQueryResults();
// If the temporary results were never set, then don't cache null.
if (result != null) {
// Cached query results must exist on the original query rather than the cloned one.
setQueryResults(result, query.getTranslationRow(), query.getSession());
}
}
}
/**
* PUBLIC:
* Clears the current cached results, the next execution with
* read from the database.
*
*/
public void clearQueryResults(AbstractSession session) {
session.getIdentityMapAccessor().clearQueryCache(this);
}
/**
* ADVANCED:
* <P>This method will instruct the query not to cache results. All subsequent
* executions return result sets according to the current configuration of
* query parameters. After calling this method, any previously cached result
* set will be discarded the next time the query is executed.</P>
* <P>To enable this behavior, call {@link #cacheQueryResults} or
* {@link #setQueryResultsCachePolicy} passing in a valid QueryResultsCachePolicy.</P>
* Note: If this method is called on a query that initially cached query results,
* clearQueryResults(Session) should also be called. Otherwise, the results of
* this query will remain in the cache and cause extra memory use
*/
public void doNotCacheQueryResults() {
setQueryResultsCachePolicy(null);
}
/**
* PUBLIC:
* Return the QueryResultsCachePolicy for this query.
*
* @see org.eclipse.persistence.queries.QueryResultsCachePolicy
*/
public QueryResultsCachePolicy getQueryResultsCachePolicy() {
return queryResultCachingPolicy;
}
/**
* PUBLIC:
* Return the value that will be set for the firstResult in the returned result set
*/
public int getFirstResult() {
return firstResult;
}
/**
* INTERNAL:
* This method is used to get the time in millis that this query is being executed at.
* it is set just prior to executing the SQL and will be used to determine which objects should be refreshed.
* CR #4365
* CR #2698903 ... instead of using millis we will now use id's instead. Method
* renamed appropriately.
*/
public long getQueryId() {
return this.queryId;
}
/**
* INTERNAL:
* returns the JPA max results that may have been set on a NamedQuery
* @return the maxResults
*/
public int getInternalMax() {
return maxResults;
}
/**
* PUBLIC:
* Return the limit for the maximum number of rows that any ResultSet can contain to the given number.
*/
public int getMaxRows() {
return this.maxRows;
}
/**
* PUBLIC:
* Return the fetchSize setting that this query will set on the JDBC Statement
* NB - a value of zero means that no call to statement.setFetchSize() will be made.
*/
public int getFetchSize() {
return this.fetchSize;
}
/**
* INTERNAL:
* To any user of this object with some knowledge of what the query's results may contain.
* Return the results of the query.
* If the query has never been executed, or does not cache results,
* the results will be null.
*/
protected Object getQueryResults(AbstractSession session) {
return getQueryResults(session, getTranslationRow(), true);
}
/**
* INTERNAL:
* To any user of this object with some knowledge of what the query's results may contain.
* Return the results of the query.
* If the query has never been executed, or does not cache results,
* the results will be null.
*/
protected Object getQueryResults(AbstractSession session, boolean checkExpiry) {
return getQueryResults(session, getTranslationRow(), checkExpiry);
}
/**
* INTERNAL:
* To any user of this object with some knowledge of what the query's results may contain.
* Return the results of the query.
* If the query has never been executed, or does not cache results,
* the results will be null.
*/
protected Object getQueryResults(AbstractSession session, AbstractRecord row, boolean checkExpiry) {
// Check for null translation row.
List arguments = null;
if (row != null) {
arguments = row.getValues();
}
return session.getIdentityMapAccessorInstance().getQueryResult(this, arguments, checkExpiry);
}
/**
* INTERNAL:
* Get results from the remporary cache.
* Used when caching query results on a clone so they can be copied to the original
* query
*/
public Object getTemporaryCachedQueryResults(){
return temporaryCachedQueryResults;
}
/**
* 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 EJBQL or selection criteria.
*/
@Override
public boolean isDefaultPropertiesQuery() {
return super.isDefaultPropertiesQuery()
&& (this.maxRows == 0)
&& (this.firstResult == 0)
&& (this.fetchSize == 0);
}
/**
* PUBLIC:
* Return if this is a read query.
*/
@Override
public boolean isReadQuery() {
return true;
}
/**
* INTERNAL:
* Copy all setting from the query.
* This is used to morph queries from one type to the other.
* By default this calls prepareFromQuery, but additional properties may be required
* to be copied as prepareFromQuery only copies properties that affect the SQL.
*/
@Override
public void copyFromQuery(DatabaseQuery query) {
super.copyFromQuery(query);
if (query.isReadQuery()) {
ReadQuery readQuery = (ReadQuery)query;
this.fetchSize = readQuery.fetchSize;
this.firstResult = readQuery.firstResult;
this.maxRows = readQuery.maxRows;
this.queryResultCachingPolicy = readQuery.queryResultCachingPolicy;
}
}
/**
* INTERNAL:
* This is different from 'prepareForExecution' in that this is called on the original query,
* and the other is called on the copy of the query.
* This query is copied for concurrency so this prepare can only setup things that
* will apply to any future execution of this query.
*
* Clear the query cache when a query is prepared.
*/
@Override
protected void prepare() throws QueryException {
super.prepare();
if (shouldCacheQueryResults()) {
clearQueryResults(getSession());
if (getReferenceClass() != null) {
getQueryResultsCachePolicy().getInvalidationClasses().add(getReferenceClass());
}
}
}
/**
* INTERNAL:
* Prepare the receiver for execution in a session.
*/
@Override
public void prepareForExecution() throws QueryException {
super.prepareForExecution();
DatabaseCall databaseCall = this.getCall();
if ( databaseCall !=null && (databaseCall.shouldIgnoreFirstRowSetting() || databaseCall.shouldIgnoreMaxResultsSetting())){
AbstractRecord parameters = this.getTranslationRow();
if (parameters.isEmpty()){
parameters = new DatabaseRecord();
}
//Some DB don't support FirstRow in SELECT statements in spite of supporting MaxResults(Symfoware).
//We should check FirstRow and MaxResults separately.
if (databaseCall.shouldIgnoreFirstRowSetting()) {
parameters.add(DatabaseCall.FIRSTRESULT_FIELD, this.getFirstResult());
}
if (databaseCall.shouldIgnoreMaxResultsSetting()) {
// Bug #493771
parameters.add(DatabaseCall.MAXROW_FIELD, ((DatabasePlatform) session.getPlatform(databaseCall.getQuery().getReferenceClass())).computeMaxRowsForSQL(this.getFirstResult(), this.getMaxRows()));
}
this.setTranslationRow(parameters);
}
}
/**
* INTERNAL:
* Return if this is a read query.
*/
@Override
public Object remoteExecute(AbstractSession session) throws DatabaseException {
if (shouldCacheQueryResults()) {
AbstractRecord arguments = new DatabaseRecord();
if (translationRow != null){
arguments = translationRow;
}
Object queryResults = getQueryResults(session, arguments, true);
if (queryResults != null) {
return queryResults;
}
queryResults = super.remoteExecute(session);
if (queryResults != null){
setQueryResults(queryResults, arguments, session);
}
return queryResults;
}
return super.remoteExecute(session);
}
/**
* Set the QueryResultsCachePolicy.
*
* @see org.eclipse.persistence.queries.QueryResultsCachePolicy
*/
public void setQueryResultsCachePolicy(QueryResultsCachePolicy policy) {
queryResultCachingPolicy = policy;
// ensure the cache is cleared if the caching policy is changed
setIsPrepared(false);
}
/**
* PUBLIC:
* Used to set the first result in any result set that is returned for this query.
* On supported database platforms this will cause the query to issue specific SQL
* that avoids selecting the firstResult number of rows.
* Otherwise by it will use the JDBC absolute to skip the firstResult number of rows.
*/
public void setFirstResult(int firstResult) {
if (isPrepared() && this.firstResult != firstResult) {
if (getCall()!=null && getCall().shouldIgnoreFirstRowSetting()) {
// Don't need to reprepare as firstResult is already built into the sql if ignoreFirstRowMaxResultsSettings is set,
// firstResult is just a query parameter.
} else {
setIsPrepared(false);
}
}
this.firstResult = firstResult;
this.shouldCloneCall = true;
}
/**
* INTERNAL:
* This method is used to set the current system time in millis that this query is being executed at.
* it is set just prior to executing the SQL and will be used to determine which objects should be refreshed.
* CR #4365
* CR #2698903 ... instead of using millis we will now use id's instead. Method
* renamed appropriately.
*/
public void setQueryId(long id) {
this.queryId = id;
}
/**
* INTERNAL:
* sets the JPA max results that may have been set on a NamedQuery
*/
public void setInternalMax(int max) {
this.maxResults = max;
}
/**
* PUBLIC:
* Used to set the limit for the maximum number of rows that any ResultSet can contain to the given number.
* This method should only be set once per query. To change the max rows use another query.
* This method limits the number of candidate results returned to TopLink that can be used to build objects
*/
public void setMaxRows(int maxRows) {
if ( isPrepared() && this.maxRows != maxRows){
if ( this.getCall()!=null && this.getCall().shouldIgnoreMaxResultsSetting() && this.maxRows>0 ){
}else{
setIsPrepared(false);
}
}
this.maxRows = maxRows;
shouldCloneCall=true;
}
/**
* PUBLIC:
* Set the fetchSize setting that this query will set on the JDBC Statement
* NB - a value of zero means that no call to statement.setFetchSize() will be made.
*/
public void setFetchSize(int fetchSize) {
if ( isPrepared() && this.getCall()!=null) {
getCall().setResultSetFetchSize(fetchSize);
}
this.fetchSize = fetchSize;
}
/**
* INTERNAL:
* Set the cached results of the query.
* This will only be set if the query caches results.
*/
protected void setQueryResults(Object resultFromQuery, AbstractRecord row, AbstractSession session) {
Vector arguments = null;
if (row == null) {
arguments = new NonSynchronizedVector(1);
} else {
arguments = row.getValues();
}
session.getIdentityMapAccessorInstance().putQueryResult(this, arguments, resultFromQuery);
}
/**
* PUBLIC:
* Return if the query should cache the results of the next execution or not.
*/
public boolean shouldCacheQueryResults() {
return queryResultCachingPolicy != null;
}
/**
* INTERNAL:
* Put results in the temporary cache.
* Used when caching query results on a clone so they can be copied to the original
* query
*/
public void setTemporaryCachedQueryResults(Object queryResults){
temporaryCachedQueryResults = queryResults;
}
}