Package org.datanucleus.store.rdbms.query

Source Code of org.datanucleus.store.rdbms.query.JPQLQuery

/**********************************************************************
Copyright (c) 2009 Andy Jefferson 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:
    ...
**********************************************************************/
package org.datanucleus.store.rdbms.query;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.NucleusDataStoreException;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.query.QueryUtils;
import org.datanucleus.query.evaluator.JPQLEvaluator;
import org.datanucleus.query.evaluator.JavaQueryEvaluator;
import org.datanucleus.query.expression.Expression;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.connection.ManagedConnection;
import org.datanucleus.store.connection.ManagedConnectionResourceListener;
import org.datanucleus.store.mapped.DatastoreAdapter;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.StatementClassMapping;
import org.datanucleus.store.mapped.StatementMappingIndex;
import org.datanucleus.store.mapped.StatementNewObjectMapping;
import org.datanucleus.store.mapped.StatementResultMapping;
import org.datanucleus.store.query.AbstractJPQLQuery;
import org.datanucleus.store.query.CandidateIdsQueryResult;
import org.datanucleus.store.query.Query;
import org.datanucleus.store.query.QueryInterruptedException;
import org.datanucleus.store.query.QueryManager;
import org.datanucleus.store.query.QueryResult;
import org.datanucleus.store.query.QueryTimeoutException;
import org.datanucleus.store.query.ResultObjectFactory;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.SQLController;
import org.datanucleus.store.rdbms.adapter.RDBMSAdapter;
import org.datanucleus.store.rdbms.sql.SQLStatement;
import org.datanucleus.store.rdbms.sql.SQLStatementHelper;
import org.datanucleus.store.rdbms.sql.SQLJoin.JoinType;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.NucleusLogger;

/**
* RDBMS representation of a JPQL query for use by DataNucleus.
* The query can be specified via method calls, or via a single-string form.
* This implementation uses the generic query compilation in "org.datanucleus.query".
* There are the following main ways of running a query here
* <ul>
* <li>Totally in the datastore (no candidate collection specified, and no in-memory eval).</li>
* <li>Totally in-memory (candidate collection specified, and in-memory eval)</li>
* <li>Retrieve candidates from datastore (no candidate collection), and evaluate in-memory</li>
* </ul>
*/
public class JPQLQuery extends AbstractJPQLQuery
{
    /** The compilation of the query for this datastore. Not applicable if totally in-memory. */
    protected transient RDBMSQueryCompilation datastoreCompilation;

    public static final String EXTENSION_RDBMS_RESULTSET_TYPE = "datanucleus.rdbms.query.resultSetType";
    public static final String EXTENSION_RDBMS_RESULTSET_CONCURRENCY = "datanucleus.rdbms.query.resultSetConcurrency";
    public static final String EXTENSION_RDBMS_FETCH_DIRECTION = "datanucleus.rdbms.query.fetchDirection";

    /**
     * Constructs a new query instance that uses the given object manager.
     * @param ec execution context
     */
    public JPQLQuery(ExecutionContext ec)
    {
        this(ec, (JPQLQuery) null);
    }

    /**
     * Constructs a new query instance having the same criteria as the given query.
     * @param ec execution context
     * @param q The query from which to copy criteria.
     */
    public JPQLQuery(ExecutionContext ec, JPQLQuery q)
    {
        super(ec, q);
    }

    /**
     * Constructor for a JPQL query where the query is specified using the "Single-String" format.
     * @param ec The ObjectManager
     * @param query The single-string query form
     */
    public JPQLQuery(ExecutionContext ec, String query)
    {
        super(ec, query);
    }

    @Override
    public void setImplicitParameter(int position, Object value)
    {
        if (datastoreCompilation != null && !datastoreCompilation.isPrecompilable())
        {
            // Force recompile since parameter value set and not compilable without parameter values
            datastoreCompilation = null;
        }
        super.setImplicitParameter(position, value);
    }

    @Override
    public void setImplicitParameter(String name, Object value)
    {
        if (datastoreCompilation != null && !datastoreCompilation.isPrecompilable())
        {
            // Force recompile since parameter value set and not compilable without parameter values
            datastoreCompilation = null;
        }
        super.setImplicitParameter(name, value);
    }

    /**
     * Utility to remove any previous compilation of this Query.
     */
    protected void discardCompiled()
    {
        super.discardCompiled();

        datastoreCompilation = null;
    }

