Package org.jpox.store.rdbms.query

Source Code of org.jpox.store.rdbms.query.JDOQLQueryCompiler

/**********************************************************************
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.jpox.store.rdbms.query;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Map.Entry;

import org.jpox.ClassLoaderResolver;
import org.jpox.ObjectManager;
import org.jpox.ObjectManagerHelper;
import org.jpox.api.ApiAdapter;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.metadata.AbstractClassMetaData;
import org.jpox.metadata.AbstractMemberMetaData;
import org.jpox.metadata.MetaDataManager;
import org.jpox.metadata.Relation;
import org.jpox.store.Extent;
import org.jpox.store.exceptions.NoSuchPersistentFieldException;
import org.jpox.store.mapped.MappedStoreManager;
import org.jpox.store.mapped.expression.AggregateExpression;
import org.jpox.store.mapped.expression.ArrayExpression;
import org.jpox.store.mapped.expression.ClassExpression;
import org.jpox.store.mapped.expression.LogicSetExpression;
import org.jpox.store.mapped.expression.NullLiteral;
import org.jpox.store.mapped.expression.QueryExpression;
import org.jpox.store.mapped.expression.ScalarExpression;
import org.jpox.store.mapped.expression.SubqueryExpression;
import org.jpox.store.mapped.expression.UnboundVariable;
import org.jpox.store.mapped.mapping.JavaTypeMapping;
import org.jpox.store.mapped.query.CollectionCandidates;
import org.jpox.store.mapped.query.Queryable;
import org.jpox.store.query.AbstractJDOQLQuery;
import org.jpox.store.query.AbstractJavaQuery;
import org.jpox.store.query.JDOQLQueryHelper;
import org.jpox.store.query.JPOXQueryInvalidParametersException;
import org.jpox.store.query.Parser;
import org.jpox.store.query.QueryCompiler;
import org.jpox.store.query.QueryCompilerSyntaxException;
import org.jpox.store.query.Query.SubqueryDefinition;
import org.jpox.util.Imports;
import org.jpox.util.StringUtils;

/**
* Compiler of JDOQL queries for RDBMS datastores.
* Takes the input query and provides two forms of compilation :-
* <ul>
* <li>preCompile - where all is compiled except that parameter values arent known</li>
* <li>executionCompile - like preCompile except also using the parameter values, just before execution</li>
* </li>
* <p>
* During either compilation step other parts of the query are resolved and are available for update
* by accessors.
* </p>
* @version $Revision$
*/
public class JDOQLQueryCompiler extends JavaQueryCompiler
{
    /** Flag for whether we have explicit parameters. JDO allows both types but either all explicit or all implicit. */
    protected boolean explicitParameters = false;

    /** Flag for whether we have explicit variables. JDO allows both types but either all explicit or all implicit. */
    protected boolean explicitVariables = false;

    /** State variable for the current implicit parameter number. */
    protected int implicitParamNo = 0;

    /** Parameter map when treating as a subquery. */
    protected Map subqueryParameters;

    /**
     * Flag for whether we have set the alias of the subquery. If this is true then the subquery
     * would have had use of "this" implying the subquery candidate. Otherwise "this" would refer to
     * the parent query.
     */
    protected boolean subqueryAliasSet = false;

    /**
     * Constructor.
     * @param query JDOQL query to compile
     * @param imports Imports handler to use for class resolution
     * @param parameters map of declared parameters in the query
     */
    public JDOQLQueryCompiler(AbstractJDOQLQuery query, Imports imports, Map parameters)
    {
        super(query, imports, parameters);
        this.language = "JDOQL";
    }

    /**
     * Method to set the parent expression that this is a subquery of.
     * Should be set before calling preCompile/executionCompile.
     * @param parentExpr The parent expression
     */
    public void processAsSubquery(QueryExpression parentExpr, String candidateExpression, Map paramMap)
    {
        this.parentExpr = parentExpr;
        this.subqueryCandidateExpr = candidateExpression;
        this.subqueryParameters = paramMap;
    }

