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