/**********************************************************************
Copyright (c) 2011 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.mongodb.query;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.AbstractMemberMetaData;
import org.datanucleus.metadata.Relation;
import org.datanucleus.query.compiler.CompilationComponent;
import org.datanucleus.query.compiler.QueryCompilation;
import org.datanucleus.query.evaluator.AbstractExpressionEvaluator;
import org.datanucleus.query.expression.Expression;
import org.datanucleus.query.expression.Literal;
import org.datanucleus.query.expression.PrimaryExpression;
import org.datanucleus.store.ExecutionContext;
import org.datanucleus.store.mongodb.MongoDBUtils;
import org.datanucleus.store.mongodb.query.expression.MongoBooleanExpression;
import org.datanucleus.store.mongodb.query.expression.MongoExpression;
import org.datanucleus.store.mongodb.query.expression.MongoFieldExpression;
import org.datanucleus.store.mongodb.query.expression.MongoLiteral;
import org.datanucleus.store.mongodb.query.expression.MongoOperator;
import org.datanucleus.store.query.Query;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;
import com.mongodb.BasicDBObject;
/**
* Class which maps a compiled (generic) query to an MongoDB query.
*/
public class QueryToMongoDBMapper extends AbstractExpressionEvaluator
{
final ExecutionContext ec;
final String candidateAlias;
final AbstractClassMetaData candidateCmd;
final Query query;
final QueryCompilation compilation;
/** Input parameter values, keyed by the parameter name. Will be null if compiled pre-execution. */
final Map parameters;
/** State variable for the component being compiled. */
CompilationComponent compileComponent;
/** Whether the filter clause is completely evaluatable in the datastore. */
boolean filterComplete = true;
/** The filter object. If no filter is required then this will be an empty object. */
BasicDBObject filterObject;
/** Whether the result clause is completely evaluatable in the datastore. */
boolean resultComplete = true;
/** The result object. If no result is required then this will be null. */
BasicDBObject resultObject;
/** Stack of mongo expressions, used for compilation of the query into MongoDB objects. */
Stack<MongoExpression> stack = new Stack();
public QueryToMongoDBMapper(QueryCompilation compilation, Map parameters, AbstractClassMetaData cmd,
ExecutionContext ec, Query q)
{
this.ec = ec;
this.query = q;
this.compilation = compilation;
this.parameters = parameters;
this.candidateCmd = cmd;
this.candidateAlias = compilation.getCandidateAlias();
}
public boolean isFilterComplete()
{
return filterComplete;
}
public boolean isResultComplete()
{
return resultComplete;
}
public BasicDBObject getFilterObject()
{
return filterObject;
}
public BasicDBObject getResultObject()
{
return resultObject;
}
public void compile()
{
compileFrom();
compileFilter();
compileResult();
compileGrouping();
compileHaving();
compileOrdering();
}
/**
* Method to compile the FROM clause of the query
*/
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++)
{
// TODO Compile FROM class expression
}
}
}
/**
* Method to compile the WHERE clause of the query
*/
protected void compileFilter()
{
if (compilation.getExprFilter() != null)
{
compileComponent = CompilationComponent.FILTER;
try
{
compilation.getExprFilter().evaluate(this);
MongoExpression mongoExpr = stack.pop();
if (!(mongoExpr instanceof MongoBooleanExpression))
{
NucleusLogger.QUERY.error(">> invalid compilation : filter compiled to " + mongoExpr);
filterComplete = false;
}
else
{
MongoBooleanExpression mbExpr = (MongoBooleanExpression) mongoExpr;
filterObject = mbExpr.getDBObject();
}
}
catch (Exception e)
{
// Impossible to compile all to run in the datastore, so just exit
filterComplete = false;
}
compileComponent = null;
}
}
/**
* Method to compile the result clause of the query
*/
protected void compileResult()
{
if (compilation.getExprResult() != null)
{
compileComponent = CompilationComponent.RESULT;
resultObject = new BasicDBObject();
// Select any result expressions
Expression[] resultExprs = compilation.getExprResult();
for (int i=0;i<resultExprs.length;i++)
{
// TODO Compile this
}
}
// TODO Handle distinct
compileComponent = null;
}
/**
* Method to compile the grouping clause of the query
*/
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++)
{
// TODO Compile grouping
}
compileComponent = null;
}
}
/**
* Method to compile the having clause of the query
*/
protected void compileHaving()
{
if (compilation.getExprHaving() != null)
{
// Apply any having to the statement
compileComponent = CompilationComponent.HAVING;
/*Expression havingExpr = */compilation.getExprHaving();
// TODO Compile having
compileComponent = null;
}
}
/**
* Method to compile the ordering clause of the query
*/
protected void compileOrdering()
{
if (compilation.getExprOrdering() != null)
{
compileComponent = CompilationComponent.ORDERING;
Expression[] orderingExpr = compilation.getExprOrdering();
for (int i=0;i<orderingExpr.length;i++)
{
// TODO Compile ordering
}
compileComponent = null;
}
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processAndExpression(org.datanucleus.query.expression.Expression)
*/
@Override
protected Object processAndExpression(Expression expr)
{
MongoBooleanExpression right = (MongoBooleanExpression) stack.pop();
MongoBooleanExpression left = (MongoBooleanExpression) stack.pop();
MongoBooleanExpression andExpr = new MongoBooleanExpression(left, right, MongoOperator.OP_AND);
stack.push(andExpr);
return andExpr;
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processOrExpression(org.datanucleus.query.expression.Expression)
*/
@Override
protected Object processOrExpression(Expression expr)
{
MongoBooleanExpression right = (MongoBooleanExpression) stack.pop();
MongoBooleanExpression left = (MongoBooleanExpression) stack.pop();
MongoBooleanExpression andExpr = new MongoBooleanExpression(left, right, MongoOperator.OP_OR);
stack.push(andExpr);
return andExpr;
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processEqExpression(org.datanucleus.query.expression.Expression)
*/
@Override
protected Object processEqExpression(Expression expr)
{
Object right = stack.pop();
Object left = stack.pop();
if (left instanceof MongoLiteral && right instanceof MongoFieldExpression)
{
String field = ((MongoFieldExpression)right).getFieldName();
Object value = ((MongoLiteral)left).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_EQ);
stack.push(mongoExpr);
return mongoExpr;
}
else if (left instanceof MongoFieldExpression && right instanceof MongoLiteral)
{
String field = ((MongoFieldExpression)left).getFieldName();
Object value = ((MongoLiteral)right).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_EQ);
stack.push(mongoExpr);
return mongoExpr;
}
// TODO Auto-generated method stub
return super.processEqExpression(expr);
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processNoteqExpression(org.datanucleus.query.expression.Expression)
*/
@Override
protected Object processNoteqExpression(Expression expr)
{
Object right = stack.pop();
Object left = stack.pop();
if (left instanceof MongoLiteral && right instanceof MongoFieldExpression)
{
String field = ((MongoFieldExpression)right).getFieldName();
Object value = ((MongoLiteral)left).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_NOTEQ);
stack.push(mongoExpr);
return mongoExpr;
}
else if (left instanceof MongoFieldExpression && right instanceof MongoLiteral)
{
String field = ((MongoFieldExpression)left).getFieldName();
Object value = ((MongoLiteral)right).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_NOTEQ);
stack.push(mongoExpr);
return mongoExpr;
}
// TODO Auto-generated method stub
return super.processNoteqExpression(expr);
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processGtExpression(org.datanucleus.query.expression.Expression)
*/
@Override
protected Object processGtExpression(Expression expr)
{
Object right = stack.pop();
Object left = stack.pop();
if (left instanceof MongoLiteral && right instanceof MongoFieldExpression)
{
String field = ((MongoFieldExpression)right).getFieldName();
Object value = ((MongoLiteral)left).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_LTEQ);
stack.push(mongoExpr);
return mongoExpr;
}
else if (left instanceof MongoFieldExpression && right instanceof MongoLiteral)
{
String field = ((MongoFieldExpression)left).getFieldName();
Object value = ((MongoLiteral)right).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_GT);
stack.push(mongoExpr);
return mongoExpr;
}
// TODO Auto-generated method stub
return super.processGtExpression(expr);
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processLtExpression(org.datanucleus.query.expression.Expression)
*/
@Override
protected Object processLtExpression(Expression expr)
{
Object right = stack.pop();
Object left = stack.pop();
if (left instanceof MongoLiteral && right instanceof MongoFieldExpression)
{
String field = ((MongoFieldExpression)right).getFieldName();
Object value = ((MongoLiteral)left).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_GTEQ);
stack.push(mongoExpr);
return mongoExpr;
}
else if (left instanceof MongoFieldExpression && right instanceof MongoLiteral)
{
String field = ((MongoFieldExpression)left).getFieldName();
Object value = ((MongoLiteral)right).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_LT);
stack.push(mongoExpr);
return mongoExpr;
}
// TODO Auto-generated method stub
return super.processLtExpression(expr);
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processGteqExpression(org.datanucleus.query.expression.Expression)
*/
@Override
protected Object processGteqExpression(Expression expr)
{
Object right = stack.pop();
Object left = stack.pop();
if (left instanceof MongoLiteral && right instanceof MongoFieldExpression)
{
String field = ((MongoFieldExpression)right).getFieldName();
Object value = ((MongoLiteral)left).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_LT);
stack.push(mongoExpr);
return mongoExpr;
}
else if (left instanceof MongoFieldExpression && right instanceof MongoLiteral)
{
String field = ((MongoFieldExpression)left).getFieldName();
Object value = ((MongoLiteral)right).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_GTEQ);
stack.push(mongoExpr);
return mongoExpr;
}
// TODO Auto-generated method stub
return super.processGteqExpression(expr);
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processLteqExpression(org.datanucleus.query.expression.Expression)
*/
@Override
protected Object processLteqExpression(Expression expr)
{
Object right = stack.pop();
Object left = stack.pop();
if (left instanceof MongoLiteral && right instanceof MongoFieldExpression)
{
String field = ((MongoFieldExpression)right).getFieldName();
Object value = ((MongoLiteral)left).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_GT);
stack.push(mongoExpr);
return mongoExpr;
}
else if (left instanceof MongoFieldExpression && right instanceof MongoLiteral)
{
String field = ((MongoFieldExpression)left).getFieldName();
Object value = ((MongoLiteral)right).getValue();
MongoExpression mongoExpr = new MongoBooleanExpression(field, value, MongoOperator.OP_LTEQ);
stack.push(mongoExpr);
return mongoExpr;
}
// TODO Auto-generated method stub
return super.processLteqExpression(expr);
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processPrimaryExpression(org.datanucleus.query.expression.PrimaryExpression)
*/
@Override
protected Object processPrimaryExpression(PrimaryExpression expr)
{
Expression left = expr.getLeft();
if (left == null)
{
List<String> tuples = expr.getTuples();
String fieldName = getFieldNameForPrimary(tuples);
if (fieldName == null)
{
if (compileComponent == CompilationComponent.FILTER)
{
filterComplete = false;
}
else if (compileComponent == CompilationComponent.RESULT)
{
resultComplete = false;
}
NucleusLogger.QUERY.debug(">> Primary " + expr +
" is not stored in this document, so unexecutable in datastore");
}
else
{
MongoFieldExpression fieldExpr = new MongoFieldExpression(fieldName);
stack.push(fieldExpr);
return fieldExpr;
}
}
// TODO Auto-generated method stub
return super.processPrimaryExpression(expr);
}
/* (non-Javadoc)
* @see org.datanucleus.query.evaluator.AbstractExpressionEvaluator#processLiteral(org.datanucleus.query.expression.Literal)
*/
@Override
protected Object processLiteral(Literal expr)
{
Object litValue = expr.getLiteral();
if (litValue instanceof Number)
{
MongoLiteral lit = new MongoLiteral(litValue);
stack.push(lit);
return lit;
}
else if (litValue instanceof String)
{
MongoLiteral lit = new MongoLiteral(litValue);
stack.push(lit);
return lit;
}
// TODO Handle all MongoDB supported (literal) types
return super.processLiteral(expr);
}
/**
* Convenience method to return the "field name" in candidate document for this primary.
* Allows for non-relation fields, and (nested) embedded PC fields - i.e all fields that are present
* in the document.
* @param tuples Tuples for the primary
* @return The document field name for this primary (or null if not resolvable in this document)
*/
protected String getFieldNameForPrimary(List<String> tuples)
{
if (tuples == null || tuples.size() == 0)
{
return null;
}
ClassLoaderResolver clr = ec.getClassLoaderResolver();
AbstractClassMetaData cmd = candidateCmd;
AbstractMemberMetaData embMmd = null;
boolean embeddedFlat = false;
String embeddedNestedField = null;
Iterator<String> iter = tuples.iterator();
while (iter.hasNext())
{
String name = iter.next();
if (name.equals(candidateAlias))
{
cmd = candidateCmd;
}
else
{
AbstractMemberMetaData mmd = cmd.getMetaDataForMember(name);
int relationType = mmd.getRelationType(clr);
if (relationType == Relation.NONE)
{
if (iter.hasNext())
{
throw new NucleusUserException("Query has reference to " +
StringUtils.collectionToString(tuples) + " yet " + name + " is a non-relation field!");
}
if (embMmd != null)
{
if (embeddedFlat)
{
return MongoDBUtils.getFieldName(embMmd, mmd.getAbsoluteFieldNumber());
}
else
{
return embeddedNestedField + "." + MongoDBUtils.getFieldName(mmd);
}
}
return MongoDBUtils.getFieldName(mmd);
}
else if (mmd.isEmbedded())
{
if (Relation.isRelationSingleValued(relationType))
{
boolean nested = true;
String nestedStr = mmd.getValueForExtension("nested");
if (nestedStr != null && nestedStr.equalsIgnoreCase("false"))
{
nested = false;
}
cmd = ec.getMetaDataManager().getMetaDataForClass(mmd.getType(), clr);
embMmd = mmd;
if (nested)
{
if (embeddedNestedField == null)
{
embeddedNestedField = MongoDBUtils.getFieldName(mmd);
}
else
{
embeddedNestedField += ("." + MongoDBUtils.getFieldName(mmd));
}
}
else
{
// Embedded PC, with fields flat in the document of the owner
if (!embeddedFlat)
{
embeddedFlat = true;
}
}
}
else if (Relation.isRelationMultiValued(relationType))
{
throw new NucleusUserException("Dont currently support querying of embedded collection fields at " + mmd.getFullFieldName());
}
}
else
{
if (compileComponent == CompilationComponent.FILTER)
{
filterComplete = false;
}
else if (compileComponent == CompilationComponent.RESULT)
{
resultComplete = false;
}
NucleusLogger.QUERY.debug("Query has reference to " +
StringUtils.collectionToString(tuples) + " and " + mmd.getFullFieldName() +
" is not persisted into this document, so unexecutable in the datastore");
return null;
}
}
}
return null;
}
}