    /**
     * Method to compile the query.
     * @param type Type of compilation
     * @return the compilation artifact (if any)
     */
    public Object compile(int type)
    {
        switch (type)
        {
            case COMPILE_EXPLICIT_PARAMETERS :
            {
                compileExplicitParameters();
                ObjectManager om = query.getObjectManager();
                if (query.getExplicitParameters() != null && query.getExplicitParameters().length() > 0)
                {
                    // Using explicit parameters
                    explicitParameters = true;
                    if (parameters != null)
                    {
                        ApiAdapter api = om.getApiAdapter();
                        if (parameters.size() != parameterTypesByName.size())
                        {
                            // Number of explicit parameters doesn't match the number of parameter values
                            throw new JPOXQueryInvalidParametersException(LOCALISER.msg("021025",
                                "" + parameterTypesByName.size(), "" + parameters.size()));
                        }

                        for (Iterator it = parameterTypesByName.entrySet().iterator(); it.hasNext(); )
                        {
                            Map.Entry entry = (Entry) it.next();
                            Object key = entry.getKey();
                            Object value = parameters.get(key);
                            if (value == null)
                            {
                                // primitive parameter can't be null
                                if (((Class)entry.getValue()).isPrimitive())
                                {
                                    throw new JPOXQueryInvalidParametersException(LOCALISER.msg("021026",
                                        entry.getKey(), ((Class)entry.getValue()).getName()));
                                }
                            }
                            else
                            {
                                if (api.isPersistable(value))
                                {
                                    // parameters bound to a different PM can't be used with this PM
                                    ObjectManager valueOM = ObjectManagerHelper.getObjectManager(value);
                                    if (valueOM != null && om != valueOM)
                                    {
                                        throw new JPOXUserException(LOCALISER.msg("021068", key));
                                    }
                                }
                            }
                        }
                    }
                }
                else
                {
                    // Using implicit parameters
                    // TODO Need to check the number of implicit params in the query (only do-able after compiling)
                    if (parameters != null)
                    {
                        ApiAdapter api = om.getApiAdapter();
                        Iterator parameterEntryIter = parameters.entrySet().iterator();
                        while (parameterEntryIter.hasNext())
                        {
                            Map.Entry entry = (Map.Entry)parameterEntryIter.next();
                            Object value = entry.getValue();
                            if (api.isPersistable(value))
                            {
                                // parameters bound to a different PM can't be used with this PM
                                ObjectManager valueOM = ObjectManagerHelper.getObjectManager(value);
                                if (valueOM != null && om != valueOM)
                                {
                                    // TODO Really should put the param name in here, but we don't know it
                                    throw new JPOXUserException(LOCALISER.msg("021068", value));
                                }
                            }
                        }
                    }
                }
                return null;
            }
            case COMPILE_EXPLICIT_VARIABLES :
            {
                compileExplicitVariables();
                if (query.getExplicitVariables() != null && query.getExplicitVariables().length() > 0)
                {
                    // Using explicit variables
                    explicitVariables = true;
                }
                return null;
            }
            case COMPILE_SYNTAX :
            {
                compile(COMPILE_EXPLICIT_PARAMETERS);
                compile(COMPILE_EXPLICIT_VARIABLES);
                preCompile();
                return null;
            }
            case COMPILE_EXECUTION :
            {
                compile(COMPILE_EXPLICIT_PARAMETERS);
                compile(COMPILE_EXPLICIT_VARIABLES);
                return executionCompile();
            }
            default :
                return super.compile(type);
        }
    }

    /**
     * Perform the actual compilation of the query.
     * @param qs The QueryExpression to use during compilation (if required)
     */
    protected void performCompile(QueryExpression qs)
    {
        if (parentExpr != null)
        {
            // Compile any candidate-expression when this is a subquery
            compileSubqueryCandidateExpression(true);
        }

        // Compile and apply the result/result-class to the query
        fieldExpressions.clear();
        compileResult(qs, query.getResult());
        ScalarExpression[] resultFieldExprs = (ScalarExpression[]) fieldExpressions.toArray(new ScalarExpression[fieldExpressions.size()]);
        for (int i=0; i<resultFieldExprs.length; i++)
        {
            if (resultFieldExprs[i].getLogicSetExpression() == null)
            {
                if (resultFieldExprs[i] instanceof UnboundVariable)
                {
                    throw new JPOXUserException(LOCALISER.msg("021049", ((UnboundVariable)resultFieldExprs[i]).getVariableName()));
                }
            }
            qs.crossJoin(resultFieldExprs[i].getLogicSetExpression(),true);
        }

        // Compile and apply the filter to the query
        compileFilter(qs, query.getFilter());

        // Compile and apply the grouping to the query
        ScalarExpression[] groupingFieldExprs = null;
        String grouping = query.getGrouping();
        if (grouping != null && grouping.length() > 0)
        {
            // Compile the "grouping"
            fieldExpressions.clear();
            compileGrouping(qs, grouping);
            groupingFieldExprs = (ScalarExpression[]) fieldExpressions.toArray(new ScalarExpression[fieldExpressions.size()]);
        }

        ScalarExpression[] havingFieldExprs = null;
        String having = query.getHaving();
        if (having != null && having.length() > 0)
        {
            // Compile the "having"
            fieldExpressions.clear();
            compileHaving(qs, having);
            havingFieldExprs = (ScalarExpression[]) fieldExpressions.toArray(new ScalarExpression[fieldExpressions.size()]);
        }

        // Compile and apply the ordering to the query
        fieldExpressions.clear();
        compileOrdering(qs, query.getOrdering());
        ScalarExpression[] orderingFieldExprs =
            (ScalarExpression[])fieldExpressions.toArray(new ScalarExpression[fieldExpressions.size()]);

        // Check that all result expression fields having fields and ordering fields are defined in any group spec
        if (groupingFieldExprs != null)
        {
            // Check that all ordering expression fields are in the grouping clause
            checkExpressionsAgainstGrouping(orderingFieldExprs, groupingFieldExprs, "021069");

            // Check having fields against what is in the grouping spec
            checkExpressionsAgainstGrouping(havingFieldExprs, groupingFieldExprs, "021071");

            // Check that all result clause fields are in the grouping clause
            checkExpressionsAgainstGrouping(resultFieldExprs, groupingFieldExprs, "021070");
        }

        // Compile and apply the range to the query
        compileRange(qs);

        // Check that all variables have been bound to the query
        checkVariableBinding();
    }

