/**********************************************************************
Copyright (c) 2006 Erik Bengtson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
2006 Andy Jefferson - migrated from Java5 plugin to Core
2008 Andy Jefferson - refactored compiler to be stand-alone. Major changes
...
**********************************************************************/
package org.jpox.store.rdbms.query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.jpox.ObjectManager;
import org.jpox.exceptions.JPOXException;
import org.jpox.store.Extent;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.query.CollectionCandidates;
import org.jpox.store.mapped.query.Evaluator;
import org.jpox.store.mapped.query.Queryable;
import org.jpox.store.query.AbstractJPQLQuery;
import org.jpox.store.query.JPQLSingleStringParser;
import org.jpox.store.query.Query;
import org.jpox.store.query.QueryCompiler;
import org.jpox.store.query.QueryResult;
import org.jpox.store.query.ResultObjectFactory;
import org.jpox.util.JPOXLogger;
/**
* RDBMS representation of a JPQL query for use by JPOX.
* The query can be specified via method calls, or via a single-string form.
*
* @see Query
* @version $Revision: 1.27 $
*/
public class JPQLQuery extends AbstractJPQLQuery
{
/** The Query Statement. */
protected transient QueryExpression queryStmt = null;
/** Candidates for this query. */
protected transient Queryable candidates = null;
/**
* Constructs a new query instance that uses the given persistence manager.
* @param om the associated ObjectManager for this query.
*/
public JPQLQuery(ObjectManager om)
{
this(om, (JPQLQuery)null);
}
/**
* Constructs a new query instance having the same criteria as the given query.
* @param om The ObjectManager
* @param q The query from which to copy criteria.
*/
public JPQLQuery(ObjectManager om, JPQLQuery q)
{
super(om);
if (q == null)
{
candidateClass = null;
candidateClassName = null;
filter = null;
imports = null;
explicitVariables = null;
explicitParameters = null;
grouping = null;
ordering = null;
result = null;
resultClass = null;
resultClassName = null;
range = null;
fromInclNo = 0;
toExclNo = Long.MAX_VALUE;
}
else
{
candidateClass = q.candidateClass;
candidateClassName = q.candidateClassName;
filter = q.filter;
imports = q.imports;
explicitVariables = q.explicitVariables;
explicitParameters = q.explicitParameters;
grouping = q.grouping;
ordering = q.ordering;
result = q.result;
resultClass = q.resultClass;
resultClassName = q.resultClassName;
range = q.range;
fromInclNo = q.fromInclNo;
toExclNo = q.toExclNo;
// JDO 1.0.1, Sect 12.6.3 : IgnoreCache is preserved when using other query baseline
ignoreCache = q.ignoreCache;
}
}
/**
* Constructor for a JPQL query where the query is specified using the "Single-String" format.
* @param om The ObjectManager
* @param query The query string
*/
public JPQLQuery(ObjectManager om, String query)
{
super(om);
new JPQLSingleStringParser(this, query).parse();
}
/**
* Accessor for the candidates for the query.
* This is only valid after compiling the query.
* @return Candidates for the query
*/
public Queryable getCandidates()
{
return candidates;
}
/**
* Equality operator for JPQL.
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj)
{
if (obj == this)
{
return true;
}
if (!(obj instanceof JPQLQuery) || !super.equals(obj))
{
return false;
}
return true;
}
/**
* Verify the elements of the query and provide a hint to the query to prepare and optimize an execution plan.
*/
protected void compileInternal(boolean forExecute, Map parameterValues)
{
if (isCompiled)
{
return;
}
try
{
if (forExecute)
{
// Compile for execution
if (JPOXLogger.QUERY.isDebugEnabled())
{
JPOXLogger.QUERY.debug(LOCALISER.msg("021044", "JPQL", getSingleStringQuery(), "execution"));
}
JPQLQueryCompiler c = new JPQLQueryCompiler(this, getParsedImports(), parameterValues);
queryStmt = (QueryExpression)c.compile(QueryCompiler.COMPILE_EXECUTION);
resultMetaData = c.getResultMetaData();
candidateClass = c.getCandidateClass();
candidates = c.getCandidates();
distinct = c.getDistinct();
resultClass = c.getResultClass();
fromInclNo = c.getRangeFromIncl();
toExclNo = c.getRangeToExcl();
c.close();
c = null;
}
else
{
// Use the compiler to perform a "pre-compile" check to validate the expressions
if (JPOXLogger.QUERY.isDebugEnabled())
{
JPOXLogger.QUERY.debug(LOCALISER.msg("021044", "JPQL", getSingleStringQuery(), "precompile"));
}
JPQLQueryCompiler c = new JPQLQueryCompiler(this, getParsedImports(), parameterValues);
c.compile(QueryCompiler.COMPILE_SYNTAX);
resultMetaData = c.getResultMetaData();
candidateClass = c.getCandidateClass();
candidates = c.getCandidates();
distinct = c.getDistinct();
resultClass = c.getResultClass();
fromInclNo = c.getRangeFromIncl();
toExclNo = c.getRangeToExcl();
c.close();
c = null;
}
isCompiled = true;
}
catch (JPOXException jpe)
{
// Compile failed
discardCompiled();
isCompiled = false;
throw jpe;
}
}
/**
* Method to discard our current compiled query due to changes.
* @see org.jpox.store.query.Query#discardCompiled()
*/
protected void discardCompiled()
{
super.discardCompiled();
queryStmt = null;
}
/**
* Convenience method to return whether the query should return a single row.
* @return Whether a single row should result
*/
protected boolean shouldReturnSingleRow()
{
if (unique)
{
// UNIQUE implies a single row
return true;
}
else if (grouping != null)
{
// GROUP BY implies multiple possible
return false;
}
else if (candidates instanceof ResultExpressionsQueryable)
{
// All result expressions being aggregate implies a single row
return ((ResultExpressionsQueryable)candidates).hasAggregatedExpressionsOnly();
}
return false;
}
/**
* Execute the query and return the filtered QueryResult.
* @param executeParameters Map containing all of the parameters.
* @return the results of the query - a QueryResult (if SELECT) or a Long (if BULK_UPDATE/BULK_DELETE)
*/
protected Object performExecute(Map executeParameters)
{
// Throw away any existing compilation remnants
discardCompiled();
// Create a compiled query ready for execution
try
{
// execution-compile the query statement
long startTime = System.currentTimeMillis();
// Generate Map of previously defined implicit parameter plus any just input at execute()
Map params = new HashMap();
if (implicitParameters != null)
{
params.putAll(implicitParameters);
}
if (executeParameters != null)
{
params.putAll(executeParameters);
}
// Compile imports, explicit parameters and explicit variables
compileInternal(true, params);
if (candidateCollection == null)
{
//only cache if use dont use candidates, otherwise the query using candidates may be different
//TODO temporarily disabled
//compiledCache.put(getSingleStringQuery(),new ExecutedCompileCache(queryStmt,getSingleStringQuery(),candidates,parameterNames));
}
if (JPOXLogger.QUERY.isDebugEnabled())
{
JPOXLogger.QUERY.debug(LOCALISER.msg("021045", "JPQL", "" + (System.currentTimeMillis() - startTime)));
}
}
finally
{
if (!isCompiled)
{
discardCompiled();
}
}
if (candidates.isEmpty())
{
// if the candidates are empty, we don't have to go to the database
// to find out that we have no elements to return
return new ArrayList();
}
// Apply the Query FetchPlan to the query
// Note : we could have added getFetchPlan() to Queryable, but this would affect *many* classes
// so leaving like this for now
if (candidates instanceof CollectionCandidates)
{
((CollectionCandidates)candidates).getFetchPlan().setGroups(getFetchPlan().getGroups());
}
else if (candidates instanceof Extent)
{
((Extent)candidates).getFetchPlan().setGroups(getFetchPlan().getGroups());
}
boolean useFetchPlan = om.getOMFContext().getPersistenceConfiguration().getBooleanProperty("org.jpox.query.useFetchPlan");
if (extensions != null && extensions.containsKey("org.jpox.query.useFetchPlan"))
{
useFetchPlan = Boolean.valueOf((String)extensions.get("org.jpox.query.useFetchPlan")).booleanValue();
}
if (type == BULK_UPDATE || type == BULK_DELETE)
{
// Don't want anything selecting apart from the PK fields
useFetchPlan = false;
}
// Create a processor for extracting the objects from the ResultSet
rof = candidates.newResultObjectFactory(queryStmt, getIgnoreCache(), resultClass, useFetchPlan);
if (rof instanceof PersistentIDROF)
{
// Allow for the user specifying an Extent of a base class but restricting this query to a subclass
((PersistentIDROF)rof).setPersistentClass(candidateClass);
}
// Make sure the datastore is prepared for the query
prepareDatastore();
// Execute the query
if (JPOXLogger.QUERY.isDebugEnabled())
{
JPOXLogger.QUERY.debug(LOCALISER.msg("021046", "JPQL", getSingleStringQuery()));
}
Evaluator eval = getEvaluator(this, distinct, rof, candidateCollection);
// TODO Cater for BULK_UPDATE, BULK_DELETE where return is Long
QueryResult qr = (QueryResult)eval.evaluate(queryStmt);
queryResults.add(qr);
return qr;
}
/**
* Method to return the evaluator to use for this JPQL query.
* @param query The query
* @param distinct whether to return distinct results
* @param rof ResultObjectFactory
* @param candidateCollection Any candidate collection
* @return The evaluator
*/
protected Evaluator getEvaluator(Query query, boolean distinct, ResultObjectFactory rof, Collection candidateCollection)
{
return new SQLEvaluator(this, distinct, rof, candidateCollection);
}
}