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