    /**
     * Convenience method to process the candidates for this query.
     * Processes the "candidateClassName" and "candidateClass" and sets up "candidates".
     */
    protected void compileCandidates()
    {
        ObjectManager om = query.getObjectManager();

        if (parentExpr != null)
        {
            // Subquery, so cater for different ways of specifying the candidate class
            if (subqueryCandidateExpr != null)
            {
                // Query being treated as a subquery for this compilation
                if (query.getCandidateClassName() == null)
                {
                    // No candidate class specified for subquery somehow, so derive from candidate-expression
                    Class candCls = getClassForSubqueryCandidateExpression();
                    query.setCandidateClassName(candCls.getName());
                }
            }
            else
            {
                // Single-string subquery
                String from = query.getFrom();
                if (from != null)
                {
                    if (from.indexOf(' ') > 0)
                    {
                        // "<candidate-expression> alias"
                        String candidateExpr = from.substring(0, from.indexOf(' ')).trim();
                        if (candidateExpr.startsWith("this"))
                        {
                            // <candidate-expression> is an expression for joining to the parent query
                            subqueryCandidateExpr = candidateExpr.trim();
                            Class cls = getClassForSubqueryCandidateExpression();
                            query.setCandidateClassName(cls.getName());
                        }
                        else
                        {
                            // <candidate-expression> is a class
                            query.setCandidateClassName(candidateExpr);
                        }
                        this.candidateAlias = from.substring(from.indexOf(' ')+1).trim();
                        this.subqueryAliasSet = true;
                    }
                    else
                    {
                        if (from.startsWith("this"))
                        {
                            // <candidate-expression> is an expression for joining to the parent query
                            subqueryCandidateExpr = from.trim();
                            Class cls = getClassForSubqueryCandidateExpression();
                            query.setCandidateClassName(cls.getName());
                        }
                        else
                        {
                            // <candidate-expression> is a class
                            query.setCandidateClassName(from);
                        }
                    }
                }
            }
            if (this.candidateAlias.equals("this"))
            {
                // Cant use "this" in subquery so replace with something else
                this.candidateAlias = "SUB"; // TODO Parameterise this as a config option
            }
        }

        // Check the candidate class existence
        String candidateClassName = query.getCandidateClassName();
        if (candidateClass == null && candidateClassName != null)
        {
            try
            {
                candidateClass = om.getClassLoaderResolver().classForName(candidateClassName, true);
            }
            catch (JPOXException jpe)
            {
                candidateClass = query.resolveClassDeclaration(candidateClassName);
            }
        }

        // Set the "candidates"
        Extent candidateExtent = ((AbstractJavaQuery)query).getCandidateExtent();
        Collection candidateCollection = ((AbstractJavaQuery)query).getCandidateCollection();
        if (candidateExtent != null)
        {
            candidates = (Queryable)candidateExtent;
        }
        else if (candidateCollection != null)
        {
            candidates = new CollectionCandidates(om, candidateClass, candidateCollection);
        }
        else
        {
            if (candidateClass == null)
            {
                throw new JPOXUserException(LOCALISER.msg("042001"));
            }
            candidates = (Queryable)om.getExtent(candidateClass, query.isSubclasses());
        }

        String result = query.getResult();
        if (result != null)
        {
            // User has specified a result so set the candidates based on the result definition
            if (candidateCollection != null)
            {
                candidates = new ResultExpressionsQueryable(om, candidateClass,
                    ((CollectionCandidates)candidates).getUserCandidates(), query.isSubclasses());
            }
            else
            {
                candidates = new ResultExpressionsQueryable(om, candidateClass, query.isSubclasses());
            }
        }
    }

