Package org.datanucleus.store.rdbms.query

Source Code of org.datanucleus.store.rdbms.query.QueryToSQLMapper$SQLTableMapping

/**********************************************************************
Copyright (c) 2008 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.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;

import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.FetchPlan;
import org.datanucleus.exceptions.ClassNotResolvedException;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.FieldPersistenceModifier;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.MetaDataManager;
import org.datanucleus.metadata.Relation;
import org.datanucleus.metadata.VersionMetaData;
import org.datanucleus.metadata.VersionStrategy;
import org.datanucleus.query.QueryUtils;
import org.datanucleus.query.compiler.CompilationComponent;
import org.datanucleus.query.compiler.QueryCompilation;
import org.datanucleus.query.evaluator.AbstractExpressionEvaluator;
import org.datanucleus.query.expression.ArrayExpression;
import org.datanucleus.query.expression.CaseExpression;
import org.datanucleus.query.expression.ClassExpression;
import org.datanucleus.query.expression.CreatorExpression;
import org.datanucleus.query.expression.DyadicExpression;
import org.datanucleus.query.expression.Expression;
import org.datanucleus.query.expression.Expression.Operator;
import org.datanucleus.query.expression.InvokeExpression;
import org.datanucleus.query.expression.JoinExpression;
import org.datanucleus.query.expression.Literal;
import org.datanucleus.query.expression.OrderExpression;
import org.datanucleus.query.expression.ParameterExpression;
import org.datanucleus.query.expression.PrimaryExpression;
import org.datanucleus.query.expression.SubqueryExpression;
import org.datanucleus.query.expression.VariableExpression;
import org.datanucleus.query.expression.JoinExpression.JoinType;
import org.datanucleus.query.symbol.Symbol;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.ObjectProvider;
import org.datanucleus.store.VersionHelper;
import org.datanucleus.store.mapped.DatastoreClass;
import org.datanucleus.store.mapped.DatastoreContainerObject;
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.mapped.mapping.AbstractContainerMapping;
import org.datanucleus.store.mapped.mapping.EmbeddedMapping;
import org.datanucleus.store.mapped.mapping.JavaTypeMapping;
import org.datanucleus.store.mapped.mapping.MappingConsumer;
import org.datanucleus.store.mapped.mapping.OIDMapping;
import org.datanucleus.store.mapped.mapping.PersistableIdMapping;
import org.datanucleus.store.mapped.mapping.PersistableMapping;
import org.datanucleus.store.mapped.mapping.ReferenceMapping;
import org.datanucleus.store.mapped.mapping.StringMapping;
import org.datanucleus.store.mapped.mapping.TemporalMapping;
import org.datanucleus.store.query.QueryCompilerSyntaxException;
import org.datanucleus.store.rdbms.RDBMSStoreManager;
import org.datanucleus.store.rdbms.sql.SQLStatement;
import org.datanucleus.store.rdbms.sql.SQLStatementHelper;
import org.datanucleus.store.rdbms.sql.SQLTable;
import org.datanucleus.store.rdbms.sql.expression.BooleanExpression;
import org.datanucleus.store.rdbms.sql.expression.BooleanLiteral;
import org.datanucleus.store.rdbms.sql.expression.BooleanSubqueryExpression;
import org.datanucleus.store.rdbms.sql.expression.ExpressionUtils;
import org.datanucleus.store.rdbms.sql.expression.IntegerLiteral;
import org.datanucleus.store.rdbms.sql.expression.NewObjectExpression;
import org.datanucleus.store.rdbms.sql.expression.NumericExpression;
import org.datanucleus.store.rdbms.sql.expression.NumericSubqueryExpression;
import org.datanucleus.store.rdbms.sql.expression.ParameterLiteral;
import org.datanucleus.store.rdbms.sql.expression.SQLExpression;
import org.datanucleus.store.rdbms.sql.expression.SQLExpressionFactory;
import org.datanucleus.store.rdbms.sql.expression.SQLLiteral;
import org.datanucleus.store.rdbms.sql.expression.StringSubqueryExpression;
import org.datanucleus.store.rdbms.sql.expression.SubqueryExpressionComponent;
import org.datanucleus.store.rdbms.sql.expression.TemporalLiteral;
import org.datanucleus.store.rdbms.sql.expression.TemporalSubqueryExpression;
import org.datanucleus.store.rdbms.sql.expression.UnboundExpression;
import org.datanucleus.store.rdbms.table.ClassTable;
import org.datanucleus.store.rdbms.table.CollectionTable;
import org.datanucleus.store.rdbms.table.ElementContainerTable;
import org.datanucleus.util.ClassUtils;
import org.datanucleus.util.Imports;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;

/**
* Class which maps a compiled (generic) query to an SQL query for RDBMS datastores.
* Takes in an SQLStatement, and use of compile() will update the SQLStatement to reflect
* the filter, result, grouping, having, ordering etc.
* <p>
* Supports the following extensions currently :-
* <ul>
* <li><b>datanucleus.query.jdoql.{varName}.join</b> defines the join type for that alias. This only currently
* applies if the variable is (finally) bound using the equality operator (e.g var.field == this.field).
* The extension should be set to "LEFTOUTERJOIN", "INNERJOIN"</li>
* </ul>
* </p>
*/
public class QueryToSQLMapper extends AbstractExpressionEvaluator implements QueryGenerator
{
    /** Localiser for messages. */
    protected static final Localiser LOCALISER = Localiser.getInstance(
        "org.datanucleus.Localisation", org.datanucleus.ClassConstants.NUCLEUS_CONTEXT_LOADER);

    /** Localiser for messages. */
    protected static final Localiser LOCALISER_RDBMS = Localiser.getInstance(
        "org.datanucleus.store.rdbms.Localisation", RDBMSStoreManager.class.getClassLoader());

    public static final String OPTION_CASE_INSENSITIVE = "CASE_INSENSITIVE";
    public static final String OPTION_EXPLICIT_JOINS = "EXPLICIT_JOINS";
    public static final String OPTION_BULK_UPDATE_VERSION = "BULK_UPDATE_VERSION";

    final String candidateAlias;

    final AbstractClassMetaData candidateCmd;

    final QueryCompilation compilation;

    /** Input parameter values, keyed by the parameter name. Will be null if compiled pre-execution. */
    final Map parameters;

    /** Work Map for keying parameter value for the name, for case where parameters input as positional. */
    Map<String, Object> parameterValueByName = null;

    Map<Integer, String> paramNameByPosition = null;

    /** Positional parameter that we are up to (-1 implies not being used). */
    int positionalParamNumber = -1;

    /** Map of query extensions defining behaviour on the compilation. */
    Map<String, Object> extensionsByName = null;

    /** SQL statement that we are updating. */
    final SQLStatement stmt;

    /** Definition of mapping for the results of this SQL statement (candidate). */
    final StatementClassMapping resultDefinitionForClass;

    /** Definition of mapping for the results of this SQL statement (result). */
    final StatementResultMapping resultDefinition;

    Map<Object, SQLExpression> expressionForParameter;

    final RDBMSStoreManager storeMgr;

    final FetchPlan fetchPlan;

    final SQLExpressionFactory exprFactory;

    ExecutionContext ec;

    ClassLoaderResolver clr;

    Imports importsDefinition = null;

    Map<String, Object> compileProperties = new HashMap();

    /** State variable for the component being compiled. */
    CompilationComponent compileComponent;

    /** Stack of expressions, used for compilation of the query into SQL. */
    Stack<SQLExpression> stack = new Stack();

    /** Map of SQLTable/mapping keyed by the name of the primary that it relates to. */
    Map<String, SQLTableMapping> sqlTableByPrimary = new HashMap<String, SQLTableMapping>();

    /** Map of the join primary expression string keyed by the join alias (explicit joins only). */
    Map<String, String> explicitJoinPrimaryByAlias = null;

    Map<String, JavaTypeMapping> paramMappingForName = new HashMap();

    /** Options for the SQL generation process. See OPTION_xxx above. */
    Set<String> options = new HashSet<String>();

    /** Parent mapper if we are processing a subquery. */
    public QueryToSQLMapper parentMapper = null;

    org.datanucleus.store.rdbms.sql.SQLJoin.JoinType defaultJoinType = null;

    /**
     * State variable for whether this query is precompilable (hence whether it is cacheable).
     * Or in other words, whether we can compile it without knowing parameter values.
     */
    boolean precompilable = true;

    static class SQLTableMapping
    {
        SQLTable table;
        AbstractClassMetaData cmd;
        JavaTypeMapping mapping;

        public SQLTableMapping(SQLTable tbl, AbstractClassMetaData cmd, JavaTypeMapping m)
        {
            this.table = tbl;
            this.cmd = cmd;
            this.mapping = m;
        }

        public String toString()
        {
            return "SQLTableMapping: tbl=" + table +
                " class=" + (cmd != null ? cmd.getFullClassName() : "null") +
                " mapping=" + mapping;
        }
    }

    /**
     * Constructor.
     * @param stmt SQL Statement to update with the query contents.
     * @param compilation The generic query compilation
     * @param parameters Parameters needed
     * @param resultDefForClass Definition of results for the statement (populated here)
     * @param resultDef Definition of results if we have a result clause (populated here)
     * @param cmd Metadata for the candidate class
     * @param fetchPlan Fetch Plan to apply
     * @param ec execution context
     * @param importsDefinition Import definition to use in class lookups (optional)
     * @param caseInsensitive Whether aliases are case insensitive (like in JPQL)
     * @param explicitJoins Whether joins are defined explicitly (like in JPQL)
     * @param extensions Any query extensions that may define compilation behaviour
     */
    public QueryToSQLMapper(SQLStatement stmt, QueryCompilation compilation, Map parameters,
            StatementClassMapping resultDefForClass, StatementResultMapping resultDef,
            AbstractClassMetaData cmd, FetchPlan fetchPlan, ExecutionContext ec, Imports importsDefinition,
            Set<String> options, Map<String, Object> extensions)
    {
        this.parameters = parameters;
        this.compilation = compilation;
        this.stmt = stmt;
        this.resultDefinitionForClass = resultDefForClass;
        this.resultDefinition = resultDef;
        this.candidateAlias = compilation.getCandidateAlias();
        this.fetchPlan = fetchPlan;
        this.storeMgr = stmt.getRDBMSManager();
        this.exprFactory = stmt.getRDBMSManager().getSQLExpressionFactory();
        this.candidateCmd = cmd;
        this.ec = ec;
        this.clr = ec.getClassLoaderResolver();
        this.importsDefinition = importsDefinition;
        if (options != null)
        {
            this.options.addAll(options);
        }
        this.extensionsByName = extensions;

        this.stmt.setQueryGenerator(this);

        // Register the candidate
        SQLTableMapping tblMapping =
            new SQLTableMapping(stmt.getPrimaryTable(), candidateCmd, stmt.getPrimaryTable().getTable().getIdMapping());
        setSQLTableMappingForAlias(candidateAlias, tblMapping);
    }

    /**
     * Method to set the default join type to be used.
     * By default we use the nullability of the field to define whether to use LEFT OUTER, or INNER.
     * In JPQL if a relation isn't specified explicitly in the FROM clause then it should use INNER
     * hence where this method will be used, to define the default.
     * @param joinType The default join type
     */
    void setDefaultJoinType(org.datanucleus.store.rdbms.sql.SQLJoin.JoinType joinType)
    {
        this.defaultJoinType = joinType;
    }

    void setParentMapper(QueryToSQLMapper parent)
    {
        this.parentMapper = parent;
    }

    /**
     * Accessor for the query language that this query pertains to.
     * Can be used to decide how to handle the input.
     * @return The query language
     */
    public String getQueryLanguage()
    {
        return compilation.getQueryLanguage();
    }