    /**
     * Method to return if the query is compiled.
     * @return Whether it is compiled
     */
    protected boolean isCompiled()
    {
        if (candidateCollection != null)
        {
            // Don't need datastore compilation here since evaluating in-memory
            return compilation != null;
        }
        else
        {
            // Need both to be present to say "compiled"
            if (compilation == null || datastoreCompilation == null)
            {
                return false;
            }
            if (!datastoreCompilation.isPrecompilable())
            {
                NucleusLogger.GENERAL.info("Query compiled but not precompilable so ditching datastore compilation");
                datastoreCompilation = null;
                return false;
            }
            return true;
        }
    }

    /**
     * Method to compile the JPQL query.
     * Uses the superclass to compile the generic query populating the "compilation", and then generates
     * the datastore-specific "datastoreCompilation".
     * @param parameterValues Map of param values keyed by param name (if available at compile time)
     */
    protected synchronized void compileInternal(Map parameterValues)
    {
        if (isCompiled())
        {
            return;
        }

        // Compile the generic query expressions
        super.compileInternal(parameterValues);

        boolean inMemory = evaluateInMemory();
        if (candidateCollection != null)
        {
            // Everything done in-memory so just return now (don't need datastore compilation)
            // TODO Maybe apply the result class checks ?
            return;
        }

        if (candidateClass == null || candidateClassName == null)
        {
            candidateClass = compilation.getCandidateClass();
            candidateClassName = candidateClass.getName();
        }

        // Create the SQL statement, and its result/parameter definitions
        RDBMSStoreManager storeMgr = (RDBMSStoreManager)getStoreManager();
        ClassLoaderResolver clr = ec.getClassLoaderResolver();
        AbstractClassMetaData acmd = ec.getMetaDataManager().getMetaDataForClass(candidateClass, clr);
        QueryManager qm = getQueryManager();
        String datastoreKey = storeMgr.getQueryCacheKey();
        String queryCacheKey = getQueryCacheKey();

        if (useCaching() && queryCacheKey != null)
        {
            // Check if we have any parameters set to null, since this can invalidate a datastore compilation
            // e.g " field == :val" can be "COL IS NULL" or "COL = <val>"
            boolean nullParameter = false;
            if (parameterValues != null)
            {
                Iterator iter = parameterValues.values().iterator();
                while (iter.hasNext())
                {
                    Object val = iter.next();
                    if (val == null)
                    {
                        nullParameter = true;
                        break;
                    }
                }
            }

            if (!nullParameter)
            {
                // Allowing caching so try to find compiled (datastore) query
                datastoreCompilation = (RDBMSQueryCompilation)qm.getDatastoreQueryCompilation(datastoreKey,
                    getLanguage(), queryCacheKey);
                if (datastoreCompilation != null)
                {
                    // Cached compilation exists for this datastore so reuse it
                    return;
                }
            }
        }

        // No cached compilation for this query in this datastore so compile it
        if (type == Query.BULK_UPDATE)
        {
            datastoreCompilation = new RDBMSQueryCompilation();
            compileQueryUpdate(parameterValues, acmd);
        }
        else if (type == Query.BULK_DELETE)
        {
            datastoreCompilation = new RDBMSQueryCompilation();
            compileQueryDelete(parameterValues, acmd);
        }
        else
        {
            datastoreCompilation = new RDBMSQueryCompilation();
            if (inMemory)
            {
                // Generate statement to just retrieve all candidate objects for later processing
                compileQueryToRetrieveCandidates(parameterValues, acmd);
            }
            else
            {
                // Generate statement to perform the full query in the datastore
                compileQueryFull(parameterValues, acmd);

                if (result != null)
                {
                    StatementResultMapping resultMapping = datastoreCompilation.getResultDefinition();
                    for (int i=0;i<resultMapping.getNumberOfResultExpressions();i++)
                    {
                        Object stmtMap = resultMapping.getMappingForResultExpression(i);
                        if (stmtMap instanceof StatementMappingIndex)
                        {
                            StatementMappingIndex idx = (StatementMappingIndex)stmtMap;
                            AbstractMemberMetaData mmd = idx.getMapping().getMemberMetaData();
                            if (mmd != null)
                            {
                                if (mmd.hasCollection() || mmd.hasMap() || mmd.hasArray())
                                {
                                    throw new NucleusUserException(LOCALISER.msg("021213"));
                                }
                            }
                        }
                    }
                }
            }

            if (resultClass != null && result != null)
            {
                // Do as PrivilegedAction since uses reflection
                AccessController.doPrivileged(new PrivilegedAction()
                {
                    public Object run()
                    {
                        // Check that this class has the necessary constructor/setters/fields to be used
                        StatementResultMapping resultMapping = datastoreCompilation.getResultDefinition();
                        if (QueryUtils.resultClassIsSimple(resultClass.getName()))
                        {
                            if (resultMapping.getNumberOfResultExpressions() > 1)
                            {
                                // Invalid number of result expressions
                                throw new NucleusUserException(LOCALISER.msg("021201", resultClass.getName()));
                            }

                            Object stmtMap = resultMapping.getMappingForResultExpression(0);
                            // TODO Handle StatementNewObjectMapping
                            StatementMappingIndex idx = (StatementMappingIndex)stmtMap;
                            Class exprType = idx.getMapping().getJavaType();
                            boolean typeConsistent = false;
                            if (exprType == resultClass)
                            {
                                typeConsistent = true;
                            }
                            else if (exprType.isPrimitive())
                            {
                                Class resultClassPrimitive = ClassUtils.getPrimitiveTypeForType(resultClass);
                                if (resultClassPrimitive == exprType)
                                {
                                    typeConsistent = true;
                                }
                            }
                            if (!typeConsistent)
                            {
                                // Inconsistent expression type not matching the result class type
                                throw new NucleusUserException(LOCALISER.msg("021202", resultClass.getName(), exprType));
                            }
                        }
                        else if (QueryUtils.resultClassIsUserType(resultClass.getName()))
                        {
                            // Check for valid constructor (either using param types, or using default ctr)
                            Class[] ctrTypes = new Class[resultMapping.getNumberOfResultExpressions()];
                            for (int i=0;i<ctrTypes.length;i++)
                            {
                                Object stmtMap = resultMapping.getMappingForResultExpression(i);
                                if (stmtMap instanceof StatementMappingIndex)
                                {
                                    ctrTypes[i] = ((StatementMappingIndex)stmtMap).getMapping().getJavaType();
                                }
                                else if (stmtMap instanceof StatementNewObjectMapping)
                                {
                                    // TODO Handle this
                                }
                            }
                            Constructor ctr = ClassUtils.getConstructorWithArguments(resultClass, ctrTypes);
                            if (ctr == null && !ClassUtils.hasDefaultConstructor(resultClass))
                            {
                                // No valid constructor found!
                                throw new NucleusUserException(LOCALISER.msg("021205", resultClass.getName()));
                            }
                            else if (ctr == null)
                            {
                                // We are using default constructor, so check the types of the result expressions for means of input
                                for (int i=0;i<resultMapping.getNumberOfResultExpressions();i++)
                                {
                                    Object stmtMap = resultMapping.getMappingForResultExpression(i);
                                    if (stmtMap instanceof StatementMappingIndex)
                                    {
                                        StatementMappingIndex mapIdx = (StatementMappingIndex)stmtMap;
                                        AbstractMemberMetaData mmd = mapIdx.getMapping().getMemberMetaData();
                                        String fieldName = mapIdx.getColumnAlias();
                                        Class fieldType = mapIdx.getMapping().getJavaType();
                                        if (fieldName == null && mmd != null)
                                        {
                                            fieldName = mmd.getName();
                                        }

                                        if (fieldName != null)
                                        {
                                            // Check for the field of that name in the result class
                                            Class resultFieldType = null;
                                            boolean publicField = true;
                                            try
                                            {
                                                Field fld = resultClass.getDeclaredField(fieldName);
                                                resultFieldType = fld.getType();

                                                // Check the type of the field
                                                if (!ClassUtils.typesAreCompatible(fieldType, resultFieldType) &&
                                                        !ClassUtils.typesAreCompatible(resultFieldType, fieldType))
                                                {
                                                    throw new NucleusUserException(LOCALISER.msg("021211",
                                                        fieldName, fieldType.getName(), resultFieldType.getName()));
                                                }
                                                if (!Modifier.isPublic(fld.getModifiers()))
                                                {
                                                    publicField = false;
                                                }
                                            }
                                            catch (NoSuchFieldException nsfe)
                                            {
                                                publicField = false;
                                            }

                                            // Check for a public set method
                                            if (!publicField)
                                            {
                                                Method setMethod = QueryUtils.getPublicSetMethodForFieldOfResultClass(resultClass, fieldName, resultFieldType);
                                                if (setMethod == null)
                                                {
                                                    // No setter, so check for a public put(Object, Object) method
                                                    Method putMethod = QueryUtils.getPublicPutMethodForResultClass(resultClass);
                                                    if (putMethod == null)
                                                    {
                                                        throw new NucleusUserException(LOCALISER.msg("021212",
                                                            resultClass.getName(), fieldName));
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    else if (stmtMap instanceof StatementNewObjectMapping)
                                    {
                                        // TODO Handle this
                                    }
                                }
                            }
                        }

                        return null;
                    }
                });
            }

            if (NucleusLogger.QUERY.isDebugEnabled())
            {
                NucleusLogger.QUERY.debug(LOCALISER.msg("021085", this, datastoreCompilation.getSQL()));
            }

            boolean hasParams = false;
            if (explicitParameters != null)
            {
                hasParams = true;
            }
            else if (parameterValues != null && parameterValues.size() > 0)
            {
                hasParams = true;
            }
            if (!datastoreCompilation.isPrecompilable() ||
                (datastoreCompilation.getSQL().indexOf('?') < 0 && hasParams))
            {
                // Some parameters had their clauses evaluated during compilation so the query
                // didn't gain any parameters, so don't cache it
                NucleusLogger.QUERY.debug(LOCALISER.msg("021075"));
            }
            else
            {
                if (useCaching() && queryCacheKey != null)
                {
                  qm.addDatastoreQueryCompilation(datastoreKey, getLanguage(), queryCacheKey, datastoreCompilation);
                }
            }
        }
    }

    /**
     * Convenience accessor for the SQL to invoke in the datastore for this query.
     * @return The SQL.
     */
    public String getSQL()
    {
        if (datastoreCompilation != null)
        {
            return datastoreCompilation.getSQL();
        }
        return null;
    }

    protected Object performExecute(Map parameters)
    {
        if (candidateCollection != null)
        {
            // Supplied collection of instances, so evaluate in-memory
            if (candidateCollection.isEmpty())
            {
                return Collections.EMPTY_LIST;
            }
            else
            {
                List candidates = new ArrayList(candidateCollection);
                JavaQueryEvaluator resultMapper = new JPQLEvaluator(this, candidates, compilation,
                    parameters, clr);
                return resultMapper.execute(true, true, true, true, true);
            }
        }
        else if (type == Query.SELECT)
        {
            // Query results are cached, so return those
            List<Object> cachedResults = getQueryManager().getDatastoreQueryResult(this, parameters);
            if (cachedResults != null)
            {
                return new CandidateIdsQueryResult(this, cachedResults);
            }
        }

        Object results = null;
        ManagedConnection mconn = ec.getStoreManager().getConnection(ec);
        try
        {
            // Execute the query
            long startTime = System.currentTimeMillis();
            if (NucleusLogger.QUERY.isDebugEnabled())
            {
                NucleusLogger.QUERY.debug(LOCALISER.msg("021046", getLanguage(), getSingleStringQuery(),
                    null));
            }

            RDBMSStoreManager storeMgr = (RDBMSStoreManager)getStoreManager();
            AbstractClassMetaData acmd = ec.getMetaDataManager().getMetaDataForClass(candidateClass, clr);
            SQLController sqlControl = storeMgr.getSQLController();
            PreparedStatement ps = null;
            try
            {
                if (type == Query.SELECT)
                {
                    // Create PreparedStatement and apply parameters, result settings etc
                    ps = RDBMSQueryUtils.getPreparedStatementForQuery(mconn,
                        datastoreCompilation.getSQL(), this);
                    SQLStatementHelper.applyParametersToStatement(ps, ec,
                        datastoreCompilation.getStatementParameters(),
                        null, parameters);
                    RDBMSQueryUtils.prepareStatementForExecution(ps, this, false);

                    registerTask(ps);
                    ResultSet rs = null;
                    try
                    {
                        rs = sqlControl.executeStatementQuery(mconn, toString(), ps);
                    }
                    finally
                    {
                        deregisterTask();
                    }

                    QueryResult qr = null;
                    try
                    {
                        if (evaluateInMemory())
                        {
                            // IN-MEMORY EVALUATION
                            ResultObjectFactory rof = storeMgr.newResultObjectFactory(acmd,
                                datastoreCompilation.getResultDefinitionForClass(),
                                RDBMSQueryUtils.useUpdateLockForQuery(this), getFetchPlan(),
                                candidateClass);

                            // Just instantiate the candidates for later in-memory processing
                            // TODO Use a queryResult rather than an ArrayList so we load when required
                            List candidates = new ArrayList();
                            while (rs.next())
                            {
                                candidates.add(rof.getObject(ec, rs));
                            }

                            // Perform in-memory filter/result/order etc
                            JavaQueryEvaluator resultMapper =
                                new JPQLEvaluator(this, candidates, compilation, parameters, clr);
                            results = resultMapper.execute(true, true, true, true, true);
                        }
                        else
                        {
                            // IN-DATASTORE EVALUATION
                            ResultObjectFactory rof = null;
                            if (result != null)
                            {
                                // Each result row is of a result type
                                rof = new ResultClassROF(resultClass, datastoreCompilation.getResultDefinition());
                            }
                            else if (resultClass != null)
                            {
                                rof = new ResultClassROF(resultClass, datastoreCompilation.getResultDefinitionForClass());
                            }
                            else
                            {
                                // Each result row is a candidate object
                                rof = storeMgr.newResultObjectFactory(acmd,
                                    datastoreCompilation.getResultDefinitionForClass(),
                                    RDBMSQueryUtils.useUpdateLockForQuery(this), getFetchPlan(),
                                    candidateClass);
                            }

                            // Create the required type of QueryResult
                            String resultSetType = RDBMSQueryUtils.getResultSetTypeForQuery(this);
                            if (resultSetType.equals("scroll-insensitive") ||
                                    resultSetType.equals("scroll-sensitive"))
                            {
                                qr = new ScrollableQueryResult(this, rof, rs,
                                    getResultDistinct() ? null : candidateCollection);
                            }
                            else
                            {
                                qr = new ForwardQueryResult(this, rof, rs,
                                    getResultDistinct() ? null : candidateCollection);
                            }

                            final QueryResult qr1 = qr;
                            final ManagedConnection mconn1 = mconn;
                            ManagedConnectionResourceListener listener =
                                new ManagedConnectionResourceListener()
                            {
                                public void transactionFlushed(){}
                                public void transactionPreClose()
                                {
                                    // Disconnect the query from this ManagedConnection (read in unread rows etc)
                                    qr1.disconnect();
                                }
                                public void managedConnectionPreClose(){}
                                public void managedConnectionPostClose(){}
                                public void resourcePostClose()
                                {
                                    mconn1.removeListener(this);
                                }
                            };
                            mconn.addListener(listener);
                            ((AbstractRDBMSQueryResult)qr).addConnectionListener(listener);
                            results = qr;
                        }
                    }
                    finally
                    {
                        if (qr == null)
                        {
                            rs.close();
                        }
                    }
                }
                else if (type == Query.BULK_UPDATE)
                {
                    // Create PreparedStatement and apply parameters, result settings etc
                    ps = sqlControl.getStatementForUpdate(mconn, datastoreCompilation.getSQL(), false);
                    SQLStatementHelper.applyParametersToStatement(ps, ec,
                        datastoreCompilation.getStatementParameters(),
                        null, parameters);
                    RDBMSQueryUtils.prepareStatementForExecution(ps, this, false);

                    int[] updateResults = sqlControl.executeStatementUpdate(mconn, toString(), ps, true);

                    try
                    {
                        // Evict all objects of this type from the cache
                        ec.getNucleusContext().getLevel2Cache().evictAll(candidateClass, subclasses);
                    }
                    catch (UnsupportedOperationException uoe)
                    {
                        // Do nothing
                    }

                    results = Long.valueOf(updateResults[0]);
                }
                else if (type == Query.BULK_DELETE)
                {
                    // TODO Cater for multiple DELETE statements
                    // Create PreparedStatement and apply parameters, result settings etc
                    ps = sqlControl.getStatementForUpdate(mconn, datastoreCompilation.getSQL(), false);
                    SQLStatementHelper.applyParametersToStatement(ps, ec,
                        datastoreCompilation.getStatementParameters(),
                        null, parameters);
                    RDBMSQueryUtils.prepareStatementForExecution(ps, this, false);

                    int[] deleteResults = sqlControl.executeStatementUpdate(mconn, toString(), ps, true);

                    try
                    {
                        // Evict all objects of this type from the cache
                        ec.getNucleusContext().getLevel2Cache().evictAll(candidateClass, subclasses);
                    }
                    catch (UnsupportedOperationException uoe)
                    {
                        // Do nothing
                    }

                    results = Long.valueOf(deleteResults[0]);
                }
            }
            catch (SQLException sqle)
            {
                if (((RDBMSAdapter)storeMgr.getDatastoreAdapter()).isStatementCancel(sqle))
                {
                    throw new QueryInterruptedException("Query has been interrupted", sqle);
                }
                else if (((RDBMSAdapter)storeMgr.getDatastoreAdapter()).isStatementTimeout(sqle))
                {
                    throw new QueryTimeoutException("Query has been timed out", sqle);
                }
                throw new NucleusException(LOCALISER.msg("021042"), sqle);
            }

            if (NucleusLogger.QUERY.isDebugEnabled())
            {
                NucleusLogger.QUERY.debug(LOCALISER.msg("021074", getLanguage(),
                    "" + (System.currentTimeMillis() - startTime)));
            }

            return results;
        }
        finally
        {
            mconn.release();
        }
    }

    /**
     * Method that will throw an {@link UnsupportedOperationException} if the query implementation doesn't
     * support cancelling queries.
     */
    protected void assertSupportsCancel()
    {
        // We support cancel via JDBC PreparedStatement.cancel();
    }

    protected boolean cancelTaskObject(Object obj)
    {
        Statement ps = (Statement)obj;
        try
        {
            ps.cancel();
            return true;
        }
        catch (SQLException sqle)
        {
            NucleusLogger.DATASTORE_RETRIEVE.warn("Error cancelling query", sqle);
            return false;
        }
    }

    /**
     * Convenience method for whether this query supports timeouts.
     * @return Whether timeouts are supported.
     */
    protected boolean supportsTimeout()
    {
        return true;
    }

    /**
     * Method to set the (native) query statement for the compiled query as a whole.
     * The "table groups" in the resultant SQLStatement will be named as per the candidate alias,
     * and thereafter "{alias}.{fieldName}".
     * @param parameters Input parameters (if known)
     * @param candidateCmd Metadata for the candidate class
     */
    private void compileQueryFull(Map parameters, AbstractClassMetaData candidateCmd)
    {
        if (type != Query.SELECT)
        {
            return;
        }
        if (candidateCollection != null)
        {
            return;
        }

        long startTime = 0;
        if (NucleusLogger.QUERY.isDebugEnabled())
        {
            startTime = System.currentTimeMillis();
            NucleusLogger.QUERY.debug(LOCALISER.msg("021083", getLanguage(), toString()));
        }

        if (result != null)
        {
            datastoreCompilation.setResultDefinition(new StatementResultMapping());
        }
        else
        {
            datastoreCompilation.setResultDefinitionForClass(new StatementClassMapping());
        }

        // Generate statement for candidate(s)
        SQLStatement stmt = RDBMSQueryUtils.getStatementForCandidates(null, candidateCmd,
            datastoreCompilation.getResultDefinitionForClass(), ec, candidateClass, subclasses, result,
            compilation.getCandidateAlias(), compilation.getCandidateAlias());

        // Update the SQLStatement with filter, ordering, result etc
        Set<String> options = new HashSet<String>();
        options.add(QueryToSQLMapper.OPTION_CASE_INSENSITIVE);
        options.add(QueryToSQLMapper.OPTION_EXPLICIT_JOINS);
        QueryToSQLMapper sqlMapper = new QueryToSQLMapper(stmt, compilation, parameters,
            datastoreCompilation.getResultDefinitionForClass(), datastoreCompilation.getResultDefinition(),
            candidateCmd, getFetchPlan(), ec, null, options, extensions);
        sqlMapper.setDefaultJoinType(JoinType.INNER_JOIN);
        sqlMapper.compile();
        datastoreCompilation.setParameterNameByPosition(sqlMapper.getParameterNameByPosition());
        datastoreCompilation.setPrecompilable(sqlMapper.isPrecompilable());

        // Apply any range
        if (range != null)
        {
            long lower = fromInclNo;
            long upper = toExclNo;
            if (fromInclParam != null)
            {
                lower = ((Number)parameters.get(fromInclParam)).longValue();
            }
            if (toExclParam != null)
            {
                upper = ((Number)parameters.get(toExclParam)).longValue();
            }
            stmt.setRange(lower, upper-lower);
        }

        // Set any extensions
        boolean useUpdateLock = RDBMSQueryUtils.useUpdateLockForQuery(this);
        stmt.addExtension("lock-for-update", Boolean.valueOf(useUpdateLock));

        datastoreCompilation.setSQL(stmt.getSelectStatement().toString());
        datastoreCompilation.setStatementParameters(stmt.getSelectStatement().getParametersForStatement());

        if (NucleusLogger.QUERY.isDebugEnabled())
        {
            NucleusLogger.QUERY.debug(LOCALISER.msg("021084", getLanguage(), System.currentTimeMillis()-startTime));
        }
    }

    /**
     * Method to set the statement (and parameter/results definitions) to retrieve all candidates.
     * This is used when we want to evaluate in-memory and so just retrieve all possible candidates
     * first.
     * @param parameters Input parameters (if known)
     * @param candidateCmd Metadata for the candidate class
     */
    private void compileQueryToRetrieveCandidates(Map parameters, AbstractClassMetaData candidateCmd)
    {
        if (type != Query.SELECT)
        {
            return;
        }
        if (candidateCollection != null)
        {
            return;
        }

        StatementClassMapping resultsDef = new StatementClassMapping();
        datastoreCompilation.setResultDefinitionForClass(resultsDef);

        // Generate statement for candidate(s)
        SQLStatement stmt = RDBMSQueryUtils.getStatementForCandidates(null, candidateCmd,
            datastoreCompilation.getResultDefinitionForClass(), ec, candidateClass, subclasses, result, null, null);

        if (stmt.allUnionsForSamePrimaryTable())
        {
            // Select fetch-plan fields of candidate class
            SQLStatementHelper.selectFetchPlanOfCandidateInStatement(stmt,
                datastoreCompilation.getResultDefinitionForClass(), candidateCmd, getFetchPlan(), 1);
        }
        else
        {
            // Select id only since tables don't have same mappings or column names
            // TODO complete-table will come through here but maybe ought to be treated differently
            SQLStatementHelper.selectIdentityOfCandidateInStatement(stmt,
                datastoreCompilation.getResultDefinitionForClass(), candidateCmd);
        }

        datastoreCompilation.setSQL(stmt.getSelectStatement().toString());
        datastoreCompilation.setStatementParameters(stmt.getSelectStatement().getParametersForStatement());
    }

    /**
     * Method to return the names of the extensions supported by this query.
     * To be overridden by subclasses where they support additional extensions.
     * @return The supported extension names
     */
    public Set<String> getSupportedExtensions()
    {
        Set<String> supported = super.getSupportedExtensions();
        supported.add(EXTENSION_RDBMS_RESULTSET_TYPE);
        supported.add(EXTENSION_RDBMS_RESULTSET_CONCURRENCY);
        supported.add(EXTENSION_RDBMS_FETCH_DIRECTION);
        return supported;
    }

    /**
     * Method to return if the query results should have the range checked and unnecessary rows
     * discarded. This is for where the query language has specified a range but the datastore doesn't
     * allow removal of unnecessary results in the query itself (so has to be done in post-processing).
     * This implementation returns false and so should be overridden by query languages to match their
     * capabilities.
     * @return Whether to apply range checks in post-processing of results.
     */
    protected boolean applyRangeChecks()
    {
        if (range == null)
        {
            // No range specified so don't apply checks!
            return false;
        }

        RDBMSStoreManager storeMgr = (RDBMSStoreManager)ec.getStoreManager();
        RDBMSAdapter dba = (RDBMSAdapter)storeMgr.getDatastoreAdapter();
        boolean using_limit_where_clause = (dba.getRangeByLimitEndOfStatementClause(fromInclNo, toExclNo).length() > 0);
        boolean using_rownum = (dba.getRangeByRowNumberColumn().length() > 0);

        return (range != null && !using_limit_where_clause && !using_rownum);
    }

    /**
     * Method to compile the query for RDBMS for a bulk update.
     * @param parameterValues The parameter values (if any)
     * @param candidateCmd Meta-data for the candidate class
     */
    protected void compileQueryUpdate(Map parameterValues, AbstractClassMetaData candidateCmd)
    {
        Expression[] updateExprs = compilation.getExprUpdate();
        if (updateExprs == null || updateExprs.length == 0)
        {
            // Nothing to update
            return;
        }

        // Generate statement for candidate
        StatementClassMapping resultsDef = new StatementClassMapping();
        SQLStatement stmt = RDBMSQueryUtils.getStatementForCandidates(null, candidateCmd,
            resultsDef, ec, candidateClass, subclasses, result, null, null);

        Set<String> options = new HashSet<String>();
        options.add(QueryToSQLMapper.OPTION_CASE_INSENSITIVE);
        options.add(QueryToSQLMapper.OPTION_EXPLICIT_JOINS);
        QueryToSQLMapper sqlMapper = new QueryToSQLMapper(stmt, compilation, parameterValues,
            resultsDef, null, candidateCmd, getFetchPlan(), ec, null, options, extensions);
        sqlMapper.setDefaultJoinType(JoinType.INNER_JOIN);
        sqlMapper.compile();

        // The assumption here is that the SQL UPDATE statement generated will not need to use multiple
        // tables. This means that the SET clause only refers to the table of the candidate, and that
        // the WHERE clause only refers to the table of the candidate.
        // It is possible with some RDBMS e.g MySQL that support multiple table update syntax
        // that these 2 conditions don't need to be enforced.
        DatastoreAdapter dba = ((RDBMSStoreManager)ec.getStoreManager()).getDatastoreAdapter();
        if (stmt.getNumberOfTables() > 0 && !dba.supportsOption(RDBMSAdapter.UPDATE_MULTITABLE))
        {
            throw new NucleusDataStoreException("Bulk update requires use of multiple tables yet datastore doesnt allow multiple table syntax");
        }
        datastoreCompilation.setSQL(stmt.getUpdateStatement().toString());
        datastoreCompilation.setStatementParameters(stmt.getSelectStatement().getParametersForStatement());
    }

    /**
     * Method to compile the query for RDBMS for a bulk delete.
     * @param parameterValues The parameter values (if any)
     * @param candidateCmd Meta-data for the candidate class
     */
    protected void compileQueryDelete(Map parameterValues, AbstractClassMetaData candidateCmd)
    {
        ClassLoaderResolver clr = ec.getClassLoaderResolver();
        HashSet<String> subclassNames =
            ec.getStoreManager().getSubClassesForClass(candidateCmd.getFullClassName(), true, clr);
        if (subclassNames != null && !subclassNames.isEmpty())
        {
            // Check for subclasses having their own tables and hence needing multiple DELETEs
            RDBMSStoreManager storeMgr = (RDBMSStoreManager)ec.getStoreManager();
            DatastoreClass candidateTbl = storeMgr.getDatastoreClass(candidateCmd.getFullClassName(), clr);
            Iterator<String> iter = subclassNames.iterator();
            while (iter.hasNext())
            {
                String subclassName = iter.next();
                DatastoreClass subclassTbl = storeMgr.getDatastoreClass(subclassName, clr);
                if (candidateTbl != null && candidateTbl != subclassTbl)
                {
                    throw new NucleusException("Bulk delete doesn't currently support deletion "+
                        "where the candidate table also has subclasses in their own tables");
                }
            }
            if (candidateTbl.getSuperDatastoreClass() != null)
            {
                throw new NucleusException("Bulk delete doesn't currently support deletion " +
                    "where the candidate table also has superclass table(s)");
            }
        }

        // Generate statement for candidate
        StatementClassMapping resultsDef = new StatementClassMapping();
        SQLStatement stmt = RDBMSQueryUtils.getStatementForCandidates(null, candidateCmd,
            resultsDef, ec, candidateClass, subclasses, result, null, null);

        Set<String> options = new HashSet<String>();
        options.add(QueryToSQLMapper.OPTION_CASE_INSENSITIVE);
        options.add(QueryToSQLMapper.OPTION_EXPLICIT_JOINS);
        QueryToSQLMapper sqlMapper = new QueryToSQLMapper(stmt, compilation, parameterValues,
            resultsDef, null, candidateCmd, getFetchPlan(), ec, null, options, extensions);
        sqlMapper.setDefaultJoinType(JoinType.INNER_JOIN);
        sqlMapper.compile();

        // This currently assumes that we only need to delete from one table.
        // TODO Delete entity from all populated tables
        datastoreCompilation.setSQL(stmt.getDeleteStatement().toString());
        datastoreCompilation.setStatementParameters(stmt.getSelectStatement().getParametersForStatement());
    }

    /**
     * Add a vendor-specific extension this query.
     * Intercepts any setting of in-memory evaluation, so we can throw away any datastore compilation.
     * @param key the extension key
     * @param value the extension value
     */
    public void addExtension(String key, Object value)
    {
        if (key != null && key.equals(EXTENSION_EVALUATE_IN_MEMORY))
        {
            datastoreCompilation = null;
            getQueryManager().deleteDatastoreQueryCompilation(getStoreManager().getQueryCacheKey(), getLanguage(),
                toString());
        }
        super.addExtension(key, value);
    }

    /**
     * Set multiple extensions, or use null to clear extensions.
     * Intercepts any settong of in-memory evaluation, so we can throw away any datastore compilation.
     * @param extensions
     */
    public void setExtensions(Map extensions)
    {
        if (extensions != null && extensions.containsKey(EXTENSION_EVALUATE_IN_MEMORY))
        {
            datastoreCompilation = null;
            getQueryManager().deleteDatastoreQueryCompilation(getStoreManager().getQueryCacheKey(), getLanguage(),
                toString());
        }
        super.setExtensions(extensions);
    }
}
TOP

Related Classes of org.datanucleus.store.rdbms.query.JPQLQuery

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.