    /**
     * Convenience method to process the subquery "<candidate-expression>" to return the
     * class to use.
     * @return Class for candidate expression
     */
    protected Class getClassForSubqueryCandidateExpression()
    {
        if (subqueryCandidateExpr == null)
        {
            return null;
        }

        String[] tokens = StringUtils.split(subqueryCandidateExpr, ".");
        Class cls = parentExpr.getCandidateClass();
        ClassLoaderResolver clr = query.getObjectManager().getClassLoaderResolver();
        MetaDataManager mmgr = query.getObjectManager().getMetaDataManager();
        AbstractClassMetaData cmd = mmgr.getMetaDataForClass(cls, clr);
        for (int i=1;i<tokens.length;i++)
        {
            AbstractMemberMetaData mmd = cmd.getMetaDataForMember(tokens[i]);
            int relationType = mmd.getRelationType(clr);

            if (relationType == Relation.ONE_TO_ONE_BI ||
                relationType == Relation.ONE_TO_ONE_UNI ||
                relationType == Relation.MANY_TO_ONE_BI)
            {
                cls = mmd.getType();
            }
            else if (relationType == Relation.ONE_TO_MANY_UNI ||
                relationType == Relation.ONE_TO_MANY_BI ||
                relationType == Relation.MANY_TO_MANY_BI)
            {
                if (mmd.hasCollection())
                {
                    cls = clr.classForName(mmd.getCollection().getElementType());
                }
                else if (mmd.hasMap())
                {
                    // Assume we're using the value
                    cls = clr.classForName(mmd.getMap().getValueType());
                }
                else if (mmd.hasArray())
                {
                    cls = clr.classForName(mmd.getArray().getElementType());
                }
            }

            if (i < tokens.length-1)
            {
                cmd = mmgr.getMetaDataForClass(cls, clr);
            }
        }
        return cls;
    }

    /**
     * Convenience method to compile the ordering defintion.
     * Processes any ordering definition and updates the QueryExpression accordingly.
     * @param qs The Query Expression to apply the ordering to (if specified)
     * @param ordering The ordering specification
     */
    private void compileOrdering(QueryExpression qs, String ordering)
    {
        if (ordering != null && ordering.length() > 0)
        {
            // Compile the ordering
            StringTokenizer t1 = new StringTokenizer(ordering, ",");

            int n = t1.countTokens();
            ScalarExpression[] orderExprs = new ScalarExpression[n];
            boolean[] descending = new boolean[n];
            for (n = 0; t1.hasMoreTokens(); ++n)
            {
                String orderExpression = t1.nextToken().trim();

                // Allow all sensible forms of direction specification (not just JDOQL standard)
                if (orderExpression.endsWith("ascending") || orderExpression.endsWith("ASCENDING"))
                {
                    descending[n] = false;
                    orderExpression = orderExpression.substring(0, orderExpression.length()-"ascending".length());
                }
                else if (orderExpression.endsWith("asc") || orderExpression.endsWith("ASC"))
                {
                    descending[n] = false;
                    orderExpression = orderExpression.substring(0, orderExpression.length()-"asc".length());
                }
                else if (orderExpression.endsWith("descending") || orderExpression.endsWith("DESCENDING"))
                {
                    descending[n] = true;
                    orderExpression = orderExpression.substring(0, orderExpression.length()-"descending".length());
                }
                else if (orderExpression.endsWith("desc") || orderExpression.endsWith("DESC"))
                {
                    descending[n] = true;
                    orderExpression = orderExpression.substring(0, orderExpression.length()-"desc".length());
                }
                else
                {
                    throw new JPOXUserException(LOCALISER.msg("042004",ordering));
                }

                orderExprs[n] = compileExpressionFromString(orderExpression);
            }

            // Update the query statement
            if (qs != null)
            {
                qs.setOrdering(orderExprs, descending);
            }
        }
    }

    /**
     * Convenience method to parse an expression string into its query expression.
     * @param str The string
     * @return The query expression for the passed string
     */
    protected ScalarExpression compileExpressionFromString(String str)
    {
        try
        {
            p = new Parser(str, imports);
            ScalarExpression expr = compileExpression();
            if (!p.parseEOS())
            {
                throw new QueryCompilerSyntaxException(LOCALISER.msg("021054", language), p.getIndex(), p.getInput());
            }
            return expr;
        }
        finally
        {
            p = null;
        }
    }

    /**
     * Principal method for compiling an expression.
     * An expression could be the filter, the range, the result, etc.
     * @return The compiled expression
     */
    protected ScalarExpression compileExpression()
    {
        return compileConditionalOrExpression();
        /*
         * OR ("||")
         * AND ("&&")
         * Bitwise OR ("|")
         * Bitwise XOR ("^")
         * Bitwise AND ("&")
         * Equality ("==") ("!=")
         * Relational (">=") (">") ("<=") ("<")
         * Additive ("+") ("-")
         * Multiplicative ("*") ("/") ("%")
         * Unary ("+") ("-")
         * Unary ("~") ("!")
         * Cast
         */
    }