    /* (non-Javadoc)
     * @see org.datanucleus.store.rdbms.query.QueryGenerator#getClassLoaderResolver()
     */
    public ClassLoaderResolver getClassLoaderResolver()
    {
        return clr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.store.rdbms.query.QueryGenerator#getCompilationComponent()
     */
    public CompilationComponent getCompilationComponent()
    {
        return compileComponent;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.store.rdbms.query.QueryGenerator#getObjectManager()
     */
    public ExecutionContext getObjectManager()
    {
        return ec;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.store.rdbms.query.QueryGenerator#getProperty(java.lang.String)
     */
    public Object getProperty(String name)
    {
        return compileProperties.get(name);
    }

    /**
     * Accessor for whether the query is precompilable (doesn't need to know parameter values
     * to be able to compile it).
     * @return Whether the query is precompilable and hence cacheable
     */
    public boolean isPrecompilable()
    {
        return precompilable;
    }

    protected void setNotPrecompilable()
    {
        if (parentMapper != null)
        {
            parentMapper.setNotPrecompilable();
        }
        this.precompilable = false;
    }

    /**
     * Convenience accessor for a map of the parameter name keyed by its position.
     * This is only available after <pre>compile</pre> is called.
     * @return Map of parameter name keyed by position
     */
    public Map<Integer, String> getParameterNameByPosition()
    {
        return paramNameByPosition;
    }

    /**
     * Method to update the supplied SQLStatement with the components of the specified query.
     * During the compilation process this updates the SQLStatement "compileComponent" to the
     * component of the query being compiled.
     */
    public void compile()
    {
        compileFrom();
        compileFilter();

        // Set whether the statement returns distinct. Do this before processing the result in case
        // the datastore doesn't allow select of some field types when used with DISTINCT
        if (compilation.getResultDistinct())
        {
            stmt.setDistinct(true);
        }
        else if (!options.contains(OPTION_EXPLICIT_JOINS) && compilation.getExprResult() == null)
        {
            // Joins are made implicitly and no result so set distinct based on whether joining to other table groups
            if (stmt.getNumberOfTableGroups() > 1)
            {
                // Queries against an extent always consider only distinct candidate instances, regardless of
                // whether distinct is specified (JDO spec)
                stmt.setDistinct(true);
            }
        }

        compileResult();
        compileGrouping();
        compileHaving();
        compileOrdering();

        // Check for variables that haven't been bound to the query (declared but not used)
        Collection<String> symbols = compilation.getSymbolTable().getSymbolNames();
        Iterator<String> symIter = symbols.iterator();
        while (symIter.hasNext())
        {
            Symbol sym = compilation.getSymbolTable().getSymbol(symIter.next());
            if (sym.getType() == Symbol.VARIABLE)
            {
                if (compilation.getCompilationForSubquery(sym.getQualifiedName()) == null &&
                    !hasSQLTableMappingForAlias(sym.getQualifiedName()))
                {
                    // Variable not a subquery, nor had its table allocated
                    throw new QueryCompilerSyntaxException("Query has variable \"" + sym.getQualifiedName() + "\" which is not bound to the query");
                }
            }
        }
    }

    /**
     * Method to compile the FROM clause of the query into the SQLStatement.
     */
    protected void compileFrom()
    {
        if (compilation.getExprFrom() != null)
        {
            // Process all ClassExpression(s) in the FROM, adding joins to the statement as required
            compileComponent = CompilationComponent.FROM;
            Expression[] fromExprs = compilation.getExprFrom();
            for (int i=0;i<fromExprs.length;i++)
            {
                ClassExpression clsExpr = (ClassExpression)fromExprs[i];
                compileFromClassExpression(clsExpr);
            }
            compileComponent = null;
        }
    }

    /**
     * Method to compile the WHERE clause of the query into the SQLStatement.
     */
    protected void compileFilter()
    {
        if (compilation.getExprFilter() != null)
        {
            // Apply the filter to the SQLStatement
            compileComponent = CompilationComponent.FILTER;
            if (QueryUtils.expressionHasOrOperator(compilation.getExprFilter()))
            {
                compileProperties.put("Filter.OR", true);
            }
            if (QueryUtils.expressionHasNotOperator(compilation.getExprFilter()))
            {
                // Really we want to know if there is a NOT contains, but just check NOT for now
                compileProperties.put("Filter.NOT", true);
            }

            BooleanExpression filterExpr = (BooleanExpression)compilation.getExprFilter().evaluate(this);
            filterExpr = getBooleanExpressionForUseInFilter(filterExpr);
            stmt.whereAnd(filterExpr, true);
            compileComponent = null;
        }
    }

    /**
     * Method to compile the result clause of the query into the SQLStatement.
     * Note that this also compiles
     * <ul>
     * <li>queries of the candidate (no specified result), selecting the candidate field(s).</li>
     * <li>update clauses of queries</li>
     * </ul>
     */
    protected void compileResult()
    {
        if (compilation.getExprUpdate() != null)
        {
            // Update statement, so generate update expression(s)
            compileComponent = CompilationComponent.UPDATE;
            Expression[] updateExprs = compilation.getExprUpdate();
            SQLExpression[] updateSqlExprs = new SQLExpression[updateExprs.length];
            for (int i=0;i<updateExprs.length;i++)
            {
                // "field = value"
                DyadicExpression updateExpr = (DyadicExpression)updateExprs[i];

                // Left-side has to be PrimaryExpression
                SQLExpression leftSqlExpr = null;
                if (updateExpr.getLeft() instanceof PrimaryExpression)
                {
                    processPrimaryExpression((PrimaryExpression)updateExpr.getLeft());
                    leftSqlExpr = stack.pop();
                }
                else
                {
                    throw new NucleusException("Dont currently support update clause containing left expression of type " + updateExpr.getLeft());
                }

                // Right-side can be Literal, or Parameter, or PrimaryExpression
                SQLExpression rightSqlExpr = null;
                if (updateExpr.getRight() instanceof Literal)
                {
                    processLiteral((Literal)updateExpr.getRight());
                    rightSqlExpr = stack.pop();
                }
                else if (updateExpr.getRight() instanceof ParameterExpression)
                {
                    processParameterExpression((ParameterExpression)updateExpr.getRight());
                    rightSqlExpr = stack.pop();
                }
                else if (updateExpr.getRight() instanceof PrimaryExpression)
                {
                    processPrimaryExpression((PrimaryExpression)updateExpr.getRight());
                    rightSqlExpr = stack.pop();
                }
                else if (updateExpr.getRight() instanceof DyadicExpression)
                {
                    updateExpr.getRight().evaluate(this);
                    rightSqlExpr = stack.pop();
                }
                else if (updateExpr.getRight() instanceof CaseExpression)
                {
                    updateExpr.getRight().evaluate(this);
                    rightSqlExpr = stack.pop();
                }
                else
                {
                    throw new NucleusException("Dont currently support update clause containing right expression of type " + updateExpr.getRight());
                }

                if (leftSqlExpr != null && rightSqlExpr != null)
                {
                    updateSqlExprs[i] = leftSqlExpr.eq(rightSqlExpr);
                }
            }

            if (candidateCmd.hasVersionStrategy() && options.contains(OPTION_BULK_UPDATE_VERSION))
            {
                SQLExpression updateSqlExpr = null;

                ClassTable table = (ClassTable)stmt.getPrimaryTable().getTable();
                JavaTypeMapping verMapping = table.getVersionMapping(true);
                ClassTable verTable = table.getTableManagingMapping(verMapping);
                VersionMetaData vermd = candidateCmd.getVersionMetaData();
                if (vermd.getVersionStrategy() == VersionStrategy.VERSION_NUMBER)
                {
                    // Increment the version
                    SQLTable verSqlTbl = stmt.getTable(verTable, stmt.getPrimaryTable().getGroupName());
                    SQLExpression verExpr = new NumericExpression(stmt, verSqlTbl, verMapping);
                    SQLExpression incrExpr = verExpr.add(new IntegerLiteral(stmt,
                        stmt.getSQLExpressionFactory().getMappingForType(Integer.class, false), new Integer(1), null));

                    updateSqlExpr = verExpr.eq(incrExpr);

                    SQLExpression[] oldArray = updateSqlExprs;
                    updateSqlExprs = new SQLExpression[oldArray.length+1];
                    System.arraycopy(oldArray, 0, updateSqlExprs, 0, oldArray.length);
                    updateSqlExprs[oldArray.length] = updateSqlExpr;
                }
                else if (vermd.getVersionStrategy() == VersionStrategy.DATE_TIME)
                {
                    // Set version to the time of update
                    SQLTable verSqlTbl = stmt.getTable(verTable, stmt.getPrimaryTable().getGroupName());
                    SQLExpression verExpr = new NumericExpression(stmt, verSqlTbl, verMapping);
                    Object newVersion = VersionHelper.getNextVersion(vermd.getVersionStrategy(), null);
                    JavaTypeMapping valMapping = stmt.getSQLExpressionFactory().getMappingForType(newVersion.getClass(), false);
                    SQLExpression valExpr = new TemporalLiteral(stmt, valMapping, newVersion, null);

                    updateSqlExpr = verExpr.eq(valExpr);

                    SQLExpression[] oldArray = updateSqlExprs;
                    updateSqlExprs = new SQLExpression[oldArray.length+1];
                    System.arraycopy(oldArray, 0, updateSqlExprs, 0, oldArray.length);
                    updateSqlExprs[oldArray.length] = updateSqlExpr;
                }
            }

            stmt.setUpdates(updateSqlExprs);
        }
        else if (compilation.getExprResult() != null)
        {
            compileComponent = CompilationComponent.RESULT;

            // Select any result expressions
            Expression[] resultExprs = compilation.getExprResult();
            for (int i=0;i<resultExprs.length;i++)
            {
                String alias = resultExprs[i].getAlias();
                if (resultExprs[i] instanceof InvokeExpression)
                {
                    processInvokeExpression((InvokeExpression)resultExprs[i]);
                    SQLExpression sqlExpr = stack.pop();
                    validateExpressionForResult(sqlExpr);
                    int[] cols = stmt.select(sqlExpr, alias);
                    StatementMappingIndex idx = new StatementMappingIndex(sqlExpr.getJavaTypeMapping());
                    idx.setColumnPositions(cols);
                    if (alias != null)
                    {
                        idx.setColumnAlias(alias);
                    }
                    resultDefinition.addMappingForResultExpression(i, idx);
                }
                else if (resultExprs[i] instanceof PrimaryExpression)
                {
                    PrimaryExpression primExpr = (PrimaryExpression)resultExprs[i];
                    if (primExpr.getId().equals(candidateAlias))
                    {
                        // "this", so select fetch plan fields
                        StatementClassMapping map = new StatementClassMapping(candidateCmd.getFullClassName(), null);
                        SQLStatementHelper.selectFetchPlanOfCandidateInStatement(stmt, map,
                            candidateCmd, fetchPlan, 1);
                        resultDefinition.addMappingForResultExpression(i, map);
                    }
                    else
                    {
                        processPrimaryExpression(primExpr);
                        SQLExpression sqlExpr = stack.pop();
                        validateExpressionForResult(sqlExpr);
                        if (sqlExpr instanceof SQLLiteral)
                        {
                            int[] cols = stmt.select(sqlExpr, alias);
                            StatementMappingIndex idx = new StatementMappingIndex(sqlExpr.getJavaTypeMapping());
                            idx.setColumnPositions(cols);
                            if (alias != null)
                            {
                                idx.setColumnAlias(alias);
                            }
                            resultDefinition.addMappingForResultExpression(i, idx);
                        }
                        else
                        {
                            int[] cols = stmt.select(sqlExpr.getSQLTable(), sqlExpr.getJavaTypeMapping(), alias);
                            StatementMappingIndex idx = new StatementMappingIndex(sqlExpr.getJavaTypeMapping());
                            idx.setColumnPositions(cols);
                            if (alias != null)
                            {
                                idx.setColumnAlias(alias);
                            }
                            resultDefinition.addMappingForResultExpression(i, idx);
                        }
                    }
                }
                else if (resultExprs[i] instanceof ParameterExpression)
                {
                    processParameterExpression((ParameterExpression)resultExprs[i], true); // Params are literals in result
                    SQLExpression sqlExpr = stack.pop();
                    validateExpressionForResult(sqlExpr);
                    int[] cols = stmt.select(sqlExpr, alias);
                    StatementMappingIndex idx = new StatementMappingIndex(sqlExpr.getJavaTypeMapping());
                    idx.setColumnPositions(cols);
                    if (alias != null)
                    {
                        idx.setColumnAlias(alias);
                    }
                    resultDefinition.addMappingForResultExpression(i, idx);
                }
                else if (resultExprs[i] instanceof VariableExpression)
                {
                    processVariableExpression((VariableExpression)resultExprs[i]);
                    SQLExpression sqlExpr = stack.pop();
                    validateExpressionForResult(sqlExpr);
                    if (sqlExpr instanceof UnboundExpression)
                    {
                        // Variable wasn't bound in the compilation so far, so handle as cross-join
                        processUnboundExpression((UnboundExpression) sqlExpr);
                        sqlExpr = stack.pop();
                        NucleusLogger.QUERY.debug(">> QueryToSQL.exprResult variable was still unbound, so binding via cross-join");
                    }
                    StatementMappingIndex idx = new StatementMappingIndex(sqlExpr.getJavaTypeMapping());
                    int[] cols = stmt.select(sqlExpr, alias);
                    idx.setColumnPositions(cols);
                    if (alias != null)
                    {
                        idx.setColumnAlias(alias);
                    }
                    resultDefinition.addMappingForResultExpression(i, idx);
                }
                else if (resultExprs[i] instanceof Literal)
                {
                    processLiteral((Literal)resultExprs[i]);
                    SQLExpression sqlExpr = stack.pop();
                    validateExpressionForResult(sqlExpr);
                    int[] cols = stmt.select(sqlExpr, alias);
                    StatementMappingIndex idx = new StatementMappingIndex(sqlExpr.getJavaTypeMapping());
                    idx.setColumnPositions(cols);
                    if (alias != null)
                    {
                        idx.setColumnAlias(alias);
                    }
                    resultDefinition.addMappingForResultExpression(i, idx);
                }
                else if (resultExprs[i] instanceof CreatorExpression)
                {
                    processCreatorExpression((CreatorExpression)resultExprs[i]);
                    NewObjectExpression sqlExpr = (NewObjectExpression)stack.pop();
                    StatementNewObjectMapping stmtMap = getStatementMappingForNewObjectExpression(sqlExpr);
                    resultDefinition.addMappingForResultExpression(i, stmtMap);
                }
                else if (resultExprs[i] instanceof DyadicExpression)
                {
                    // Something like {a+b} perhaps. Maybe we should check for invalid expressions?
                    resultExprs[i].evaluate(this);
                    SQLExpression sqlExpr = stack.pop();
                    int[] cols = stmt.select(sqlExpr, alias);
                    StatementMappingIndex idx = new StatementMappingIndex(sqlExpr.getJavaTypeMapping());
                    idx.setColumnPositions(cols);
                    if (alias != null)
                    {
                        idx.setColumnAlias(alias);
                    }
                    resultDefinition.addMappingForResultExpression(i, idx);
                }
                else if (resultExprs[i] instanceof CaseExpression)
                {
                    resultExprs[i].evaluate(this);
                    SQLExpression sqlExpr = stack.pop();
                    int[] cols = stmt.select(sqlExpr, alias);
                    StatementMappingIndex idx = new StatementMappingIndex(sqlExpr.getJavaTypeMapping());
                    idx.setColumnPositions(cols);
                    if (alias != null)
                    {
                        idx.setColumnAlias(alias);
                    }
                    resultDefinition.addMappingForResultExpression(i, idx);
                }
                else
                {
                    throw new NucleusException("Dont currently support result clause containing expression of type " + resultExprs[i]);
                }
            }

            if (stmt.getNumberOfSelects() == 0)
            {
                // Nothing selected so likely the user had some "new MyClass()" expression, so select "1"
                stmt.select(exprFactory.newLiteral(stmt,
                    storeMgr.getMappingManager().getMapping(Integer.class), 1), null);
            }
        }
        else
        {
            // Select of the candidate (no result)
            compileComponent = CompilationComponent.RESULT;
            if (candidateCmd.getIdentityType() == IdentityType.NONDURABLE)
            {
                // Nondurable identity cases have no "id" for later fetching so get all fields now
                if (NucleusLogger.QUERY.isDebugEnabled())
                {
                    NucleusLogger.QUERY.debug(LOCALISER_RDBMS.msg("052520", candidateCmd.getFullClassName()));
                }
                fetchPlan.setGroup("all");
            }

            if (stmt.allUnionsForSamePrimaryTable())
            {
                // Select fetch-plan members of the candidate (and optionally the next level of sub-objects)
                // Don't select next level when we are processing a subquery
                SQLStatementHelper.selectFetchPlanOfCandidateInStatement(stmt, resultDefinitionForClass,
                    candidateCmd, fetchPlan, parentMapper == null ? 1 : 0);
            }
            else
            {
                // Select identity of the candidates since use different base tables
                // TODO complete-table will come through here but maybe ought to be treated differently
                SQLStatementHelper.selectIdentityOfCandidateInStatement(stmt, resultDefinitionForClass,
                    candidateCmd);
            }
        }
        compileComponent = null;
    }

    /**
     * Method that validates that the specified expression is valid for use in a result clause.
     * Throws a NucleusUserException if it finds a multi-value mapping selected (collection, map).
     * @param sqlExpr The SQLExpression
     */
    protected void validateExpressionForResult(SQLExpression sqlExpr)
    {
        JavaTypeMapping m = sqlExpr.getJavaTypeMapping();
        if (m != null)
        {
            if (m instanceof AbstractContainerMapping)
            {
                throw new NucleusUserException(LOCALISER.msg("021213"));
            }
        }
    }

    /**
     * Method to compile the grouping clause of the query into the SQLStatement.
     */
    protected void compileGrouping()
    {
        if (compilation.getExprGrouping() != null)
        {
            // Apply any grouping to the statement
            compileComponent = CompilationComponent.GROUPING;
            Expression[] groupExprs = compilation.getExprGrouping();
            for (int i = 0; i < groupExprs.length; i++)
            {
                Expression groupExpr = groupExprs[i];
                SQLExpression sqlGroupExpr = (SQLExpression)groupExpr.evaluate(this);
                stmt.addGroupingExpression(sqlGroupExpr);
            }
            compileComponent = null;
        }
    }

    /**
     * Method to compile the having clause of the query into the SQLStatement.
     */
    protected void compileHaving()
    {
        if (compilation.getExprHaving() != null)
        {
            // Apply any having to the statement
            compileComponent = CompilationComponent.HAVING;
            Expression havingExpr = compilation.getExprHaving();
            Object havingEval = havingExpr.evaluate(this);
            if (!(havingEval instanceof BooleanExpression))
            {
                // Non-boolean having clause should be user exception
                throw new NucleusUserException(LOCALISER.msg("021051", havingExpr));
            }
            stmt.setHaving((BooleanExpression)havingEval);
            compileComponent = null;
        }
    }

    /**
     * Method to compile the ordering clause of the query into the SQLStatement.
     */
    protected void compileOrdering()
    {
        if (compilation.getExprOrdering() != null)
        {
            compileComponent = CompilationComponent.ORDERING;
            Expression[] orderingExpr = compilation.getExprOrdering();
            SQLExpression[] orderSqlExprs = new SQLExpression[orderingExpr.length];
            boolean[] directions = new boolean[orderingExpr.length];
            for (int i = 0; i < orderingExpr.length; i++)
            {
                OrderExpression orderExpr = (OrderExpression)orderingExpr[i];
                orderSqlExprs[i] = (SQLExpression)orderExpr.getLeft().evaluate(this);
                String orderDir = orderExpr.getSortOrder();
                directions[i] = ((orderDir == null || orderDir.equals("ascending")) ? false : true);
            }
            stmt.setOrdering(orderSqlExprs, directions);
            compileComponent = null;
        }
    }

    /**
     * Method to take a ClassExpression (in a FROM clause) and process the candidate and any
     * linked JoinExpression(s), adding joins to the SQLStatement as required.
     * @param clsExpr The ClassExpression
     */
    protected void compileFromClassExpression(ClassExpression clsExpr)
    {
        Symbol clsExprSym = clsExpr.getSymbol();
        Class baseCls = (clsExprSym != null ? clsExprSym.getValueType() : null);
        SQLTable candSqlTbl = stmt.getPrimaryTable();
        MetaDataManager mmgr = storeMgr.getMetaDataManager();
        AbstractClassMetaData cmd = mmgr.getMetaDataForClass(baseCls, clr);
        if (baseCls != null && baseCls != compilation.getCandidateClass())
        {
            // Not candidate class so must be cross join (JPA spec 4.4.5)
            DatastoreClass candTbl = storeMgr.getDatastoreClass(baseCls.getName(), clr);
            candSqlTbl = stmt.crossJoin(candTbl, clsExpr.getAlias(), null);
            SQLTableMapping tblMapping = new SQLTableMapping(candSqlTbl, cmd, candTbl.getIdMapping());
            setSQLTableMappingForAlias(clsExpr.getAlias(), tblMapping);
        }

        if (clsExpr.getCandidateExpression() != null && parentMapper != null)
        {
            // User defined the candidate of the subquery as an implied join to the outer query
            // e.g SELECT c FROM Customer c WHERE EXISTS (SELECT o FROM c.orders o ...)
            // so add the join(s) to the outer query
            String[] tokens = StringUtils.split(clsExpr.getCandidateExpression(), ".");

            String leftAlias = tokens[0];
            SQLTableMapping outerSqlTblMapping = parentMapper.getSQLTableMappingForAlias(leftAlias);
            AbstractClassMetaData leftCmd = outerSqlTblMapping.cmd;

            // Get array of the left-right sides of this expression so we can work back from the subquery candidate
            AbstractMemberMetaData[] leftMmds = new AbstractMemberMetaData[tokens.length-1];
            AbstractMemberMetaData[] rightMmds = new AbstractMemberMetaData[tokens.length-1];
            for (int i=0;i<tokens.length-1;i++)
            {
                String joinedField = tokens[i+1];

                AbstractMemberMetaData leftMmd = leftCmd.getMetaDataForMember(joinedField);
                AbstractMemberMetaData rightMmd = null;
                AbstractClassMetaData rightCmd = null;
                int relationType = leftMmd.getRelationType(clr);

                switch (relationType)
                {
                    case Relation.ONE_TO_ONE_BI :
                    case Relation.ONE_TO_MANY_BI :
                    case Relation.MANY_TO_ONE_BI :
                    case Relation.MANY_TO_MANY_BI :
                        rightMmd = leftMmd.getRelatedMemberMetaData(clr)[0]; // Take first possible
                        rightCmd = rightMmd.getAbstractClassMetaData();
                        break;
                    case Relation.ONE_TO_ONE_UNI :
                        rightCmd = mmgr.getMetaDataForClass(leftMmd.getType(), clr);
                        rightMmd = null;
                        break;
                    case Relation.ONE_TO_MANY_UNI :
                        if (leftMmd.hasCollection())
                        {
                            rightCmd = mmgr.getMetaDataForClass(leftMmd.getCollection().getElementType(), clr);
                        }
                        else if (leftMmd.hasMap())
                        {
                            rightCmd = mmgr.getMetaDataForClass(leftMmd.getMap().getValueType(), clr);
                        }
                        break;
                    default :
                        throw new NucleusUserException(
                            "Subquery has been specified with a candidate-expression that" +
                            " includes \"" + tokens[i] + "\" that isnt a relation field!!");
                }

                leftMmds[i] = leftMmd;
                rightMmds[i] = rightMmd;
                leftCmd = rightCmd;
            }

            // Work from subquery candidate back to outer query table, adding joins and where clause as appropriate
            SQLTable rSqlTbl = candSqlTbl;
            SQLTable outerSqlTbl = outerSqlTblMapping.table;
            for (int i=leftMmds.length-1;i>=0;i--)
            {
                AbstractMemberMetaData leftMmd = leftMmds[i];
                AbstractMemberMetaData rightMmd = rightMmds[i];
                DatastoreClass leftTbl = storeMgr.getDatastoreClass(leftMmd.getClassName(true), clr);
                SQLTable lSqlTbl = null;
                int relationType = leftMmd.getRelationType(clr);

                switch (relationType)
                {
                    case Relation.ONE_TO_ONE_UNI:
                    {
                        // 1-1 with FK in left table
                        if (i == 0)
                        {
                            // Add where clause right table to outer table
                            SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(),
                                outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                            SQLExpression rightExpr = exprFactory.newExpression(stmt,
                                rSqlTbl, rSqlTbl.getTable().getIdMapping());
                            stmt.whereAnd(outerExpr.eq(rightExpr), false);
                        }
                        else
                        {
                            // Join to left table
                            JavaTypeMapping leftMapping = leftTbl.getMemberMapping(leftMmd);
                            lSqlTbl = stmt.innerJoin(rSqlTbl, rSqlTbl.getTable().getIdMapping(),
                                leftTbl, null, leftMapping, null, null);
                        }

                        break;
                    }
                    case Relation.ONE_TO_ONE_BI:
                    {
                        if (leftMmd.getMappedBy() != null)
                        {
                            // 1-1 with FK in right table
                            JavaTypeMapping rightMapping = rSqlTbl.getTable().getMemberMapping(rightMmd);
                            if (i == 0)
                            {
                                // Add where clause right table to outer table
                                SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(),
                                    outerSqlTbl, outerSqlTbl.getTable().getIdMapping());
                                SQLExpression rightExpr = exprFactory.newExpression(stmt,
                                    rSqlTbl, rightMapping);
                                stmt.whereAnd(outerExpr.eq(rightExpr), false);
                            }
                            else
                            {
                                // Join to left table
                                lSqlTbl = stmt.innerJoin(rSqlTbl, rightMapping,
                                    leftTbl, null, leftTbl.getIdMapping(), null, null);
                            }
                        }
                        else
                        {
                            // 1-1 with FK in left table
                            if (i == 0)
                            {
                                // Add where clause right table to outer table
                                SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(),
                                    outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                                SQLExpression rightExpr = exprFactory.newExpression(stmt,
                                    rSqlTbl, rSqlTbl.getTable().getIdMapping());
                                stmt.whereAnd(outerExpr.eq(rightExpr), false);
                            }
                            else
                            {
                                // Join to left table
                                lSqlTbl = stmt.innerJoin(rSqlTbl, rSqlTbl.getTable().getIdMapping(),
                                    leftTbl, null, leftTbl.getMemberMapping(leftMmd), null, null);
                            }
                        }
                        break;
                    }
                    case Relation.ONE_TO_MANY_UNI:
                    {
                        if (leftMmd.getJoinMetaData() != null || rightMmd.getJoinMetaData() != null)
                        {
                            // 1-N with join table to right table, so join from right to join table
                            ElementContainerTable joinTbl =
                                (ElementContainerTable)storeMgr.getDatastoreContainerObject(leftMmd);
                            SQLTable joinSqlTbl = stmt.innerJoin(rSqlTbl, rSqlTbl.getTable().getIdMapping(),
                                joinTbl, null, joinTbl.getElementMapping(), null, null);

                            if (i == 0)
                            {
                                // Add where clause join table to outer table
                                SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(),
                                    outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                                SQLExpression joinExpr = exprFactory.newExpression(stmt,
                                    joinSqlTbl, joinTbl.getOwnerMapping());
                                stmt.whereAnd(outerExpr.eq(joinExpr), false);
                            }
                            else
                            {
                                // Join to left table
                                lSqlTbl = stmt.innerJoin(joinSqlTbl, joinTbl.getOwnerMapping(),
                                    leftTbl, null, leftTbl.getIdMapping(), null, null);
                            }
                        }
                        else
                        {
                            // 1-N with FK in right table
                            if (i == 0)
                            {
                                // Add where clause right table to outer table
                                SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(),
                                    outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                                SQLExpression rightExpr = exprFactory.newExpression(stmt,
                                    rSqlTbl, rSqlTbl.getTable().getMemberMapping(rightMmd));
                                stmt.whereAnd(outerExpr.eq(rightExpr), false);
                            }
                            else
                            {
                                // Join to left table
                                lSqlTbl = stmt.innerJoin(rSqlTbl, rSqlTbl.getTable().getMemberMapping(rightMmd),
                                    leftTbl, null, leftTbl.getIdMapping(), null, null);
                            }
                        }
                        break;
                    }
                    case Relation.ONE_TO_MANY_BI:
                    {
                        if (leftMmd.getJoinMetaData() != null || rightMmd.getJoinMetaData() != null)
                        {
                            // 1-N with join table to right table, so join from right to join table
                            ElementContainerTable joinTbl =
                                (ElementContainerTable)storeMgr.getDatastoreContainerObject(leftMmd);
                            SQLTable joinSqlTbl = stmt.innerJoin(rSqlTbl, rSqlTbl.getTable().getIdMapping(),
                                joinTbl, null, joinTbl.getElementMapping(), null, null);

                            if (i == 0)
                            {
                                // Add where clause join table to outer table
                                SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(),
                                    outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                                SQLExpression joinExpr = exprFactory.newExpression(stmt,
                                    joinSqlTbl, joinTbl.getOwnerMapping());
                                stmt.whereAnd(outerExpr.eq(joinExpr), false);
                            }
                            else
                            {
                                // Join to left table
                                lSqlTbl = stmt.innerJoin(joinSqlTbl, joinTbl.getOwnerMapping(),
                                    leftTbl, null, leftTbl.getIdMapping(), null, null);
                            }
                        }
                        else
                        {
                            // 1-N with FK in right table
                            if (i == 0)
                            {
                                // Add where clause right table to outer table
                                SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(),
                                    outerSqlTbl, outerSqlTbl.getTable().getIdMapping());
                                SQLExpression rightExpr = exprFactory.newExpression(stmt,
                                    rSqlTbl, rSqlTbl.getTable().getMemberMapping(rightMmd));
                                stmt.whereAnd(outerExpr.eq(rightExpr), false);
                            }
                            else
                            {
                                // Join to left table
                                lSqlTbl = stmt.innerJoin(rSqlTbl, rSqlTbl.getTable().getMemberMapping(rightMmd),
                                    leftTbl, null, leftTbl.getIdMapping(), null, null);
                            }
                        }
                        break;
                    }
                    case Relation.MANY_TO_ONE_BI:
                    {
                        if (leftMmd.getJoinMetaData() != null || rightMmd.getJoinMetaData() != null)
                        {
                            // 1-N with join table to right table, so join from right to join table
                            ElementContainerTable joinTbl =
                                (ElementContainerTable)storeMgr.getDatastoreContainerObject(leftMmd);
                            SQLTable joinSqlTbl = stmt.innerJoin(rSqlTbl, rSqlTbl.getTable().getIdMapping(),
                                joinTbl, null, joinTbl.getOwnerMapping(), null, null);

                            if (i == 0)
                            {
                                // Add where clause join table to outer table
                                SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(),
                                    outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                                SQLExpression joinExpr = exprFactory.newExpression(stmt,
                                    joinSqlTbl, joinTbl.getElementMapping());
                                stmt.whereAnd(outerExpr.eq(joinExpr), false);
                            }
                            else
                            {
                                // Join to left table
                                lSqlTbl = stmt.innerJoin(joinSqlTbl, joinTbl.getElementMapping(),
                                    leftTbl, null, leftTbl.getIdMapping(), null, null);
                            }
                        }
                        else
                        {
                            if (i == 0)
                            {
                                // Add where clause right table to outer table
                                SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(),
                                    outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                                SQLExpression rightExpr = exprFactory.newExpression(stmt,
                                    rSqlTbl, rSqlTbl.getTable().getIdMapping());
                                stmt.whereAnd(outerExpr.eq(rightExpr), false);
                            }
                            else
                            {
                                // Join to left table
                                lSqlTbl = stmt.innerJoin(rSqlTbl, rSqlTbl.getTable().getIdMapping(),
                                    leftTbl, null, leftTbl.getMemberMapping(leftMmd), null, null);
                            }
                        }
                    }
                    default:
                        break;
                }
                rSqlTbl = lSqlTbl;
            }
        }

        // Process all join expressions
        Expression rightExpr = clsExpr.getRight();
        SQLTable sqlTbl = candSqlTbl;
        while (rightExpr != null)
        {
            if (rightExpr instanceof JoinExpression)
            {
                JoinExpression joinExpr = (JoinExpression)rightExpr;
                JoinType joinType = joinExpr.getType();
                String joinAlias = joinExpr.getAlias();
                PrimaryExpression joinPrimExpr = joinExpr.getPrimaryExpression();

                Iterator<String> iter = joinPrimExpr.getTuples().iterator();
                String rootId = iter.next();
                String joinTableGroupName = null;
                if (rootId.equalsIgnoreCase(candidateAlias))
                {
                    // Join relative to the candidate
                    // Name table group of joined-to as per the relation
                    // Note : this will only work for one level out from the candidate TODO Extend this
                    joinTableGroupName = joinPrimExpr.getId();
                }
                else
                {
                    // Join relative to some other alias
                    SQLTableMapping sqlTblMapping = getSQLTableMappingForAlias(rootId);
                    if (sqlTblMapping != null)
                    {
                        cmd = sqlTblMapping.cmd;
                        joinTableGroupName = sqlTblMapping.table.getGroupName();
                        sqlTbl = sqlTblMapping.table;
                    }
                    else
                    {
                        throw new NucleusUserException("Query has " + joinPrimExpr.getId() + " yet the first component "+
                            rootId + " is unknown!");
                    }
                }

                while (iter.hasNext())
                {
                    String id = iter.next();
                    AbstractMemberMetaData mmd = cmd.getMetaDataForMember(id);
                    int relationType = mmd.getRelationType(clr);
                    DatastoreClass relTable = null;
                    AbstractMemberMetaData relMmd = null;
                    // TODO Cater for map in 1-N relations?
                    switch (relationType)
                    {
                        case Relation.ONE_TO_ONE_UNI:
                            relTable = storeMgr.getDatastoreClass(mmd.getTypeName(), clr);
                            cmd = mmgr.getMetaDataForClass(mmd.getType(), clr);
                            if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                            {
                                sqlTbl = stmt.innerJoin(sqlTbl, sqlTbl.getTable().getMemberMapping(mmd),
                                    relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                            }
                            else
                            {
                                sqlTbl = stmt.leftOuterJoin(sqlTbl, sqlTbl.getTable().getMemberMapping(mmd),
                                    relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                            }
                            break;
                        case Relation.ONE_TO_ONE_BI:
                            relTable = storeMgr.getDatastoreClass(mmd.getTypeName(), clr);
                            cmd = storeMgr.getMetaDataManager().getMetaDataForClass(mmd.getType(), clr);
                            if (mmd.getMappedBy() != null)
                            {
                                relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                                if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                                {
                                    sqlTbl = stmt.innerJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        relTable, null, relTable.getMemberMapping(relMmd), null, joinTableGroupName);
                                }
                                else
                                {
                                    sqlTbl = stmt.leftOuterJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        relTable, null, relTable.getMemberMapping(relMmd), null, joinTableGroupName);
                                }
                            }
                            else
                            {
                                if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                                {
                                    sqlTbl = stmt.innerJoin(sqlTbl, sqlTbl.getTable().getMemberMapping(mmd), relTable, null,
                                        relTable.getIdMapping(), null, joinTableGroupName);
                                }
                                else
                                {
                                    sqlTbl = stmt.leftOuterJoin(sqlTbl, sqlTbl.getTable().getMemberMapping(mmd), relTable, null,
                                        relTable.getIdMapping(), null, joinTableGroupName);
                                }
                            }
                            break;
                        case Relation.ONE_TO_MANY_BI:
                            relTable = storeMgr.getDatastoreClass(mmd.getCollection().getElementType(), clr);
                            cmd = mmd.getCollection().getElementClassMetaData(clr, mmgr);
                            relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                            if (mmd.getJoinMetaData() != null || relMmd.getJoinMetaData() != null)
                            {
                                // Join to join table, then to related table
                                ElementContainerTable joinTbl = (ElementContainerTable)storeMgr.getDatastoreContainerObject(mmd);
                                if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                                {
                                    SQLTable joinSqlTbl = stmt.innerJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        joinTbl, null, joinTbl.getOwnerMapping(), null, null);
                                    sqlTbl = stmt.innerJoin(joinSqlTbl, joinTbl.getElementMapping(),
                                        relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                                }
                                else
                                {
                                    SQLTable joinSqlTbl = stmt.leftOuterJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        joinTbl, null, joinTbl.getOwnerMapping(), null, null);
                                    sqlTbl = stmt.leftOuterJoin(joinSqlTbl, joinTbl.getElementMapping(),
                                        relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                                }
                            }
                            else
                            {
                                // Join to related table
                                if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                                {
                                    sqlTbl = stmt.innerJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        relTable, null, relTable.getMemberMapping(relMmd), null, joinTableGroupName);
                                }
                                else
                                {
                                    sqlTbl = stmt.leftOuterJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        relTable, null, relTable.getMemberMapping(relMmd), null, joinTableGroupName);
                                }
                            }
                            break;
                        case Relation.ONE_TO_MANY_UNI:
                            relTable = storeMgr.getDatastoreClass(mmd.getCollection().getElementType(), clr);
                            cmd = mmd.getCollection().getElementClassMetaData(clr, mmgr);
                            if (mmd.getJoinMetaData() != null)
                            {
                                // Join to join table, then to related table
                                ElementContainerTable joinTbl = (ElementContainerTable)storeMgr.getDatastoreContainerObject(mmd);
                                if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                                {
                                    SQLTable joinSqlTbl = stmt.innerJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        joinTbl, null, joinTbl.getOwnerMapping(), null, null);
                                    sqlTbl = stmt.innerJoin(joinSqlTbl, joinTbl.getElementMapping(),
                                        relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                                }
                                else
                                {
                                    SQLTable joinSqlTbl = stmt.leftOuterJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        joinTbl, null, joinTbl.getOwnerMapping(), null, null);
                                    sqlTbl = stmt.leftOuterJoin(joinSqlTbl, joinTbl.getElementMapping(),
                                        relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                                }
                            }
                            else
                            {
                                // Join to related table
                                JavaTypeMapping relMapping = relTable.getExternalMapping(mmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
                                if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                                {
                                    sqlTbl = stmt.innerJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        relTable, null, relMapping, null, joinTableGroupName);
                                }
                                else
                                {
                                    sqlTbl = stmt.leftOuterJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        relTable, null, relMapping, null, joinTableGroupName);
                                }
                            }
                            break;
                        case Relation.MANY_TO_MANY_BI:
                            relTable = storeMgr.getDatastoreClass(mmd.getCollection().getElementType(), clr);
                            cmd = mmd.getCollection().getElementClassMetaData(clr, mmgr);
                            relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                            // Join to join table, then to related table
                            CollectionTable joinTbl = (CollectionTable)storeMgr.getDatastoreContainerObject(mmd);
                            if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                            {
                                SQLTable joinSqlTbl = stmt.innerJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                    joinTbl, null, joinTbl.getOwnerMapping(), null, null);
                                sqlTbl = stmt.innerJoin(joinSqlTbl, joinTbl.getElementMapping(),
                                    relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                            }
                            else
                            {
                                SQLTable joinSqlTbl = stmt.leftOuterJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                    joinTbl, null, joinTbl.getOwnerMapping(), null, null);
                                sqlTbl = stmt.leftOuterJoin(joinSqlTbl, joinTbl.getElementMapping(),
                                    relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                            }
                            break;
                        case Relation.MANY_TO_ONE_BI:
                            relTable = storeMgr.getDatastoreClass(mmd.getTypeName(), clr);
                            cmd = storeMgr.getMetaDataManager().getMetaDataForClass(mmd.getType(), clr);
                            relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                            if (mmd.getJoinMetaData() != null || relMmd.getJoinMetaData() != null)
                            {
                                // Join to join table, then to related table
                                joinTbl = (CollectionTable)storeMgr.getDatastoreContainerObject(relMmd);
                                if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                                {
                                    SQLTable joinSqlTbl = stmt.innerJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        joinTbl, null, joinTbl.getElementMapping(), null, null);
                                    sqlTbl = stmt.innerJoin(joinSqlTbl, joinTbl.getOwnerMapping(),
                                        relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                                }
                                else
                                {
                                    SQLTable joinSqlTbl = stmt.leftOuterJoin(sqlTbl, sqlTbl.getTable().getIdMapping(),
                                        joinTbl, null, joinTbl.getElementMapping(), null, null);
                                    sqlTbl = stmt.leftOuterJoin(joinSqlTbl, joinTbl.getOwnerMapping(),
                                        relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                                }
                            }
                            else
                            {
                                // Join to owner table
                                JavaTypeMapping fkMapping = sqlTbl.getTable().getMemberMapping(mmd);
                                if (joinType == JoinType.JOIN_INNER || joinType == JoinType.JOIN_INNER_FETCH)
                                {
                                    sqlTbl = stmt.innerJoin(sqlTbl, fkMapping,
                                        relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                                }
                                else
                                {
                                    sqlTbl = stmt.leftOuterJoin(sqlTbl, fkMapping,
                                        relTable, null, relTable.getIdMapping(), null, joinTableGroupName);
                                }
                            }
                            break;
                        default:
                            break;
                    }
                }

                if (joinAlias != null)
                {
                    if (explicitJoinPrimaryByAlias == null)
                    {
                        explicitJoinPrimaryByAlias = new HashMap<String, String>();
                    }
                    explicitJoinPrimaryByAlias.put(joinAlias, joinPrimExpr.getId());
                }

                SQLTableMapping tblMapping = new SQLTableMapping(sqlTbl, cmd, sqlTbl.getTable().getIdMapping());
                setSQLTableMappingForAlias(joinAlias, tblMapping);
            }

            // Move on to next join in the chain
            rightExpr = rightExpr.getRight();
        }
    }