    /**
     * This method deals with the OR condition
     * A condition specifies a combination of one or more expressions and logical (Boolean) operators and returns a value of TRUE, FALSE, or unknown
     */
    private ScalarExpression compileConditionalOrExpression()
    {
        ScalarExpression expr = compileConditionalAndExpression();

        while (p.parseString("||"))
        {
            expr = expr.ior(compileConditionalAndExpression());
        }

        return expr;
    }

    /**
     * This method deals with the AND condition, taking one BooleanExpression and applying
     * another BooleanExpression via AND.
     * A condition specifies a combination of one or more expressions and
     * logical (Boolean) operators and returns a value of TRUE, FALSE, or unknown
     */
    private ScalarExpression compileConditionalAndExpression()
    {
        ScalarExpression expr = compileInclusiveOrExpression();
        while (p.parseString("&&"))
        {
            ScalarExpression otherExpr = compileInclusiveOrExpression();
            expr = expr.and(otherExpr);
        }

        return expr;
    }

    private ScalarExpression compileInclusiveOrExpression()
    {
        ScalarExpression expr = compileExclusiveOrExpression();

        while (p.parseChar('|', '|'))
        {
            expr = expr.ior(compileExclusiveOrExpression());
        }

        return expr;
    }

    private ScalarExpression compileExclusiveOrExpression()
    {
        ScalarExpression expr = compileAndExpression();

        while (p.parseChar('^'))
        {
            expr = expr.eor(compileExclusiveOrExpression());
        }

        return expr;
    }

    private ScalarExpression compileAndExpression()
    {
        ScalarExpression expr = compileEqualityExpression();

        while (p.parseChar('&', '&'))
        {
            expr = expr.and(compileEqualityExpression());
        }

        return expr;
    }

    private ScalarExpression compileEqualityExpression()
    {
        ScalarExpression expr = compileRelationalExpression();

        for (;;)
        {
            if (p.parseString("=="))
            {
                expr = expr.eq(compileRelationalExpression());
            }
            else if (p.parseString("!="))
            {
                expr = expr.noteq(compileRelationalExpression());
            }
            else if (p.parseString("="))
            {
                // Assignment operator is invalid (user probably meant to specify "==")
                throw new JPOXUserException(LOCALISER.msg("042008", p.getInput()));
            }
            else
            {
                break;
            }
        }

        return expr;
    }

    private ScalarExpression compileRelationalExpression()
    {
        ScalarExpression expr = compileAdditiveExpression();

        for (;;)
        {
            if (p.parseString("<="))
            {
                expr = expr.lteq(compileAdditiveExpression());
            }
            else if (p.parseString(">="))
            {
                expr = expr.gteq(compileAdditiveExpression());
            }
            else if (p.parseChar('<'))
            {
                expr = expr.lt(compileAdditiveExpression());
            }
            else if (p.parseChar('>'))
            {
                expr = expr.gt(compileAdditiveExpression());
            }
            else if (p.parseString("instanceof"))
            {
                expr = expr.instanceOf(compileAdditiveExpression());
            }
            else if (p.parseString("AS") || p.parseString("as"))
            {
                String asName = p.parseName();
                expr = expr.as(asName);
            }
            else
            {
                break;
            }
        }

        return expr;
    }

    /**
     * this compiles a primary. First look for a literal (e.g. "text"), then
     * an identifier(e.g. variable) In the next step, call a function, if
     * executing a function, on the literal or the identifier found.
     *
     * @return Scalar Expression for the primary compiled expression
     */
    protected ScalarExpression compilePrimary()
    {
        // try to find a literal
        ScalarExpression expr = compileLiteral();

        // if expr == null, literal not found...
        if (expr == null)
        {
            if (p.parseChar('('))
            {
                //try to find an expression
                expr = compileExpression();
                if (!p.parseChar(')'))
                {
                    throw new QueryCompilerSyntaxException("')' expected", p.getIndex(), p.getInput());
                }
                expr.encloseWithInParentheses();
            }
            else if (p.parseChar('{'))
            {
                // try to find an array
                List exprs = new ArrayList();
                while (!p.parseChar('}'))
                {
                    exprs.add(compileExpression());
                    if(p.parseChar('}'))
                    {
                        break;
                    }
                    else if (!p.parseChar(','))
                    {
                        throw new QueryCompilerSyntaxException("',' or '}' expected", p.getIndex(), p.getInput());
                    }
                }
                expr = new ArrayExpression(qs,(ScalarExpression[])exprs.toArray(new ScalarExpression[exprs.size()]));
            }
            else
            {
                String methodId = p.parseMethod();
                if (methodId == null)
                {
                    // We will have an identifier (variable, parameter, or field of candidate class)
                    expr = compileIdentifier();
                }
                else
                {
                    // we are running arbitrary methods. the namespace here is "<candidateAlias>", and
                    // since "<candidateAlias>" has no methods we assume we are dealing with aggregate methods
                    if (p.parseChar('('))
                    {
                        // Aggregate function expression
                        List args = new ArrayList();
                        boolean isDistinct = false;
                        if (!p.parseChar(')'))
                        {
                            // Check for use of DISTINCT/distinct
                            isDistinct = p.parseString("DISTINCT");
                            if (!isDistinct)
                            {
                                isDistinct = p.parseString("distinct");
                            }

                            do
                            {
                                ScalarExpression argExpr = compileExpression();
                                args.add(argExpr);
                                fieldExpressions.remove(argExpr); // Remove from field expressions list since we will include aggregates
                            } while (p.parseChar(','));

                            if (!p.parseChar(')'))
                            {
                                throw new QueryCompilerSyntaxException("')' expected", p.getIndex(), p.getInput());
                            }
                        }
                        expr = new AggregateExpression(qs);
                        if (isDistinct)
                        {
                            ((AggregateExpression)expr).setDistinct();
                        }

                        // Allow for aggregate functions specified in uppercase [JDO2 spec 14.4]
                        expr = expr.callMethod(methodId.toLowerCase(), args);
                        fieldExpressions.add(expr); // Remove from field expressions list since we dont include aggregates
                    }
                }
            }
        }

        /*
         * run function on literals or identifiers
         * e.g. "primary.runMethod(arg)"
         */
        while (p.parseChar('.'))
        {
            String id = p.parseIdentifier();
            if (id == null)
            {
                throw new QueryCompilerSyntaxException("Identifier expected", p.getIndex(), p.getInput());
            }
            if (p.parseChar('('))
            {
                // Found syntax for a method, so invoke the method
                ArrayList args = new ArrayList();

                if (!p.parseChar(')'))
                {
                    do
                    {
                        args.add(compileExpression());
                    } while (p.parseChar(','));

                    if (!p.parseChar(')'))
                    {
                        throw new QueryCompilerSyntaxException("')' expected", p.getIndex(), p.getInput());
                    }
                }

                expr = expr.callMethod(id, args);
            }
            else
            {
                // access the field from an identifier
                expr = expr.accessField(id, false);
            }
        }
        return expr;
    }