    /**
     * Convenience method to convert a NewObjectExpression into a StatementNewObjectMapping.
     * Handles recursive new object calls (where a new object is an arg to a new object construction).
     * @param expr The NewObjectExpression
     * @return The mapping for the new object
     */
    protected StatementNewObjectMapping getStatementMappingForNewObjectExpression(NewObjectExpression expr)
    {
        List argExprs = expr.getConstructorArgExpressions();
        StatementNewObjectMapping stmtMap = new StatementNewObjectMapping(expr.getNewClass());
        if (argExprs != null)
        {
            Iterator<SQLExpression> argIter = argExprs.iterator();
            int j = 0;
            while (argIter.hasNext())
            {
                SQLExpression argExpr = argIter.next();
                if (argExpr instanceof SQLLiteral)
                {
                    stmtMap.addConstructorArgMapping(j, ((SQLLiteral)argExpr).getValue());
                }
                else if (argExpr instanceof NewObjectExpression)
                {
                    stmtMap.addConstructorArgMapping(j,
                        getStatementMappingForNewObjectExpression((NewObjectExpression)argExpr));
                }
                else
                {
                    StatementMappingIndex idx = new StatementMappingIndex(argExpr.getJavaTypeMapping());
                    int[] cols = stmt.select(argExpr, null);
                    idx.setColumnPositions(cols);
                    stmtMap.addConstructorArgMapping(j, idx);
                }
                j++;
            }
        }
        return stmtMap;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processAndExpression(org.datanucleus.query.expression.Expression)
     */
    protected Object processAndExpression(Expression expr)
    {
        SQLExpression rightExpr = stack.pop();
        SQLExpression leftExpr = stack.pop();
        if (!(rightExpr instanceof BooleanExpression))
        {
            throw new NucleusUserException("Query has clause " + rightExpr + " used with AND. This is illegal, and should be a boolean expression");
        }
        if (!(leftExpr instanceof BooleanExpression))
        {
            throw new NucleusUserException("Query has clause " + leftExpr + " used with AND. This is illegal, and should be a boolean expression");
        }

        BooleanExpression right = (BooleanExpression)rightExpr;
        BooleanExpression left = (BooleanExpression)leftExpr;
        if (left.getSQLStatement() != null && right.getSQLStatement() != null &&
            left.getSQLStatement() != right.getSQLStatement())
        {
            if (left.getSQLStatement() == stmt &&
                right.getSQLStatement().isChildStatementOf(stmt))
            {
                // Apply right to its sub-statement now and return left
                right.getSQLStatement().whereAnd(right, true);
                stack.push(left);
                return left;
            }
            else if (right.getSQLStatement() == stmt &&
                left.getSQLStatement().isChildStatementOf(stmt))
            {
                // Apply left to its sub-statement now and return right
                left.getSQLStatement().whereAnd(left, true);
                stack.push(right);
                return right;
            }
            // TODO Cater for more situations
        }

        if (compileComponent == CompilationComponent.FILTER)
        {
            // Make sure any simple boolean field clauses are suitable
            left = getBooleanExpressionForUseInFilter(left);
            right = getBooleanExpressionForUseInFilter(right);
        }

        BooleanExpression opExpr = left.and(right);
        stack.push(opExpr);
        return opExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processOrExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processOrExpression(Expression expr)
    {
        SQLExpression rightExpr = stack.pop();
        SQLExpression leftExpr = stack.pop();
        if (!(rightExpr instanceof BooleanExpression))
        {
            throw new NucleusUserException("Query has clause " + rightExpr + " used with AND. This is illegal, and should be a boolean expression");
        }
        if (!(leftExpr instanceof BooleanExpression))
        {
            throw new NucleusUserException("Query has clause " + leftExpr + " used with AND. This is illegal, and should be a boolean expression");
        }

        BooleanExpression right = (BooleanExpression)rightExpr;
        BooleanExpression left = (BooleanExpression)leftExpr;
        if (left.getSQLStatement() != null && right.getSQLStatement() != null &&
            left.getSQLStatement() != right.getSQLStatement())
        {
            if (left.getSQLStatement() == stmt && right.getSQLStatement().isChildStatementOf(stmt))
            {
                // Apply right to its sub-statement now and return left
                right.getSQLStatement().whereAnd(right, true);
                stack.push(left);
                return left;
            }
            else if (right.getSQLStatement() == stmt && left.getSQLStatement().isChildStatementOf(stmt))
            {
                // Apply left to its sub-statement now and return right
                left.getSQLStatement().whereAnd(left, true);
                stack.push(right);
                return right;
            }
            // TODO Cater for more situations
        }

        if (compileComponent == CompilationComponent.FILTER)
        {
            // Make sure any simple boolean field clauses are suitable
            left = getBooleanExpressionForUseInFilter(left);
            right = getBooleanExpressionForUseInFilter(right);
        }

        left.encloseInParentheses();
        right.encloseInParentheses();
        BooleanExpression opExpr = left.ior(right);
        stack.push(opExpr);
        return opExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processEqExpression(org.datanucleus.query.expression.Expression)
     */
    protected Object processEqExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        if (left instanceof ParameterLiteral && !(right instanceof ParameterLiteral))
        {
            left = replaceParameterLiteral((ParameterLiteral)left, right.getJavaTypeMapping());
        }
        else if (right instanceof ParameterLiteral && !(left instanceof ParameterLiteral))
        {
            right = replaceParameterLiteral((ParameterLiteral)right, left.getJavaTypeMapping());
        }

        if (left.isParameter() && right.isParameter())
        {
            if (left.isParameter() && left instanceof SQLLiteral && ((SQLLiteral)left).getValue() != null)
            {
                // Change this parameter to a plain literal
                useParameterExpressionAsLiteral((SQLLiteral)left);
            }
            if (right.isParameter() && right instanceof SQLLiteral && ((SQLLiteral)right).getValue() != null)
            {
                // Change this parameter to a plain literal
                useParameterExpressionAsLiteral((SQLLiteral)right);
            }
        }

        ExpressionUtils.checkAndCorrectExpressionMappingsForBooleanComparison(left, right);

        if (left instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression) left);
            left = stack.pop();
        }
        if (right instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression)right);
            right = stack.pop();
        }

        // Logic for when one side is cross-joined (variable) and other side not, so transfer to a left outer join
        if (!options.contains(OPTION_EXPLICIT_JOINS))
        {
            boolean leftIsCrossJoin = (stmt.getJoinTypeForTable(left.getSQLTable()) == org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.CROSS_JOIN);
            boolean rightIsCrossJoin = (stmt.getJoinTypeForTable(right.getSQLTable()) == org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.CROSS_JOIN);
            if (leftIsCrossJoin && !rightIsCrossJoin && !(right instanceof SQLLiteral))
            {
                // "a == b" and a is cross-joined currently (includes variable) so change to left outer join
                String varName = getAliasForSQLTable(left.getSQLTable());
                org.datanucleus.store.rdbms.sql.SQLJoin.JoinType joinType = getRequiredJoinTypeForAlias(varName);
                if (joinType != null)
                {
                    NucleusLogger.QUERY.debug(">> QueryToSQL.eq variable " + varName + " is mapped to table " + left.getSQLTable() +
                        " was previously bound as CROSS JOIN but changing to " + joinType);
                    String leftTblAlias = stmt.removeCrossJoin(left.getSQLTable());
                    if (joinType == org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.LEFT_OUTER_JOIN)
                    {
                        stmt.leftOuterJoin(right.getSQLTable(), right.getJavaTypeMapping(),
                            left.getSQLTable().getTable(), leftTblAlias, left.getJavaTypeMapping(), null,
                            left.getSQLTable().getGroupName());
                    }
                    else
                    {
                        stmt.innerJoin(right.getSQLTable(), right.getJavaTypeMapping(),
                            left.getSQLTable().getTable(), leftTblAlias, left.getJavaTypeMapping(), null,
                            left.getSQLTable().getGroupName());
                    }

                    JavaTypeMapping m = exprFactory.getMappingForType(boolean.class, true);
                    SQLExpression opExpr = exprFactory.newLiteral(stmt, m, true).eq(exprFactory.newLiteral(stmt, m, true));
                    stack.push(opExpr);
                    return opExpr;
                }
            }
            else if (!leftIsCrossJoin && rightIsCrossJoin && !(left instanceof SQLLiteral))
            {
                // "a == b" and b is cross-joined currently (includes variable) so change to left outer join
                String varName = getAliasForSQLTable(right.getSQLTable());
                org.datanucleus.store.rdbms.sql.SQLJoin.JoinType joinType = getRequiredJoinTypeForAlias(varName);
                if (joinType != null)
                {
                    NucleusLogger.QUERY.debug(">> QueryToSQL.eq variable " + varName + " is mapped to table " + right.getSQLTable() +
                        " was previously bound as CROSS JOIN but changing to " + joinType);
                    String rightTblAlias = stmt.removeCrossJoin(right.getSQLTable());
                    if (joinType == org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.LEFT_OUTER_JOIN)
                    {
                        stmt.leftOuterJoin(left.getSQLTable(), left.getJavaTypeMapping(),
                            right.getSQLTable().getTable(), rightTblAlias, right.getJavaTypeMapping(), null,
                            right.getSQLTable().getGroupName());
                    }
                    else
                    {
                        stmt.innerJoin(left.getSQLTable(), left.getJavaTypeMapping(),
                            right.getSQLTable().getTable(), rightTblAlias, right.getJavaTypeMapping(), null,
                            right.getSQLTable().getGroupName());
                    }

                    JavaTypeMapping m = exprFactory.getMappingForType(boolean.class, true);
                    SQLExpression opExpr = exprFactory.newLiteral(stmt, m, true).eq(exprFactory.newLiteral(stmt, m, true));
                    stack.push(opExpr);
                    return opExpr;
                }
            }
        }

        BooleanExpression opExpr = left.eq(right);
        stack.push(opExpr);
        return opExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processNoteqExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processNoteqExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        if (left instanceof ParameterLiteral && !(right instanceof ParameterLiteral))
        {
            left = replaceParameterLiteral((ParameterLiteral)left, right.getJavaTypeMapping());
        }
        else if (right instanceof ParameterLiteral && !(left instanceof ParameterLiteral))
        {
            right = replaceParameterLiteral((ParameterLiteral)right, left.getJavaTypeMapping());
        }

        if (left.isParameter() && right.isParameter())
        {
            if (left.isParameter() && left instanceof SQLLiteral && ((SQLLiteral)left).getValue() != null)
            {
                useParameterExpressionAsLiteral((SQLLiteral)left);
            }
            if (right.isParameter() && right instanceof SQLLiteral && ((SQLLiteral)right).getValue() != null)
            {
                useParameterExpressionAsLiteral((SQLLiteral)right);
            }
        }

        ExpressionUtils.checkAndCorrectExpressionMappingsForBooleanComparison(left, right);

        if (left instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression) left);
            left = stack.pop();
        }
        if (right instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression)right);
            right = stack.pop();
        }
        BooleanExpression opExpr = left.ne(right);

        stack.push(opExpr);
        return opExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processGteqExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processGteqExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        if (left instanceof ParameterLiteral && !(right instanceof ParameterLiteral))
        {
            left = replaceParameterLiteral((ParameterLiteral)left, right.getJavaTypeMapping());
        }
        else if (right instanceof ParameterLiteral && !(left instanceof ParameterLiteral))
        {
            right = replaceParameterLiteral((ParameterLiteral)right, left.getJavaTypeMapping());
        }

        ExpressionUtils.checkAndCorrectExpressionMappingsForBooleanComparison(left, right);

        if (left instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression) left);
            left = stack.pop();
        }
        if (right instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression)right);
            right = stack.pop();
        }

        BooleanExpression opExpr = left.ge(right);

        stack.push(opExpr);
        return opExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processGtExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processGtExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        if (left instanceof ParameterLiteral && !(right instanceof ParameterLiteral))
        {
            left = replaceParameterLiteral((ParameterLiteral)left, right.getJavaTypeMapping());
        }
        else if (right instanceof ParameterLiteral && !(left instanceof ParameterLiteral))
        {
            right = replaceParameterLiteral((ParameterLiteral)right, left.getJavaTypeMapping());
        }

        ExpressionUtils.checkAndCorrectExpressionMappingsForBooleanComparison(left, right);

        if (left instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression) left);
            left = stack.pop();
        }
        if (right instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression)right);
            right = stack.pop();
        }

        BooleanExpression opExpr = left.gt(right);

        stack.push(opExpr);
        return opExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processLteqExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processLteqExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        if (left instanceof ParameterLiteral && !(right instanceof ParameterLiteral))
        {
            left = replaceParameterLiteral((ParameterLiteral)left, right.getJavaTypeMapping());
        }
        else if (right instanceof ParameterLiteral && !(left instanceof ParameterLiteral))
        {
            right = replaceParameterLiteral((ParameterLiteral)right, left.getJavaTypeMapping());
        }

        ExpressionUtils.checkAndCorrectExpressionMappingsForBooleanComparison(left, right);

        if (left instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression) left);
            left = stack.pop();
        }
        if (right instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression)right);
            right = stack.pop();
        }

        BooleanExpression opExpr = left.le(right);

        stack.push(opExpr);
        return opExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processLtExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processLtExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        if (left instanceof ParameterLiteral && !(right instanceof ParameterLiteral))
        {
            left = replaceParameterLiteral((ParameterLiteral)left, right.getJavaTypeMapping());
        }
        else if (right instanceof ParameterLiteral && !(left instanceof ParameterLiteral))
        {
            right = replaceParameterLiteral((ParameterLiteral)right, left.getJavaTypeMapping());
        }

        ExpressionUtils.checkAndCorrectExpressionMappingsForBooleanComparison(left, right);

        if (left instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression) left);
            left = stack.pop();
        }
        if (right instanceof UnboundExpression)
        {
            processUnboundExpression((UnboundExpression)right);
            right = stack.pop();
        }

        BooleanExpression opExpr = left.lt(right);

        stack.push(opExpr);
        return opExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processLiteral(org.datanucleus.query.expression.Literal)
     */
    protected Object processLiteral(Literal expr)
    {
        Object litValue = expr.getLiteral();
        if (litValue instanceof Class)
        {
            // Convert Class literals (instanceof) into StringLiteral
            litValue = ((Class)litValue).getName();
        }
        JavaTypeMapping m = null;
        if (litValue != null)
        {
            m = exprFactory.getMappingForType(litValue.getClass(), false);
        }
        SQLExpression sqlExpr = exprFactory.newLiteral(stmt, m, litValue);
        stack.push(sqlExpr);
        return sqlExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processPrimaryExpression(org.datanucleus.query.expression.PrimaryExpression)
     */
    protected Object processPrimaryExpression(PrimaryExpression expr)
    {
        SQLExpression sqlExpr = null;
        if (expr.getLeft() != null)
        {
            if (expr.getLeft() instanceof DyadicExpression && expr.getLeft().getOperator() == Expression.OP_CAST)
            {
                // Evaluate the cast
                expr.getLeft().evaluate(this);
                sqlExpr = stack.pop();

                // Add the cast expression to the table mapping
                Literal castLitExpr = (Literal)expr.getLeft().getRight();
                AbstractClassMetaData castCmd =
                    ec.getMetaDataManager().getMetaDataForClass(resolveClass((String)castLitExpr.getLiteral()), clr);
                String exprCastName = null;
                if (expr.getLeft().getLeft() instanceof PrimaryExpression)
                {
                    exprCastName = "CAST_" + ((PrimaryExpression)expr.getLeft().getLeft()).getId();
                }
                else if (expr.getLeft().getLeft() instanceof VariableExpression)
                {
                    exprCastName = "CAST_" + ((VariableExpression)expr.getLeft().getLeft()).getId();
                }
                else if (expr.getLeft().getLeft() instanceof InvokeExpression)
                {
                    exprCastName = "CAST_" + expr.getLeft().getLeft();
                }
                else
                {
                    throw new NucleusException("Don't currently support cast of " + expr.getLeft().getLeft());
                }
                SQLTableMapping tblMapping = new SQLTableMapping(sqlExpr.getSQLTable(), castCmd, sqlExpr.getJavaTypeMapping());
                setSQLTableMappingForAlias(exprCastName, tblMapping);

                SQLTableMapping sqlMapping =
                    getSQLTableMappingForPrimaryExpression(stmt, exprCastName, expr, Boolean.FALSE);
                if (sqlMapping == null)
                {
                    throw new NucleusException("PrimaryExpression " + expr + " is not yet supported");
                }
                sqlExpr = exprFactory.newExpression(stmt, sqlMapping.table, sqlMapping.mapping);
                stack.push(sqlExpr);
                return sqlExpr;
            }
            else if (expr.getLeft() instanceof ParameterExpression)
            {
                // "{paramExpr}.field[.field[.field]]"
                setNotPrecompilable(); // Need parameter values to process this
                ParameterExpression paramExpr = (ParameterExpression)expr.getLeft();
                Symbol paramSym = compilation.getSymbolTable().getSymbol(paramExpr.getId());
                if (paramSym.getValueType() != null && paramSym.getValueType().isArray())
                {
                    // Special case : array "methods" (particularly "length")
                    String first = expr.getTuples().get(0);
                    processParameterExpression(paramExpr, true);
                    SQLExpression paramSqlExpr = stack.pop();
                    sqlExpr = exprFactory.invokeMethod(stmt, "ARRAY", first, paramSqlExpr, null);
                    stack.push(sqlExpr);
                    return sqlExpr;
                }
                else
                {
                    // Create Literal for the parameter (since we need to perform operations on it)
                    processParameterExpression(paramExpr, true);
                    SQLExpression paramSqlExpr = stack.pop();
                    SQLLiteral lit = (SQLLiteral)paramSqlExpr;
                    Object paramValue = lit.getValue();

                    List<String> tuples = expr.getTuples();
                    Iterator<String> tuplesIter = tuples.iterator();
                    Object objValue = paramValue;
                    while (tuplesIter.hasNext())
                    {
                        String fieldName = tuplesIter.next();
                        objValue = getValueForObjectField(objValue, fieldName);
                        setNotPrecompilable(); // Using literal value of parameter, so cannot precompile it
                        if (objValue == null)
                        {
                            break;
                        }
                    }

                    if (objValue == null)
                    {
                        sqlExpr = exprFactory.newLiteral(stmt, null, null);
                        stack.push(sqlExpr);
                        return sqlExpr;
                    }
                    else
                    {
                        JavaTypeMapping m = exprFactory.getMappingForType(objValue.getClass(), false);
                        sqlExpr = exprFactory.newLiteral(stmt, m, objValue);
                        stack.push(sqlExpr);
                        return sqlExpr;
                    }
                }
            }
            else if (expr.getLeft() instanceof VariableExpression)
            {
                // "{varExpr}.field[.field[.field]]"
                VariableExpression varExpr = (VariableExpression)expr.getLeft();
                processVariableExpression(varExpr);
                SQLExpression varSqlExpr = stack.pop();
                if (varSqlExpr instanceof UnboundExpression)
                {
                    // Bind as CROSS JOIN for now
                    processUnboundExpression((UnboundExpression)varSqlExpr);
                    varSqlExpr = stack.pop();
                }

                Class varType = clr.classForName(varSqlExpr.getJavaTypeMapping().getType());
                if (varSqlExpr.getSQLStatement() == stmt.getParentStatement())
                {
                    // Use parent mapper to get the mapping for this field since it has the table
                    SQLTableMapping sqlMapping =
                        parentMapper.getSQLTableMappingForPrimaryExpression(stmt, null, expr, Boolean.FALSE);
                    if (sqlMapping == null)
                    {
                        throw new NucleusException("PrimaryExpression " + expr.getId() + " is not yet supported");
                    }
                    // TODO Cater for the table required to join to not being the primary table of the outer query
                    // This should check on
                    // getDatastoreAdapter().supportsOption(RDBMSAdapter.ACCESS_PARENTQUERY_IN_SUBQUERY))

                    sqlExpr = exprFactory.newExpression(varSqlExpr.getSQLStatement(),
                        sqlMapping.table, sqlMapping.mapping);
                    stack.push(sqlExpr);
                    return sqlExpr;
                }

                SQLTableMapping varTblMapping = getSQLTableMappingForAlias(varExpr.getId());
                if (varTblMapping == null)
                {
                    throw new NucleusUserException("Variable " + varExpr.getId() + " is not yet bound, so cannot get field " + expr.getId());
                }
                if (varTblMapping.cmd == null)
                {
                    throw new NucleusUserException("Variable " + varExpr.getId() + " of type " + varType.getName() + " cannot evaluate " + expr.getId());
                }

                SQLTableMapping sqlMapping =
                    getSQLTableMappingForPrimaryExpression(varSqlExpr.getSQLStatement(), varExpr.getId(),
                        expr, Boolean.FALSE);

                sqlExpr = exprFactory.newExpression(sqlMapping.table.getSQLStatement(), sqlMapping.table,
                    sqlMapping.mapping);
                stack.push(sqlExpr);
                return sqlExpr;
            }
            else if (expr.getLeft() instanceof InvokeExpression)
            {
                processInvokeExpression((InvokeExpression)expr.getLeft());
                SQLExpression invokeSqlExpr = stack.pop();
                DatastoreContainerObject tbl = invokeSqlExpr.getSQLTable().getTable();
                if (tbl instanceof DatastoreClass)
                {
                    // Table of a class, so assume to have field in the table of the class
                    // TODO Allow joins to superclasses if required
                    if (expr.getTuples().size() > 1)
                    {
                        throw new NucleusUserException("Dont currently support evaluating " + expr.getId() +
                            " on " + invokeSqlExpr);
                    }
                    JavaTypeMapping mapping = ((DatastoreClass)tbl).getMemberMapping(expr.getId());
                    if (mapping == null)
                    {
                        throw new NucleusUserException("Dont currently support evaluating " + expr.getId() +
                            " on " + invokeSqlExpr +
                            ". The field " + expr.getId() + " doesnt exist in table " + tbl);
                    }

                    sqlExpr = exprFactory.newExpression(stmt, invokeSqlExpr.getSQLTable(), mapping);
                    stack.push(sqlExpr);
                    return sqlExpr;
                }
                else
                {
                    // Join table!
                    throw new NucleusUserException("Dont currently support evaluating " + expr.getId() +
                        " on " + invokeSqlExpr +
                        " with invoke having table of " + tbl);
                }
            }
            else
            {
                throw new NucleusUserException("Dont currently support PrimaryExpression with 'left' of " + expr.getLeft());
            }
        }

        // Real primary expression ("field.field", "alias.field.field" etc)
        SQLTableMapping sqlMapping = getSQLTableMappingForPrimaryExpression(stmt, null, expr, null);
        if (sqlMapping == null)
        {
            throw new NucleusException("PrimaryExpression " + expr.getId() + " is not yet supported");
        }
        sqlExpr = exprFactory.newExpression(stmt, sqlMapping.table, sqlMapping.mapping);
        stack.push(sqlExpr);
        return sqlExpr;
    }

    /**
     * Method to take in a PrimaryExpression and return the SQLTable mapping info that it signifies.
     * If the primary expression implies joining to other objects then adds the joins to the statement.
     * Only adds joins if necessary; so if there is a further component after the required join, or if
     * the "forceJoin" flag is set.
     * @param theStmt SQLStatement to use when looking for tables etc
     * @param exprName Name for an expression that this primary is relative to (optional)
     *                 If not specified then the tuples are relative to the candidate.
     *                 If specified then should have an entry in sqlTableByPrimary under this name.
     * @param primExpr The primary expression
     * @param forceJoin Whether to force a join if a relation member (or null if leaving to this method to decide)
     * @return The SQL table mapping information for the specified primary
     */
    private SQLTableMapping getSQLTableMappingForPrimaryExpression(SQLStatement theStmt, String exprName,
            PrimaryExpression primExpr, Boolean forceJoin)
    {
        if (forceJoin == null && primExpr.getParent() != null)
        {
            if (primExpr.getParent().getOperator() == Expression.OP_IS ||
                primExpr.getParent().getOperator() == Expression.OP_ISNOT)
            {
                // "instanceOf" needs to be in the table of the primary expression
                forceJoin = Boolean.TRUE;
            }
        }

        SQLTableMapping sqlMapping = null;

        List<String> tuples = primExpr.getTuples();

        // Find source object
        Iterator<String> iter = tuples.iterator();
        String first = tuples.get(0);
        String primaryName = null;
        if (exprName != null)
        {
            // Primary relative to some object etc
            sqlMapping = getSQLTableMappingForAlias(exprName);
            primaryName = exprName;
        }
        else
        {
            if (hasSQLTableMappingForAlias(first))
            {
                // Start from a candidate (e.g JPQL alias)
                sqlMapping = getSQLTableMappingForAlias(first);
                primaryName = first;
                iter.next(); // Skip first tuple
            }

            if (sqlMapping == null)
            {
                if (parentMapper != null && parentMapper.hasSQLTableMappingForAlias(first))
                {
                    // Try parent query
                    sqlMapping = parentMapper.getSQLTableMappingForAlias(first);
                    primaryName = first;
                    iter.next(); // Skip first tuple

                    // This expression is for the parent statement so any joins need to go on that statement
                    theStmt = sqlMapping.table.getSQLStatement();
                }
            }
            if (sqlMapping == null)
            {
                // Field of candidate, so use candidate
                sqlMapping = getSQLTableMappingForAlias(candidateAlias);
                primaryName = candidateAlias;
            }
        }

        AbstractClassMetaData cmd = sqlMapping.cmd;
        JavaTypeMapping mapping = sqlMapping.mapping;

        while (iter.hasNext())
        {
            String component = iter.next();
            primaryName += "." + component; // fully-qualified primary name

            // Derive SQLTableMapping for this component
            SQLTableMapping sqlMappingNew = getSQLTableMappingForAlias(primaryName);
            if (sqlMappingNew == null)
            {
                // Table not present for this primary
                AbstractMemberMetaData mmd = cmd.getMetaDataForMember(component);
                if (mmd == null)
                {
                    // Not valid member name
                    throw new NucleusUserException(LOCALISER.msg("021062", component, cmd.getFullClassName()));
                }
                else if (mmd.getPersistenceModifier() != FieldPersistenceModifier.PERSISTENT)
                {
                    throw new NucleusUserException("Field "+ mmd.getFullFieldName() + " is not marked as persistent so cannot be queried");
                }

                // Find the table and the mapping for this field in the table
                SQLTable sqlTbl = null;
                if (mapping instanceof EmbeddedMapping)
                {
                    // Embedded into the current table
                    sqlTbl = sqlMapping.table;
                    mapping = ((EmbeddedMapping)mapping).getJavaTypeMapping(component);
                }
                else
                {
                    DatastoreClass table = storeMgr.getDatastoreClass(cmd.getFullClassName(), clr);
                    if (table == null)
                    {
                        AbstractClassMetaData[] subCmds = storeMgr.getClassesManagingTableForClass(cmd, clr);
                        if (subCmds.length == 1)
                        {
                            table = storeMgr.getDatastoreClass(subCmds[0].getFullClassName(), clr);
                        }
                        else
                        {
                            throw new NucleusUserException("Unable to find table for primary " + primaryName +
                                " since the class " + cmd.getFullClassName() + " is managed in multiple tables");
                        }
                    }
                    mapping = table.getMemberMapping(mmd);
                    sqlTbl = SQLStatementHelper.getSQLTableForMappingOfTable(theStmt, sqlMapping.table, mapping);
                }

                int relationType = mmd.getRelationType(clr);
                switch (relationType)
                {
                    case Relation.NONE :
                        sqlMappingNew = new SQLTableMapping(sqlTbl, cmd, mapping);
                        cmd = sqlMappingNew.cmd;
                        setSQLTableMappingForAlias(primaryName, sqlMappingNew);
                        break;

                    case Relation.ONE_TO_ONE_UNI :
                    case Relation.ONE_TO_ONE_BI :
                        if (mmd.getMappedBy() != null)
                        {
                            // FK in other table so join to that first
                            AbstractMemberMetaData relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                            if (relMmd.getAbstractClassMetaData().isEmbeddedOnly())
                            {
                                // Member is embedded, so keep same SQL table mapping
                                sqlMappingNew = sqlMapping;
                                cmd = relMmd.getAbstractClassMetaData();
                            }
                            else
                            {
                                // Member is in own table, so move to that SQL table mapping
                                DatastoreClass relTable = storeMgr.getDatastoreClass(mmd.getTypeName(), clr);
                                JavaTypeMapping relMapping = relTable.getMemberMapping(relMmd);
                                // Join to related table unless we already have the join in place
                                sqlTbl = theStmt.getTable(relTable, primaryName);
                                if (sqlTbl == null)
                                {
                                    sqlTbl = SQLStatementHelper.addJoinForOneToOneRelation(theStmt,
                                        sqlMapping.table.getTable().getIdMapping(), sqlMapping.table,
                                        relMapping, relTable, null, null, primaryName, defaultJoinType);
                                }

                                if (iter.hasNext())
                                {
                                    sqlMappingNew = new SQLTableMapping(sqlTbl, relMmd.getAbstractClassMetaData(),
                                        relTable.getIdMapping());
                                    cmd = sqlMappingNew.cmd;
                                }
                                else
                                {
                                    sqlMappingNew = new SQLTableMapping(sqlTbl, cmd, relTable.getIdMapping());
                                    cmd = sqlMappingNew.cmd;
                                }
                            }
                        }
                        else
                        {
                            // FK is at this side, so only join if further component provided, or if forcing
                            if (forceJoin == null && !iter.hasNext())
                            {
                                if (primExpr.getParent() != null &&
                                    primExpr.getParent().getOperator() == Expression.OP_CAST)
                                {
                                    // Cast and not an interface field, so do a join to the table of the persistable object
                                    if (!(mapping instanceof ReferenceMapping))
                                    {
                                        // Don't join with interface field since represents multiple implementations
                                        // and the cast will be a restrict on which implementation to join to
                                        forceJoin = Boolean.TRUE;
                                    }
                                }
                            }

                            if (iter.hasNext() || Boolean.TRUE.equals(forceJoin))
                            {
                                AbstractClassMetaData relCmd = null;
                                JavaTypeMapping relMapping = null;
                                DatastoreClass relTable = null;
                                if (relationType == Relation.ONE_TO_ONE_BI)
                                {
                                    AbstractMemberMetaData relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                                    relCmd = relMmd.getAbstractClassMetaData();
                                }
                                else
                                {
                                    relCmd = ec.getMetaDataManager().getMetaDataForClass(mmd.getTypeName(), clr);
                                }

                                if (relCmd != null && relCmd.isEmbeddedOnly())
                                {
                                    // Member is embedded so keep same SQL table mapping
                                    sqlMappingNew = sqlMapping;
                                    cmd = relCmd;
                                }
                                else
                                {
                                    // Member is in own table, so move to that SQL table mapping
                                    relTable = storeMgr.getDatastoreClass(relCmd.getFullClassName(), clr);
                                    relMapping = relTable.getIdMapping();

                                    // Join to other table unless we already have the join in place
                                    sqlTbl = theStmt.getTable(relTable, primaryName);
                                    if (sqlTbl == null)
                                    {
                                        sqlTbl = SQLStatementHelper.addJoinForOneToOneRelation(theStmt, mapping,
                                            sqlMapping.table, relMapping, relTable, null, null, primaryName,
                                            defaultJoinType);
                                    }

                                    sqlMappingNew = new SQLTableMapping(sqlTbl, relCmd, relMapping);
                                    cmd = sqlMappingNew.cmd;
                                    setSQLTableMappingForAlias(primaryName, sqlMappingNew);
                                }
                            }
                            else
                            {
                                sqlMappingNew = new SQLTableMapping(sqlTbl, cmd, mapping);
                                cmd = sqlMappingNew.cmd;
                                // Don't register the SQLTableMapping for this alias since only using FK
                            }
                        }
                        break;

                    case Relation.MANY_TO_ONE_BI :
                        AbstractMemberMetaData relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                        DatastoreClass relTable = storeMgr.getDatastoreClass(mmd.getTypeName(), clr);
                        if (mmd.getJoinMetaData() != null || relMmd.getJoinMetaData() != null)
                        {
                            // Has join table so use that
                            sqlTbl = theStmt.getTable(relTable, primaryName);
                            if (sqlTbl == null)
                            {
                                // Join to the join table
                                CollectionTable joinTbl = (CollectionTable)storeMgr.getDatastoreContainerObject(relMmd);
                                if (defaultJoinType == org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.INNER_JOIN)
                                {
                                    SQLTable joinSqlTbl = theStmt.innerJoin(
                                        sqlMapping.table, sqlMapping.table.getTable().getIdMapping(),
                                        joinTbl, null, joinTbl.getElementMapping(),
                                        null, null);
                                    sqlTbl = theStmt.innerJoin(
                                        joinSqlTbl, joinTbl.getOwnerMapping(),
                                        relTable, null, relTable.getIdMapping(),
                                        null, primaryName);
                                }
                                else if (defaultJoinType == org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.LEFT_OUTER_JOIN ||
                                        defaultJoinType == null)
                                {
                                    SQLTable joinSqlTbl = theStmt.leftOuterJoin(
                                        sqlMapping.table, sqlMapping.table.getTable().getIdMapping(),
                                        joinTbl, null, joinTbl.getElementMapping(),
                                        null, null);
                                    sqlTbl = theStmt.leftOuterJoin(
                                        joinSqlTbl, joinTbl.getOwnerMapping(),
                                        relTable, null, relTable.getIdMapping(),
                                        null, primaryName);
                                }
                            }

                            sqlMappingNew =
                                new SQLTableMapping(sqlTbl, relMmd.getAbstractClassMetaData(), relTable.getIdMapping());
                            cmd = sqlMappingNew.cmd;
                            setSQLTableMappingForAlias(primaryName, sqlMappingNew);
                        }
                        else
                        {
                            // FK in this table
                            sqlTbl = theStmt.getTable(relTable, primaryName);
                            if (sqlTbl == null)
                            {
                                Operator op = (primExpr.getParent() != null ? primExpr.getParent().getOperator() : null);
                                if (!iter.hasNext() && (op == Expression.OP_EQ || op == Expression.OP_GT ||
                                    op == Expression.OP_LT || op == Expression.OP_GTEQ || op == Expression.OP_LTEQ ||
                                    op == Expression.OP_NOTEQ))
                                {
                                    // Just return the FK mapping since in a "a.b == c.d" type expression and not needing
                                    // to go further than the FK
                                    sqlMappingNew =
                                        new SQLTableMapping(sqlMapping.table, relMmd.getAbstractClassMetaData(), mapping);
                                }
                                else
                                {
                                    // Join to the related table
                                    if (defaultJoinType == org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.INNER_JOIN)
                                    {
                                        sqlTbl = theStmt.innerJoin(
                                            sqlMapping.table, mapping,
                                            relTable, null, relTable.getIdMapping(),
                                            null, primaryName);
                                    }
                                    else if (defaultJoinType == org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.LEFT_OUTER_JOIN ||
                                            defaultJoinType == null)
                                    {
                                        sqlTbl = theStmt.leftOuterJoin(
                                            sqlMapping.table, mapping,
                                            relTable, null, relTable.getIdMapping(),
                                            null, primaryName);
                                    }
                                    sqlMappingNew =
                                        new SQLTableMapping(sqlTbl, relMmd.getAbstractClassMetaData(), relTable.getIdMapping());
                                    cmd = sqlMappingNew.cmd;
                                    setSQLTableMappingForAlias(primaryName, sqlMappingNew);
                                }
                            }
                            else
                            {
                                sqlMappingNew =
                                    new SQLTableMapping(sqlTbl, relMmd.getAbstractClassMetaData(), relTable.getIdMapping());
                                cmd = sqlMappingNew.cmd;
                                setSQLTableMappingForAlias(primaryName, sqlMappingNew);
                            }
                        }
                        break;

                    case Relation.ONE_TO_MANY_UNI :
                    case Relation.ONE_TO_MANY_BI :
                    case Relation.MANY_TO_MANY_BI :
                        // Can't reference further than a collection/map so just return its mapping here
                        sqlMappingNew = new SQLTableMapping(sqlTbl, cmd, mapping);
                        cmd = sqlMappingNew.cmd;
                        setSQLTableMappingForAlias(primaryName, sqlMappingNew);
                        break;

                    default :
                        break;
                }
            }
            else
            {
                cmd = sqlMappingNew.cmd;
            }

            sqlMapping = sqlMappingNew;
        }

        return sqlMapping;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processParameterExpression(org.datanucleus.query.expression.ParameterExpression)
     */
    @Override
    protected Object processParameterExpression(ParameterExpression expr)
    {
        return processParameterExpression(expr, false);
    }

    /**
     * Method to process a parameter expression. The optional argument controls whether we should
     * create this as a parameter or as a literal (i.e the param value is known etc).
     * If the parameter doesn't have its value defined then returns ParameterLiteral
     * otherwise we get an XXXLiteral of the (declared) type of the parameter
     * @param expr The ParameterExpression
     * @param asLiteral Whether to create a SQLLiteral rather than a parameter literal
     */
    protected Object processParameterExpression(ParameterExpression expr, boolean asLiteral)
    {
        if (compileComponent == CompilationComponent.ORDERING)
        {
            // All JDBC drivers I know don't allow parameters in the order-by clause
            asLiteral = true;
        }

        if (expr.getPosition() >= 0)
        {
            if (paramNameByPosition == null)
            {
                paramNameByPosition = new HashMap<Integer, String>();
            }
            paramNameByPosition.put(Integer.valueOf(expr.getPosition()), expr.getId());
        }

        if (asLiteral)
        {
            // Parameter being represented as a literal (for whatever reason), so no longer precompilable
            if (isPrecompilable())
            {
                NucleusLogger.QUERY.debug("Parameter " + expr +
                    " is being resolved as a literal, so the query is no longer precompilable");
            }
            setNotPrecompilable();
        }

        // Find the parameter value if supplied
        Object paramValue = null;
        boolean paramValueSet = false;
        if (parameters != null && parameters.size() > 0)
        {
            // Check if the parameter has a value
            if (parameters.containsKey(expr.getId()))
            {
                // Named parameter
                paramValue = parameters.get(expr.getId());
                paramValueSet = true;
            }
            else if (parameterValueByName != null && parameterValueByName.containsKey(expr.getId()))
            {
                // Positional parameter, but already encountered
                paramValue = parameterValueByName.get(expr.getId());
                paramValueSet = true;
            }
            else
            {
                // Positional parameter, not yet encountered
                int position = positionalParamNumber;
                if (positionalParamNumber < 0)
                {
                    position = 0;
                }
                if (parameters.containsKey(Integer.valueOf(position)))
                {
                    paramValue = parameters.get(Integer.valueOf(position));
                    paramValueSet = true;
                    positionalParamNumber = position+1;
                    if (parameterValueByName == null)
                    {
                        parameterValueByName = new HashMap<String, Object>();
                    }
                    parameterValueByName.put(expr.getId(), paramValue);
                }
            }
        }

        // Find the type to use for the parameter
        JavaTypeMapping m = paramMappingForName.get(expr.getId());
        if (m == null)
        {
            // Try to determine from provided parameter value or from symbol table (declared type)
            if (paramValue != null)
            {
                String className = storeMgr.getClassNameForObjectID(paramValue, clr, ec);
                if (className != null)
                {
                    // Identity for persistable class
                    AbstractClassMetaData cmd = storeMgr.getMetaDataManager().getMetaDataForClass(className, clr);
                    if (cmd.getIdentityType() == IdentityType.APPLICATION)
                    {
                        Class cls = clr.classForName(className);
                        m = exprFactory.getMappingForType(cls, false);
                        m = new PersistableIdMapping((PersistableMapping) m);
                    }
                }

                if (m == null)
                {
                    // Use the type of the input parameter value
                    m = exprFactory.getMappingForType(paramValue.getClass(), false);
                }
                if (expr.getSymbol() != null && expr.getSymbol().getValueType() != null)
                {
                    if (!QueryUtils.queryParameterTypesAreCompatible(expr.getSymbol().getValueType(), paramValue.getClass()))
                    {
                        throw new QueryCompilerSyntaxException(LOCALISER.msg("021118", expr.getId(),
                            expr.getSymbol().getValueType().getName(), paramValue.getClass().getName()));
                    }
                    if (expr.getSymbol().getValueType() != paramValue.getClass())
                    {
                        // Mark as not precompilable since the supplied type implies a subclass of the declared type
                        setNotPrecompilable();
                    }
                }
            }
            else if (expr.getSymbol() != null && expr.getSymbol().getValueType() != null)
            {
                // Use the declared type of the parameter (explicit params)
                m = exprFactory.getMappingForType(expr.getSymbol().getValueType(), false);
            }
        }

        if (paramValue == null && expr.getSymbol() != null)
        {
            setNotPrecompilable();
        }

        SQLExpression sqlExpr = null;
        if (paramValueSet && asLiteral)
        {
            // We have the value and user wants a normal SQLLiteral so return as that
            if (paramValue == null)
            {
                // Make sure we get a NullLiteral when the param value is indeed null
                m = null;
            }
            sqlExpr = exprFactory.newLiteral(stmt, m, paramValue);
        }
        else if (paramValueSet && paramValue == null)
        {
            // Value is set to null, so enforce a NullLiteral for the case of null comparisons
            // e.g we don't want "field == ?", but instead "field IS NULL"
            m = null;
            sqlExpr = exprFactory.newLiteral(stmt, m, paramValue);
        }
        else
        {
            // Create a "parameter" Literal
            sqlExpr = exprFactory.newLiteralParameter(stmt, m, paramValue, expr.getId());
            if (sqlExpr instanceof ParameterLiteral)
            {
                ((ParameterLiteral)sqlExpr).setName(expr.getId());
            }

            if (expressionForParameter == null)
            {
                expressionForParameter = new HashMap<Object, SQLExpression>();
            }
            expressionForParameter.put(expr.getId(), sqlExpr);

            paramMappingForName.put(expr.getId(), m);
        }

        stack.push(sqlExpr);
        return sqlExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processInvokeExpression(org.datanucleus.query.expression.InvokeExpression)
     */
    protected Object processInvokeExpression(InvokeExpression expr)
    {
        // Find object that we invoke on
        Expression invokedExpr = expr.getLeft();
        SQLExpression invokedSqlExpr = null;
        if (invokedExpr == null)
        {
            // Static method
        }
        else if (invokedExpr instanceof PrimaryExpression)
        {
            processPrimaryExpression((PrimaryExpression)invokedExpr);
            invokedSqlExpr = stack.pop();
        }
        else if (invokedExpr instanceof Literal)
        {
            processLiteral((Literal)invokedExpr);
            invokedSqlExpr = stack.pop();
        }
        else if (invokedExpr instanceof ParameterExpression)
        {
            // TODO The second argument should depend on what method is being invoked on the param
            // in particular, if the method used in SQL allows input parameters as arguments
            processParameterExpression((ParameterExpression)invokedExpr, true);
            invokedSqlExpr = stack.pop();
        }
        else if (invokedExpr instanceof InvokeExpression)
        {
            processInvokeExpression((InvokeExpression)invokedExpr);
            invokedSqlExpr = stack.pop();
        }
        else if (invokedExpr instanceof VariableExpression)
        {
            processVariableExpression((VariableExpression)invokedExpr);
            invokedSqlExpr = stack.pop();
        }
        else if (invokedExpr instanceof ArrayExpression)
        {
            ArrayExpression arrExpr = (ArrayExpression)invokedExpr;
            SQLExpression[] arrSqlExprs = new SQLExpression[arrExpr.getArraySize()];
            for (int i=0;i<arrExpr.getArraySize();i++)
            {
                Expression arrElemExpr = arrExpr.getElement(i);
                arrElemExpr.evaluate(this);
                arrSqlExprs[i] = stack.pop();
            }
            JavaTypeMapping m = exprFactory.getMappingForType(Object[].class, false);
            invokedSqlExpr =
                new org.datanucleus.store.rdbms.sql.expression.ArrayExpression(stmt, m, arrSqlExprs);
        }
        else
        {
            throw new NucleusException("Dont currently support invoke expression " + invokedExpr);
        }

        String operation = expr.getOperation();
        List args = expr.getArguments();
        List sqlExprArgs = null;
        if (args != null)
        {
            sqlExprArgs = new ArrayList<SQLExpression>();
            Iterator<Expression> iter = args.iterator();
            while (iter.hasNext())
            {
                Expression argExpr = iter.next();
                if (argExpr instanceof PrimaryExpression)
                {
                    processPrimaryExpression((PrimaryExpression)argExpr);
                    sqlExprArgs.add(stack.pop());
                }
                else if (argExpr instanceof ParameterExpression)
                {
                    processParameterExpression((ParameterExpression)argExpr);
                    sqlExprArgs.add(stack.pop());
                }
                else if (argExpr instanceof InvokeExpression)
                {
                    processInvokeExpression((InvokeExpression)argExpr);
                    sqlExprArgs.add(stack.pop());
                }
                else if (argExpr instanceof Literal)
                {
                    processLiteral((Literal)argExpr);
                    sqlExprArgs.add(stack.pop());
                }
                else if (argExpr instanceof DyadicExpression)
                {
                    // Evaluate using this evaluator
                    argExpr.evaluate(this);
                    sqlExprArgs.add(stack.pop());
                }
                else if (argExpr instanceof VariableExpression)
                {
                    processVariableExpression((VariableExpression)argExpr);
                    sqlExprArgs.add(stack.pop());
                }
                else
                {
                    throw new NucleusException("Dont currently support invoke expression argument " + argExpr);
                }
            }

            if (expr.getOperation() != null && expr.getOperation().equals("INDEX"))
            {
                // Special case of index expression
                List<Expression> indexArgs = expr.getArguments();
                if (indexArgs == null || indexArgs.size() > 1)
                {
                    throw new NucleusException("Can only use INDEX with single argument");
                }

                PrimaryExpression indexExpr = (PrimaryExpression)indexArgs.get(0);
                String joinAlias = indexExpr.getId();
                String collExprName = joinAlias;
                if (explicitJoinPrimaryByAlias != null)
                {
                    collExprName = explicitJoinPrimaryByAlias.get(joinAlias);
                    if (collExprName == null)
                    {
                        throw new NucleusException("Unable to locate primary expression for alias " + joinAlias);
                    }
                }

                // Find an expression for the collection field
                List<String> tuples = new ArrayList<String>();
                StringTokenizer primTokenizer = new StringTokenizer(collExprName, ".");
                while (primTokenizer.hasMoreTokens())
                {
                    String token = primTokenizer.nextToken();
                    tuples.add(token);
                }
                PrimaryExpression collPrimExpr = new PrimaryExpression(tuples);
                processPrimaryExpression(collPrimExpr);
                SQLExpression collSqlExpr = stack.pop();
                sqlExprArgs.add(collSqlExpr);
            }
        }

        // Invoke the method
        SQLExpression sqlExpr = null;
        if (invokedSqlExpr != null)
        {
            sqlExpr = invokedSqlExpr.invoke(operation, sqlExprArgs);
        }
        else
        {
            sqlExpr = exprFactory.invokeMethod(stmt, null, operation, null, sqlExprArgs);
        }

        stack.push(sqlExpr);
        return sqlExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processSubqueryExpression(org.datanucleus.query.expression.SubqueryExpression)
     */
    @Override
    protected Object processSubqueryExpression(SubqueryExpression expr)
    {
        String keyword = expr.getKeyword();
        Expression subqueryExpr = expr.getRight();
        if (subqueryExpr instanceof VariableExpression)
        {
            processVariableExpression((VariableExpression)subqueryExpr);
            SQLExpression subquerySqlExpr = stack.pop();
            if (keyword != null && keyword.equals("EXISTS"))
            {
                // EXISTS expressions need to be Boolean
                if (subquerySqlExpr instanceof org.datanucleus.store.rdbms.sql.expression.SubqueryExpression)
                {
                    SQLStatement subStmt =
                        ((org.datanucleus.store.rdbms.sql.expression.SubqueryExpression)subquerySqlExpr).getSubqueryStatement();
                    subquerySqlExpr = new BooleanSubqueryExpression(stmt, keyword, subStmt);
                }
                else
                {
                    SQLStatement subStmt = ((SubqueryExpressionComponent)subquerySqlExpr).getSubqueryStatement();
                    subquerySqlExpr = new BooleanSubqueryExpression(stmt, keyword, subStmt);
                }
            }
            else if (subquerySqlExpr instanceof org.datanucleus.store.rdbms.sql.expression.SubqueryExpression)
            {
                SQLStatement subStmt =
                    ((org.datanucleus.store.rdbms.sql.expression.SubqueryExpression)subquerySqlExpr).getSubqueryStatement();
                subquerySqlExpr = new BooleanSubqueryExpression(stmt, keyword, subStmt);
            }
            else if (subquerySqlExpr instanceof NumericSubqueryExpression)
            {
                // Apply keyword (e.g ALL, SOME, ANY) to numeric expressions
                ((NumericSubqueryExpression)subquerySqlExpr).setKeyword(keyword);
            }
            stack.push(subquerySqlExpr);
            return subquerySqlExpr;
        }
        else
        {
            throw new NucleusException("Dont currently support SubqueryExpression " + keyword +
                " for type " + subqueryExpr);
        }
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processAddExpression(org.datanucleus.query.expression.Expression)
     */
    protected Object processAddExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        if (left instanceof ParameterLiteral && !(right instanceof ParameterLiteral))
        {
            left = replaceParameterLiteral((ParameterLiteral)left, right.getJavaTypeMapping());
        }
        else if (right instanceof ParameterLiteral && !(left instanceof ParameterLiteral))
        {
            right = replaceParameterLiteral((ParameterLiteral)right, left.getJavaTypeMapping());
        }

        SQLExpression resultExpr = left.add(right);
        stack.push(resultExpr);
        return resultExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processDivExpression(org.datanucleus.query.expression.Expression)
     */
    protected Object processDivExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        SQLExpression resultExpr = left.div(right);
        stack.push(resultExpr);
        return resultExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processMulExpression(org.datanucleus.query.expression.Expression)
     */
    protected Object processMulExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        SQLExpression resultExpr = left.mul(right);
        stack.push(resultExpr);
        return resultExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processSubExpression(org.datanucleus.query.expression.Expression)
     */
    protected Object processSubExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        if (left instanceof ParameterLiteral && !(right instanceof ParameterLiteral))
        {
            left = replaceParameterLiteral((ParameterLiteral)left, right.getJavaTypeMapping());
        }
        else if (right instanceof ParameterLiteral && !(left instanceof ParameterLiteral))
        {
            right = replaceParameterLiteral((ParameterLiteral)right, left.getJavaTypeMapping());
        }

        SQLExpression resultExpr = left.sub(right);
        stack.push(resultExpr);
        return resultExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processDistinctExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processDistinctExpression(Expression expr)
    {
        SQLExpression sqlExpr = stack.pop();
        sqlExpr.distinct();
        stack.push(sqlExpr);
        return sqlExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processComExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processComExpression(Expression expr)
    {
        // Bitwise complement - only for integer values
        SQLExpression sqlExpr = stack.pop();
        SQLExpression resultExpr = sqlExpr.com();
        stack.push(resultExpr);
        return resultExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processModExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processModExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        SQLExpression resultExpr = left.mod(right);
        stack.push(resultExpr);
        return resultExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processNegExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processNegExpression(Expression expr)
    {
        SQLExpression sqlExpr = stack.pop();
        SQLExpression resultExpr = sqlExpr.neg();
        stack.push(resultExpr);
        return resultExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processNotExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processNotExpression(Expression expr)
    {
        // Logical complement - only for boolean values
        SQLExpression sqlExpr = stack.pop();
        SQLExpression resultExpr = sqlExpr.not();
        stack.push(resultExpr);
        return resultExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processCastExpression(org.datanucleus.query.expression.CastExpression)
     */
    @Override
    protected Object processCastExpression(Expression expr)
    {
        SQLExpression right = stack.pop(); // The cast literal
        SQLExpression left = stack.pop(); // The expression to cast
        SQLExpression castExpr = left.cast(right);
        stack.push(castExpr);
        return castExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processCaseExpression(org.datanucleus.query.expression.CaseExpression)
     */
    @Override
    protected Object processCaseExpression(CaseExpression expr)
    {
        Map<Expression, Expression> conditions = expr.getConditions();
        Iterator<Map.Entry<Expression, Expression>> whenExprIter = conditions.entrySet().iterator();
        SQLExpression[] whenSqlExprs = new SQLExpression[conditions.size()];
        SQLExpression[] actionSqlExprs = new SQLExpression[conditions.size()];
        int i = 0;
        while (whenExprIter.hasNext())
        {
            Map.Entry<Expression, Expression> entry = whenExprIter.next();
            Expression whenExpr = entry.getKey();
            whenExpr.evaluate(this);
            whenSqlExprs[i] = stack.pop();

            Expression actionExpr = entry.getValue();
            actionExpr.evaluate(this);
            actionSqlExprs[i] = stack.pop();

            i++;
        }

        Expression elseExpr = expr.getElseExpression();
        elseExpr.evaluate(this);
        SQLExpression elseSqlExpr = stack.pop();

        SQLExpression caseSqlExpr = new org.datanucleus.store.rdbms.sql.expression.CaseExpression(
            whenSqlExprs, actionSqlExprs, elseSqlExpr);
        stack.push(caseSqlExpr);
        return caseSqlExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processIsExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processIsExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        SQLExpression instanceofExpr = left.is(right, false);
        stack.push(instanceofExpr);
        return instanceofExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processIsnotExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processIsnotExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        SQLExpression instanceofExpr = left.is(right, true);
        stack.push(instanceofExpr);
        return instanceofExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processInExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processInExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        SQLExpression inExpr = left.in(right, false);
        stack.push(inExpr);
        return inExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processNotInExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processNotInExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        SQLExpression inExpr = left.in(right, true);
        stack.push(inExpr);
        return inExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processCreatorExpression(org.datanucleus.query.expression.CreatorExpression)
     */
    @Override
    protected Object processCreatorExpression(CreatorExpression expr)
    {
        String className = expr.getId();
        Class cls = null;
        try
        {
            cls = clr.classForName(className);
        }
        catch (ClassNotResolvedException cnre)
        {
            if (importsDefinition != null)
            {
                cls = importsDefinition.resolveClassDeclaration(className, clr, null);
            }
        }

        List<SQLExpression> ctrArgExprs = null;
        List args = expr.getArguments();
        if (args != null)
        {
            Class[] ctrArgTypes = new Class[args.size()];
            ctrArgExprs = new ArrayList<SQLExpression>(args.size());
            Iterator iter = args.iterator();
            int i = 0;
            while (iter.hasNext())
            {
                Expression argExpr = (Expression)iter.next();
                SQLExpression sqlExpr = (SQLExpression)evaluate(argExpr);
                ctrArgExprs.add(sqlExpr);
                if (sqlExpr instanceof NewObjectExpression)
                {
                    ctrArgTypes[i] = ((NewObjectExpression)sqlExpr).getNewClass();
                }
                else if (sqlExpr.getJavaTypeMapping() instanceof OIDMapping)
                {
                    ctrArgTypes[i] = clr.classForName(sqlExpr.getJavaTypeMapping().getType());
                }
                else
                {
                    ctrArgTypes[i] = sqlExpr.getJavaTypeMapping().getJavaType();
                }
                i++;
            }

            // Check that this class has the required constructor
            Constructor ctr = ClassUtils.getConstructorWithArguments(cls, ctrArgTypes);
            if (ctr == null)
            {
                throw new NucleusUserException(LOCALISER.msg("021033", className,
                    StringUtils.objectArrayToString(ctrArgTypes)));
            }
        }

        NewObjectExpression newExpr = new NewObjectExpression(stmt, cls, ctrArgExprs);
        stack.push(newExpr);
        return newExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processLikeExpression(org.datanucleus.query.expression.Expression)
     */
    @Override
    protected Object processLikeExpression(Expression expr)
    {
        SQLExpression right = stack.pop();
        SQLExpression left = stack.pop();
        List args = new ArrayList();
        args.add(right);

        SQLExpression likeExpr = exprFactory.invokeMethod(stmt, String.class.getName(), "like", left, args);
        stack.push(likeExpr);
        return likeExpr;
    }

    /* (non-Javadoc)
     * @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processVariableExpression(org.datanucleus.query.expression.VariableExpression)
     */
    @Override
    protected Object processVariableExpression(VariableExpression expr)
    {
        Symbol varSym = expr.getSymbol();
        String varName = varSym.getQualifiedName();
        if (hasSQLTableMappingForAlias(varName))
        {
            // Variable already found
            SQLTableMapping tblMapping = getSQLTableMappingForAlias(varName);
            SQLExpression sqlExpr = exprFactory.newExpression(tblMapping.table.getSQLStatement(),
                tblMapping.table, tblMapping.mapping);
            stack.push(sqlExpr);
            return sqlExpr;
        }
        else if (compilation.getCompilationForSubquery(varName) != null)
        {
            // Subquery variable
            QueryCompilation subCompilation = compilation.getCompilationForSubquery(varName);
            AbstractClassMetaData subCmd = ec.getMetaDataManager().getMetaDataForClass(subCompilation.getCandidateClass(), ec.getClassLoaderResolver());

            // Create subquery statement, using any provided alias if possible
            String subAlias = null;
            if (subCompilation.getCandidateAlias() != null && !subCompilation.getCandidateAlias().equals(candidateAlias))
            {
                subAlias = subCompilation.getCandidateAlias();
            }
            StatementResultMapping subqueryResultMapping = new StatementResultMapping();
            // TODO Fix "avg(something)" arg - not essential but is a hack right now
            SQLStatement subStmt = RDBMSQueryUtils.getStatementForCandidates(stmt, subCmd,
                null, ec, subCompilation.getCandidateClass(), true, "avg(something)", subAlias, null);
            QueryToSQLMapper sqlMapper = new QueryToSQLMapper(subStmt, subCompilation, parameters,
                null, subqueryResultMapping, subCmd, fetchPlan, ec, importsDefinition, options,
                extensionsByName);
            sqlMapper.setParentMapper(this);
            sqlMapper.compile();
            if (subqueryResultMapping.getNumberOfResultExpressions() > 1)
            {
                throw new NucleusUserException("Number of result expressions in subquery should be 1");
            }

            SQLExpression subExpr = null;
            // TODO Cater for subquery select of its own candidate
            if (subqueryResultMapping.getNumberOfResultExpressions() == 0)
            {
                subExpr = new org.datanucleus.store.rdbms.sql.expression.SubqueryExpression(stmt, subStmt);
            }
            else
            {
                JavaTypeMapping subMapping =
                    ((StatementMappingIndex)subqueryResultMapping.getMappingForResultExpression(0)).getMapping();
                if (subMapping instanceof TemporalMapping)
                {
                    subExpr = new TemporalSubqueryExpression(stmt, subStmt);
                }
                else if (subMapping instanceof StringMapping)
                {
                    subExpr = new StringSubqueryExpression(stmt, subStmt);
                }
                else
                {
                    subExpr = new NumericSubqueryExpression(stmt, subStmt);
                }
                if (subExpr.getJavaTypeMapping() == null)
                {
                    subExpr.setJavaTypeMapping(subMapping);
                }
            }
            stack.push(subExpr);
            return subExpr;
        }
        else if (stmt.getParentStatement() != null && parentMapper != null &&
                parentMapper.candidateAlias != null && parentMapper.candidateAlias.equals(varName))
        {
            // Variable in subquery linking back to parent query
            SQLExpression varExpr = exprFactory.newExpression(stmt.getParentStatement(),
                stmt.getParentStatement().getPrimaryTable(),
                stmt.getParentStatement().getPrimaryTable().getTable().getIdMapping());
            stack.push(varExpr);
            return varExpr;
        }
        else
        {
            // Variable never met before, so return as UnboundExpression - process later if needing binding
            NucleusLogger.QUERY.debug(">> QueryToSQL.processVariable (unbound) variable=" + varName +
                " is not yet bound so returning UnboundExpression");
            UnboundExpression unbExpr = new UnboundExpression(stmt, varName);
            stack.push(unbExpr);
            return unbExpr;
        }
    }

    protected SQLExpression processUnboundExpression(UnboundExpression expr)
    {
        String varName = expr.getVariableName();
        Symbol varSym = compilation.getSymbolTable().getSymbol(varName);
        SQLExpression sqlExpr = bindVariable(expr, varSym.getValueType());
        if (sqlExpr != null)
        {
            stack.push(sqlExpr);
            return sqlExpr;
        }
        throw new NucleusUserException("Variable " + varName + " is unbound and cannot be determined");
    }

    /**
     * Convenience method to return a parameter-based literal using the supplied mapping to replace
     * the provided ParameterLiteral (generated before its type was known).
     * @param paramLit The parameter literal
     * @param mapping Mapping to use
     * @return The replacement expression
     */
    protected SQLExpression replaceParameterLiteral(ParameterLiteral paramLit, JavaTypeMapping mapping)
    {
        return exprFactory.newLiteralParameter(stmt, mapping, paramLit.getValue(),
            paramLit.getParameterName());
    }

    /* (non-Javadoc)
     * @see org.datanucleus.store.rdbms.query.QueryGenerator#useParameterExpressionAsLiteral(org.datanucleus.store.rdbms.sql.expression.SQLLiteral)
     */
    public void useParameterExpressionAsLiteral(SQLLiteral paramLiteral)
    {
        paramLiteral.setNotParameter();
        setNotPrecompilable();
    }

    public boolean hasExtension(String key)
    {
        return (extensionsByName == null ? false : extensionsByName.containsKey(key));
    }

    public Object getValueForExtension(String key)
    {
        return (extensionsByName == null ? null : extensionsByName.get(key));
    }

    /**
     * Convenience method to return the required join type for the specified alias.
     * If the table has a join type defined as an extension "datanucleus.query.jdoql.{alias}.join"
     * then this returns the type. Otherwise returns null.
     * @param alias The alias
     * @return The join type required (if any)
     */
    public org.datanucleus.store.rdbms.sql.SQLJoin.JoinType getRequiredJoinTypeForAlias(String alias)
    {
        if (alias == null)
        {
            return null;
        }
        else if (alias.equals(candidateAlias))
        {
            return null;
        }

        String extensionName = "datanucleus.query.jdoql." + alias + ".join";
        org.datanucleus.store.rdbms.sql.SQLJoin.JoinType joinType = null;
        if (hasExtension(extensionName))
        {
            String joinValue = (String)getValueForExtension(extensionName);
            if (joinValue.equalsIgnoreCase("INNERJOIN"))
            {
                joinType = org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.INNER_JOIN;
            }
            else if (joinValue.equalsIgnoreCase("LEFTOUTERJOIN"))
            {
                joinType = org.datanucleus.store.rdbms.sql.SQLJoin.JoinType.LEFT_OUTER_JOIN;
            }
        }
        return joinType;
    }

    /**
     * Convenience method to return the value of a field of the supplied object.
     * If the object is null then returns null for the field.
     * @param obj The object
     * @param fieldName The field name
     * @return The field value
     */
    protected Object getValueForObjectField(Object obj, String fieldName)
    {
        if (obj != null)
        {
            Object paramFieldValue = null;
            if (ec.getApiAdapter().isPersistable(obj))
            {
                ObjectProvider paramSM = ec.findObjectProvider(obj);
                AbstractClassMetaData paramCmd = ec.getMetaDataManager().getMetaDataForClass(obj.getClass(), clr);
                AbstractMemberMetaData paramFieldMmd = paramCmd.getMetaDataForMember(fieldName);
                if (paramSM != null)
                {
                    ec.getApiAdapter().isLoaded(paramSM, paramFieldMmd.getAbsoluteFieldNumber());
                    paramFieldValue = paramSM.provideField(paramFieldMmd.getAbsoluteFieldNumber());
                }
                else
                {
                    paramFieldValue = ClassUtils.getValueOfFieldByReflection(obj, fieldName);
                }
            }
            else
            {
                paramFieldValue = ClassUtils.getValueOfFieldByReflection(obj, fieldName);
            }

            return paramFieldValue;
        }
        else
        {
            return null;
        }
    }

    protected SQLTableMapping getSQLTableMappingForAlias(String alias)
    {
        if (alias == null)
        {
            return null;
        }
        if (options.contains(OPTION_CASE_INSENSITIVE))
        {
            return sqlTableByPrimary.get(alias.toUpperCase());
        }
        else
        {
            return sqlTableByPrimary.get(alias);
        }
    }

    public SQLTable getSQLTableForAlias(String alias)
    {
        SQLTableMapping tblMapping = getSQLTableMappingForAlias(alias);
        return (tblMapping != null ? tblMapping.table : null);
    }

    /**
     * Returns the alias used for the specified SQLTable.
     * If we have a variable "var1" and there is an access to a field also, then we may have entries
     * for "var1" and "var1.field". In this case we return the shortest.
     * @param tbl The table
     * @return The query name alias for this table
     */
    public String getAliasForSQLTable(SQLTable tbl)
    {
        Iterator<Map.Entry<String, SQLTableMapping>> iter = sqlTableByPrimary.entrySet().iterator();
        String alias = null;
        while (iter.hasNext())
        {
            Map.Entry<String, SQLTableMapping> entry = iter.next();
            if (entry.getValue().table == tbl)
            {
                // Take the shortest alias (since we could have "var", "var.field1" etc)
                if (alias == null)
                {
                    alias = entry.getKey();
                }
                else
                {
                    if (entry.getKey().length() < alias.length())
                    {
                        alias = entry.getKey();
                    }
                }
            }
        }
        return alias;
    }

    protected void setSQLTableMappingForAlias(String alias, SQLTableMapping mapping)
    {
        if (alias == null)
        {
            return;
        }
        if (options.contains(OPTION_CASE_INSENSITIVE))
        {
            sqlTableByPrimary.put(alias.toUpperCase(), mapping);
        }
        else
        {
            sqlTableByPrimary.put(alias, mapping);
        }
    }

    protected boolean hasSQLTableMappingForAlias(String alias)
    {
        if (options.contains(OPTION_CASE_INSENSITIVE))
        {
            return sqlTableByPrimary.containsKey(alias.toUpperCase());
        }
        else
        {
            return sqlTableByPrimary.containsKey(alias);
        }
    }

    /**
     * Method to bind the specified variable to the table and mapping.
     * @param varName Variable name
     * @param cmd Metadata for this variable type
     * @param sqlTbl Table for this variable
     * @param mapping The mapping of this variable in the table
     */
    public void bindVariable(String varName, AbstractClassMetaData cmd, SQLTable sqlTbl, JavaTypeMapping mapping)
    {
        SQLTableMapping m = getSQLTableMappingForAlias(varName);
        if (m != null)
        {
            throw new NucleusException("Variable " + varName + " is already bound to " + m.table +
                " yet attempting to bind to " + sqlTbl);
        }
        else
        {
            NucleusLogger.QUERY.debug(">> QueryToSQL.bindVariable variable " + varName +
                " being bound to table=" + sqlTbl + " mapping=" + mapping);
        }
        m = new SQLTableMapping(sqlTbl, cmd, mapping);
        setSQLTableMappingForAlias(varName, m);
    }

    /**
     * Method to bind the specified unbound variable (as cross join) on the assumption that the type
     * is a persistable class.
     * @param expr Unbound expression
     * @param type The type to bind as
     */
    public SQLExpression bindVariable(UnboundExpression expr, Class type)
    {
        String varName = expr.getVariableName();
        Symbol varSym = compilation.getSymbolTable().getSymbol(varName);
        if (varSym.getValueType() == null)
        {
            varSym.setValueType(type);
        }
        AbstractClassMetaData cmd = ec.getMetaDataManager().getMetaDataForClass(type, clr);
        if (cmd != null)
        {
            // Variable is persistent type, so add cross join (may need changing later on in compilation)
            DatastoreClass varTable = storeMgr.getDatastoreClass(varSym.getValueType().getName(), clr);
            SQLTable varSqlTbl = stmt.crossJoin(varTable, "VAR_" + varName, null);
            SQLTableMapping varSqlTblMapping = new SQLTableMapping(varSqlTbl, cmd, varTable.getIdMapping());
            setSQLTableMappingForAlias(varName, varSqlTblMapping);
            return exprFactory.newExpression(stmt, varSqlTbl, varTable.getIdMapping());
        }
        return null;
    }

    /**
     * Method to bind the specified parameter to the defined type.
     * If the parameter is already bound (declared in the query perhaps, or bound via an earlier usage) then
     * does nothing.
     * @param paramName Name of the parameter
     * @param type The type (or subclass)
     */
    public void bindParameter(String paramName, Class type)
    {
        Symbol paramSym = compilation.getSymbolTable().getSymbol(paramName);
        if (paramSym != null && paramSym.getValueType() == null)
        {
            paramSym.setValueType(type);
        }
    }

    /**
     * Accessor for the type of a variable if already known (declared?).
     * @param varName Name of the variable
     * @return The type if it is known
     */
    public Class getTypeOfVariable(String varName)
    {
        Symbol sym = compilation.getSymbolTable().getSymbol(varName);
        if (sym != null && sym.getValueType() != null)
        {
            return sym.getValueType();
        }
        return null;
    }

    /**
     * Accessor for whether the query has explicit variables.
     * If not then has implicit variables, meaning that they could potentially be rebound later
     * if prematurely bound in a particular way.
     * @return Whether the query has explicit variables
     */
    public boolean hasExplicitJoins()
    {
        return options.contains(OPTION_EXPLICIT_JOINS);
    }

    /**
     * Convenience method to return a boolean expression suitable for using in a filter.
     * Will return the input expression unless it is simply a reference to a field of a class
     * such as in a filter clause like <pre>myBoolField</pre>. In that case we return a boolean
     * like <pre>myBoolField == TRUE</pre>.
     * @param expr The expression to check
     * @return The expression valid for use in the filter
     */
    protected BooleanExpression getBooleanExpressionForUseInFilter(BooleanExpression expr)
    {
        if (compileComponent != CompilationComponent.FILTER)
        {
            return expr;
        }
        if (!expr.hasClosure())
        {
            // Add closure to the boolean expression
            return new BooleanExpression(expr, Expression.OP_EQ,
                new BooleanLiteral(stmt, expr.getJavaTypeMapping(), Boolean.TRUE, null));
        }
        return expr;
    }

    /**
     * Convenience method to resolve a class name.
     * @param className The class name
     * @return The class it relates to (if found)
     */
    public Class resolveClass(String className)
    {
        Class cls = null;
        try
        {
            cls = clr.classForName(className);
        }
        catch (ClassNotResolvedException cnre)
        {
            if (importsDefinition != null)
            {
                cls = importsDefinition.resolveClassDeclaration(className, clr, null);
            }
        }
        return cls;
    }
}
TOP

Related Classes of org.datanucleus.store.rdbms.query.QueryToSQLMapper$SQLTableMapping

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.