    /**
     * An identifier always designates a reference to a single value.
     * A single value can be one collection, one field.
     * @return The compiled identifier
     */
    private ScalarExpression compileIdentifier()
    {
        ScalarExpression expr;

        String id = p.parseIdentifier();
        if (id == null)
        {
            throw new QueryCompilerSyntaxException("Identifier expected", p.getIndex(), p.getInput());
        }
        if (JDOQLQueryHelper.isKeyword(id))
        {
            // Identifier is not allowed to be a JDOQL keyword
            throw new QueryCompilerSyntaxException(LOCALISER.msg("042009", id), p.getIndex(), p.getInput());
        }

        MappedStoreManager srm = (MappedStoreManager)query.getObjectManager().getStoreManager();
        ClassLoaderResolver clr = query.getObjectManager().getClassLoaderResolver();
        if (id.startsWith(":"))
        {
            // Implicit Parameter ":param1"
            return compileNamedImplicitParameter(id);
        }
        else if (id.equals("new"))
        {
            // Found syntax for "new MyObject(param1, param2)" - result expression
            expr = compileNewObject();
        }
        else if (parameterTypesByName.containsKey(id))
        {
            // Explicit parameter
            Class parameterClass = (Class)parameterTypesByName.get(id);
            boolean serialised = false;
            if (parameterClass == java.lang.Object.class)
            {
                serialised = true; // Don't yet support Object as non-serialised. TODO Remove this when available
            }

            JavaTypeMapping m = srm.getDatastoreAdapter().getMapping(parameterClass, srm, clr, serialised, false);
            if (!executionCompile)
            {
                expr = m.newLiteral(qs, m.getSampleValue(clr));
            }
            else if (parameters == null || !parameters.containsKey(id))
            {
                throw new JPOXUserException(LOCALISER.msg("021059", language, id));
            }
            else
            {
                Object parameterValue = parameters.get(id);
                if (parameterValue == null)
                {
                    expr = new NullLiteral(qs);
                }
                else
                {
                    expr = m.newLiteral(qs, parameterValue);
                    expr.setParameterName(id);
                }
            }
        }
        else if (query.hasSubqueryForVariable(id))
        {
            // Variable represented as a subquery (process before explicit variables below)
            return compileSubqueryVariable(id);
        }
        else if (variableNames.contains(id))
        {
            // Explicit variable
            return compileExplicitVariable(id);
        }
        else if (parentExpr != null && id.equals("this"))
        {
            if (subqueryAliasSet)
            {
                // Use of "this" in subquery but where the subquery has a different alias, so refers to parent query
                return parentExpr.getMainTableExpression().newFieldExpression(parentExpr.getCandidateAlias());
            }
            else
            {
                // Use of "this" in subquery with no subquery alias set, so refers to subquery candidate
                return qs.getMainTableExpression().newFieldExpression(this.candidateAlias);
            }
        }
        else
        {
            try
            {
                // Check if the identifier is a field of the candidate class
                expr = qs.getMainTableExpression().newFieldExpression(id);
                if (!id.equals(candidateAlias))
                {
                    // Make a check on whether the field exists in the candidate class (TableExpression only checks the same table).
                    if (candidateCmd == null)
                    {
                        candidateCmd = query.getObjectManager().getMetaDataManager().getMetaDataForClass(
                            candidateClass, clr);
                    }
                    if (candidateCmd.getMetaDataForMember(id) == null)
                    {
                        // The field doesnt exist in the candidate class, so no point proceeding since prohibited by the JDO spec
                        throw new JPOXUserException(LOCALISER.msg("021049", id));
                    }
                }

                fieldExpressions.add(expr); // Add to the field expressions list
            }
            catch (NoSuchPersistentFieldException nspfe)
            {
                // Not a field so retrieve the full name and it is either a class, or an implicit variable
                String name = id;
                if (p.nextIsDot())
                {
                    p.parseChar('.');
                    name += ".";
                    name += p.parseName();
                }

                Class cls = null;
                try
                {
                    cls = query.resolveClassDeclaration(name);
                    expr = new ClassExpression(qs, cls);
                }
                catch (JPOXUserException jpue)
                {
                    if (name.indexOf('.') > 0)
                    {
                        // Try without the last part of the name (in case its a class name plus field or method)
                        String partialName = name.substring(0, name.lastIndexOf('.'));
                        String finalNamePart = name.substring(name.lastIndexOf('.') + 1);
                        try
                        {
                            // try method invocation
                            expr = callUserDefinedScalarExpression(name);
                            if (expr == null)
                            {
                                // try with this class using field access
                                cls = query.resolveClassDeclaration(partialName);
                                expr = new ClassExpression(qs, cls);
                                expr = expr.accessField(finalNamePart, true);
                            }
                        }
                        catch (JPOXUserException jpue2)
                        {
                            throw new JPOXUserException(LOCALISER.msg("021066", partialName), jpue2);
                        }
                    }
                    else
                    {
                        try
                        {
                            // try with candidate class using field access
                            expr = new ClassExpression(qs, candidateClass);
                            expr = expr.accessField(name, true);
                        }
                        catch (JPOXUserException jpue2)
                        {
                            // Implicit variable
                            if (explicitVariables)
                            {
                                throw new JPOXUserException(LOCALISER.msg("021067", query.getExplicitVariables(), name));
                            }
                            expr = (ScalarExpression)expressionsByVariableName.get(name);
                            if (expr == null)
                            {
                                // Type will be null here since we have no types specified for implicit variables
                                // The type will be set later when we know its context
                                expr = new UnboundVariable(qs, name,
                                    (Class)variableTypesByName.get(name), this);
                                variableNames.add(name);
                                // We should really add this to variableTypesByName but we don't know the type here
                                fieldExpressions.add(expr); // Add to the field expressions list
                            }
                        }
                    }
                }
            }
        }

        return expr;
    }

    /**
     * Method to compile a subquery, replacing the specified variable with a SubqueryExpression.
     * @param id Variable name that the subquery replaces.
     * @return Expression for the subquery
     */
    protected ScalarExpression compileSubqueryVariable(String id)
    {
        SubqueryDefinition subqueryDef = query.getSubqueryForVariable(id);

        // Compile the subquery to get our statement
        JDOQLQueryCompiler subCompiler = new JDOQLQueryCompiler((AbstractJDOQLQuery)subqueryDef.getQuery(),
            imports, subqueryDef.getParameterMap());
        subCompiler.processAsSubquery(qs, subqueryDef.getCandidateExpression(), subqueryDef.getParameterMap());
        QueryExpression subqueryExpr = (QueryExpression)subCompiler.compile(QueryCompiler.COMPILE_EXECUTION);

        // Make sure the result clause is added - this should be refactored into the Compiler from newROF()
        subCompiler.getCandidates().newResultObjectFactory(subqueryExpr, false, subCompiler.getResultClass(), true);

        ScalarExpression expr = new SubqueryExpression(qs, subqueryExpr);

        // Mark the variable as bound
        expressionsByVariableName.put(id, expr);

        return expr;
    }

    /**
     * Method to compile a named implicit parameter into an expression.
     * @param id Identifier of the named parameter, starts with ":"
     * @return Expression representing the named param
     */
    protected ScalarExpression compileNamedImplicitParameter(String id)
    {
        // Implicit Parameter
        id = id.substring(1); // Omit the ":"
        if (explicitParameters)
        {
            // JDO2 14.6.3 Parameters should be either explicit, or implicit, but not both.
            // The user has declared at least 1 parameter yet this new parameter is implicit, so throw an exception
            throw new JPOXUserException(LOCALISER.msg("021055", parameters, id));
        }

        MappedStoreManager srm = (MappedStoreManager)query.getObjectManager().getStoreManager();
        ClassLoaderResolver clr = query.getObjectManager().getClassLoaderResolver();
        if (parameters != null && parameters.size() > 0)
        {
            if (parameters.containsKey(id))
            {
                // Implicit parameter already found, so reuse the value
                Object paramValue = parameters.get(id);
                if (paramValue != null)
                {
                    if (parentExpr != null && paramValue instanceof String && ((String)paramValue).startsWith("this"))
                    {
                        // Subquery with parameter pointing back to a field of the parent query
                        return getExpressionForSubqueryParentParameter((String)paramValue);
                    }
                    else
                    {
                        JavaTypeMapping m = srm.getDatastoreAdapter().getMapping(paramValue.getClass(), srm, clr);
                        ScalarExpression paramExpr = m.newLiteral(qs, paramValue);
                        paramExpr.setParameterName(id);
                        return paramExpr;
                    }
                }
                else
                {
                    return new NullLiteral(qs);
                }
            }

            if (parameters.size() < implicitParamNo+1)
            {
                // Already used up all parameters available
                throw new JPOXUserException(LOCALISER.msg("021056", "" + id, "" + implicitParamNo));
            }

            // Find the next implicit parameter available and assign to this implicit parameter
            if (!parameters.containsKey("JPOX_" + implicitParamNo))
            {
                throw new JPOXUserException(LOCALISER.msg("021056", "" + id, "" + implicitParamNo));
            }
            Object paramValue = parameters.get("JPOX_" + implicitParamNo);

            // Replace the value key with the true name in case its used again
            parameters.put(id, paramValue);
            parameters.remove("JPOX_" + implicitParamNo);
            implicitParamNo++;
            if (paramValue != null)
            {
                if (parentExpr != null && paramValue instanceof String && ((String)paramValue).startsWith("this"))
                {
                    // Subquery with parameter pointing back to a field of the parent query
                    return getExpressionForSubqueryParentParameter((String)paramValue);
                }
                else
                {
                    JavaTypeMapping m = srm.getDatastoreAdapter().getMapping(paramValue.getClass(), srm, clr);
                    ScalarExpression paramExpr = m.newLiteral(qs, paramValue);
                    paramExpr.setParameterName(id);
                    return paramExpr;
                }
            }
            else
            {
                return new NullLiteral(qs);
            }
        }
        else
        {
            if (executionCompile)
            {
                throw new JPOXUserException(LOCALISER.msg("021056", "" + id, "" + implicitParamNo));
            }
            else
            {
                // We have no value available at precompile so just give it a null for now
                return new NullLiteral(qs);
            }
        }
    }

    /**
     * Convenience method to obtain an expression for the value of a parameter in a subquery where
     * it relates back to the parent query.
     * @param val The value
     * @return The expression for this field in the parent query
     */
    protected ScalarExpression getExpressionForSubqueryParentParameter(String val)
    {
        LogicSetExpression tblExpr = parentExpr.getMainTableExpression();
        if (val.equals("this"))
        {
            // Just return an expression for the parent candidate table ID mapping
            return tblExpr.newFieldExpression("this");
        }

        String[] tokens = StringUtils.split(val, ".");
        for (int i=1;i<tokens.length;i++)
        {
            if (i == tokens.length-1)
            {
                return tblExpr.newFieldExpression(tokens[i]);
            }
            else
            {
                // TODO Cater for more than 1 link adding joins as necessary
                throw new JPOXException(
                    "JPOX doesnt currently support subquery parameter values that arent a direct field " +
                    "of the outer query candidate (" + val + ")");
            }
        }
        return null;
    }
}
TOP

Related Classes of org.jpox.store.rdbms.query.JDOQLQueryCompiler

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.