/*
* Copyright 2005 Nissim Karpenstein, Stein M. Hugubakken
*
* 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.
*/
package org.exolab.castor.jdo.oql;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import org.exolab.castor.jdo.DbMetaInfo;
import org.exolab.castor.jdo.QueryException;
import org.exolab.castor.jdo.engine.SQLEngine;
import org.exolab.castor.jdo.engine.SQLHelper;
import org.exolab.castor.jdo.engine.nature.ClassDescriptorJDONature;
import org.exolab.castor.jdo.engine.nature.FieldDescriptorJDONature;
import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.FieldDescriptor;
import org.exolab.castor.mapping.loader.Types;
import org.exolab.castor.persist.LockEngine;
import org.exolab.castor.persist.spi.QueryExpression;
/**
* A class which walks the parse tree created by the parser to check for errors
* and translate to SQL.
*
* @author <a href="mailto:nissim@nksystems.com">Nissim Karpenstein</a>
* @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
*/
public class ParseTreeWalker {
private LockEngine _dbEngine;
private ParseTreeNode _parseTree;
private String _projectionName;
private String _projectionAlias;
private int _projectionType;
private String _fromClassName;
private String _fromClassAlias;
private ClassLoader _classLoader;
private Class<?> _objClass;
private QueryExpression _queryExpr;
private DbMetaInfo _dbInfo;
private Hashtable<Integer, ParamInfo> _paramInfo;
private HashMap<ParseTreeNode, FieldDescriptor> _fieldInfo;
private HashMap<ParseTreeNode, Vector<String>> _pathInfo;
private HashMap<ArrayList<String>, Integer> _allPaths;
private SQLEngine _engine;
private ClassDescriptor _clsDesc;
//projection types
public static final int AGGREGATE = 1;
public static final int FUNCTION = 2;
public static final int PARENT_OBJECT = 3;
public static final int DEPENDANT_OBJECT = 4;
public static final int DEPENDANT_OBJECT_VALUE = 5;
public static final int DEPENDANT_VALUE = 6;
public static final int MAX_TABLE_LENGTH = 30;
/**
* Creates a new parse tree walker. Which checks the tree for errors, and
* generates a QueryExpression containing the SQL translation.
*
* @param dbEngine The Persistence Engine
* @param parseTree The parse tree to walk
* @param classLoader A ClassLoader instance to load classes.
* @throws QueryException Thrown by checkErrors.
*/
public ParseTreeWalker(final LockEngine dbEngine, final ParseTreeNode parseTree,
final ClassLoader classLoader, final DbMetaInfo dbInfo)
throws QueryException {
_dbEngine = dbEngine;
_parseTree = parseTree;
_classLoader = classLoader;
_dbInfo = dbInfo;
if (!_parseTree.isRoot()) {
throw new QueryException("ParseTreeWalker must be created with the "
+ "root node of the parse tree.");
}
checkErrors();
createQueryExpression();
}
/**
* Accessor method for _objClass.
*
* @return The _objClass member.
*/
public Class<?> getObjClass() {
return _objClass;
}
/**
* Accessor method for _projectionType.
*
* @return The _projectionType member.
*/
public int getProjectionType() {
return _projectionType;
}
/**
* Accessor method for private _queryExpr member.
*
* @return private _queryExpr member
*/
public QueryExpression getQueryExpression() {
return _queryExpr;
}
/**
* Accessor method for _paramInfo.
*
* @return The _paramInfo member.
*/
public Hashtable<Integer, ParamInfo> getParamInfo() {
if (_paramInfo == null) { _paramInfo = new Hashtable<Integer, ParamInfo>(); }
return _paramInfo;
}
private HashMap<ParseTreeNode, FieldDescriptor> getFieldInfo() {
if (_fieldInfo == null) { _fieldInfo = new HashMap<ParseTreeNode, FieldDescriptor>(); }
return _fieldInfo;
}
private HashMap<ParseTreeNode, Vector<String>> getPathInfo() {
if (_pathInfo == null) { _pathInfo = new HashMap<ParseTreeNode, Vector<String>>(); }
return _pathInfo;
}
/**
* Accessor method for _clsDesc.
*
* @return The _clsDesc member.
*/
public ClassDescriptor getClassDescriptor() {
return _clsDesc;
}
/**
* Method to get path info for the selected object. This is the path which
* will be used by the QueryResults to follow the path if the object
* selected is a DEPENDANT_OBJECT or DEPENDANT_OBJECT_VALUE. Any other
* projectionTypes do not need this, so null will be returned.
*
* @return Path info for the selected element, null otherwise.
*/
public Vector<String> getProjectionInfo() {
switch (_projectionType) {
case DEPENDANT_OBJECT:
case DEPENDANT_OBJECT_VALUE:
ParseTreeNode projectionNode;
int type = _parseTree.getChild(0).getToken().getTokenType();
if (type == TokenType.KEYWORD_DISTINCT) {
projectionNode = _parseTree.getChild(1);
} else {
projectionNode = _parseTree.getChild(0);
}
if (projectionNode.getToken().getTokenType() == TokenType.KEYWORD_AS) {
projectionNode = projectionNode.getChild(0);
}
return getPathInfo().get(projectionNode);
default:
return null;
}
}
/**
* Traverses the tree checking for errors.
*
* @throws QueryException if there is an error.
*/
private void checkErrors() throws QueryException {
for (Iterator<ParseTreeNode> iter = _parseTree.children(); iter.hasNext(); ) {
ParseTreeNode curChild = iter.next();
if (curChild.getToken().getTokenType() == TokenType.KEYWORD_FROM) {
checkFromPart(curChild.getChild(0));
break;
}
}
int type = _parseTree.getChild(0).getToken().getTokenType();
if (type == TokenType.KEYWORD_DISTINCT) {
checkSelectPart(_parseTree.getChild(1));
} else {
checkSelectPart(_parseTree.getChild(0));
}
for (int curChild = 2; curChild <= _parseTree.getChildCount() - 1; curChild++) {
int tokenType = _parseTree.getChild(curChild).getToken().getTokenType();
switch (tokenType) {
case TokenType.KEYWORD_WHERE:
checkWhereClause(_parseTree.getChild(curChild));
break;
case TokenType.KEYWORD_ORDER:
checkOrderClause(_parseTree.getChild(curChild));
break;
case TokenType.KEYWORD_LIMIT:
checkLimitClause(_parseTree.getChild(curChild));
break;
case TokenType.KEYWORD_OFFSET:
checkOffsetClause(_parseTree.getChild(curChild));
break;
default:
break;
}
}
}
/**
* Checks the from parts of the query.
*
* @param fromPart is the ParseTreeNode containing the from part of the
* queryTree.
* @throws QueryException if there is an error.
*/
private void checkFromPart(final ParseTreeNode fromPart) throws QueryException {
//get the class name from the from clause
if (fromPart.getToken().getTokenType() == TokenType.KEYWORD_AS) {
ParseTreeNode classNameNode = fromPart.getChild(0);
if (classNameNode.getToken().getTokenType() == TokenType.DOT) {
StringBuffer sb = new StringBuffer();
for (Iterator<ParseTreeNode> iter = classNameNode.children(); iter.hasNext(); ) {
ParseTreeNode theChild = iter.next();
sb.append(theChild.getToken().getTokenValue()).append(".");
}
sb.setLength(sb.length() - 1);
_fromClassName = sb.toString();
} else {
_fromClassName = classNameNode.getToken().getTokenValue();
}
_fromClassAlias = fromPart.getChild(1).getToken().getTokenValue();
} else {
if (fromPart.getToken().getTokenType() == TokenType.DOT) {
StringBuffer sb = new StringBuffer();
for (Iterator<ParseTreeNode> iter = fromPart.children(); iter.hasNext(); ) {
ParseTreeNode theChild = iter.next();
sb.append(theChild.getToken().getTokenValue()).append(".");
}
_fromClassName = sb.deleteCharAt(sb.length() - 1).toString();
} else {
_fromClassName = fromPart.getToken().getTokenValue();
}
_fromClassAlias = _fromClassName;
}
try {
if (_classLoader == null) {
_objClass = Class.forName(_fromClassName);
} else {
_objClass = _classLoader.loadClass(_fromClassName);
}
} catch (ClassNotFoundException except) {
throw new QueryException("Could not find class " + _fromClassName, except);
}
_engine = (SQLEngine) _dbEngine.getPersistence(_objClass);
if (_engine == null) {
throw new QueryException(
"Could not find mapping for class " + _fromClassName);
}
_clsDesc = _engine.getDescriptor();
// This should never happen
if (_clsDesc == null) {
throw new QueryException(
"Could not get a descriptor for class " + _fromClassName);
}
}
/**
* Checks the select part of the query.
*
* @param selectPart is the ParseTreeNode containing the select part of the
* queryTree.
* @throws QueryException if there is an error.
*/
private void checkSelectPart(final ParseTreeNode selectPart) throws QueryException {
if (selectPart.getToken().getTokenType() == TokenType.KEYWORD_AS) {
checkProjection(selectPart.getChild(0), true, false);
_projectionAlias = selectPart.getChild(1).getToken().getTokenValue();
} else {
checkProjection(selectPart, true, false);
_projectionAlias = "";
}
}
/**
* Search for the field in the given class descriptor and descriptors of the
* super classes, return null if not found.
*
* @param fieldName The field name.
* @param clsDesc A JDO class descriptor.
* @return JDOFieldDescriptor for the specified field, null if not found.
*/
private FieldDescriptor getFieldDesc(final String fieldName,
final ClassDescriptor clsDesc) {
ClassDescriptor classDescriptor = clsDesc;
FieldDescriptor fieldDescriptor;
while (classDescriptor != null) {
fieldDescriptor = new ClassDescriptorJDONature(classDescriptor).getField(fieldName);
if (fieldDescriptor != null) {
return fieldDescriptor;
}
classDescriptor = classDescriptor.getExtends();
}
return null;
}
/**
* Search for the field in the given class descriptor and descriptors of the
* super classes. Returns new Object[2] {fieldDesc, classDesc}, where classDesc is a
* descriptor of the class where the field was found. If not found null will be
* returned. Also adds inner joins to the QueryExpression.
*
* @param path The path info vector to build the alias with
* @param tableIndex Field index in the path info
*/
private Object[] getFieldAndClassDesc(final String fieldName,
final ClassDescriptor clsDesc, final QueryExpression expr,
final Vector<String> path, final int tableIndex) {
FieldDescriptor field = null;
ClassDescriptor cd = clsDesc;
FieldDescriptor tempField = null;
ClassDescriptor tempCd = clsDesc;
Object[] retVal;
while (tempCd != null) {
tempField = new ClassDescriptorJDONature(tempCd).getField(fieldName);
if (tempField != null) {
field = tempField;
cd = tempCd;
}
tempCd = tempCd.getExtends();
}
if (field == null) { return null; }
// prepare the return value
retVal = new Object[] {field, cd};
if (cd != clsDesc) {
// now add inner join for "extends"
ClassDescriptorJDONature clsDescNature;
clsDescNature = new ClassDescriptorJDONature(clsDesc);
String tableAlias1 = clsDescNature.getTableName();
ClassDescriptorJDONature cdNature;
cdNature = new ClassDescriptorJDONature(cd);
String tableAlias2 = cdNature.getTableName();
if (tableIndex > 0) {
tableAlias1 = buildTableAlias(tableAlias1, path, tableIndex);
tableAlias2 = buildTableAlias(tableAlias2, path, tableIndex);
}
expr.addTable(new ClassDescriptorJDONature(cd).getTableName(), tableAlias2);
String[] clsDescIdNames = SQLHelper.getIdentitySQLNames(clsDesc);
String[] cdIdNames = SQLHelper.getIdentitySQLNames(cd);
expr.addInnerJoin(
clsDescNature.getTableName(), clsDescIdNames, tableAlias1,
cdNature.getTableName(), cdIdNames, tableAlias2);
}
return retVal;
}
/**
* Checks a projection (one of the items selected). Determines the
* _projectionType.
*
* @param projection The ParseTreeNode containing the projection.
* @param topLevel pass true when calling on the top level of projection
* in the parse tree. False when recursing
* @param onlySimple pass true if you want to throw errors if the
* object passed as the ParseTreeNode is not a simple type (use this
* when recursing on the arguments passed to Aggregate and SQL functions).
* @throws QueryException if there is an error.
* @return a JDOFieldDescriptor representation of the field, if it represents a field.
*/
private FieldDescriptor checkProjection(final ParseTreeNode projection,
final boolean topLevel,
final boolean onlySimple)
throws QueryException {
FieldDescriptor field = null;
if (projection.getChildCount() == 0) {
if (topLevel) {
_projectionType = PARENT_OBJECT;
_projectionName = projection.getToken().getTokenValue();
if (!_projectionName.equals(_fromClassAlias)) {
throw new QueryException("Object name not the same in SELECT and "
+ "FROM - select: " + _projectionName + ", from: "
+ _fromClassAlias);
}
} else {
if (onlySimple) {
throw new QueryException("Only primitive values are allowed to be "
+ "passed as parameters to Aggregate and SQL functions.");
}
return null;
}
} else {
int tokenType = projection.getToken().getTokenType();
switch (tokenType) {
case TokenType.IDENTIFIER:
//a SQL function call -- check the arguments
_projectionType = FUNCTION;
ParseTreeNode child = projection.getChild(0);
for (Iterator<ParseTreeNode> iter = child.children(); iter.hasNext(); ) {
checkProjection(iter.next(), false, true);
}
break;
case TokenType.DOT:
//a path expression -- check if it is valid, and create a paramInfo
Iterator<ParseTreeNode> iter = projection.children();
ParseTreeNode curNode = null;
String curName = null;
StringBuffer projectionName = new StringBuffer();
Vector<String> projectionInfo = new Vector<String>();
//check that the first word before the dot is our class
if (iter.hasNext()) {
curNode = iter.next();
curName = curNode.getToken().getTokenValue();
if ((!curName.equals(_projectionName))
&& (!curName.equals(_projectionAlias))
&& (!curName.equals(_fromClassName))
&& (!curName.equals(_fromClassAlias))) {
// reset the enumeration
iter = projection.children();
curName = _fromClassAlias;
}
projectionName.append(curName);
projectionInfo.addElement(curName);
}
//use the ClassDescriptor to check that the rest of the path is valid.
ClassDescriptor curClassDesc = _clsDesc;
FieldDescriptor curField = null;
int count = 0;
String curToken;
while (iter.hasNext()) {
// there may be nested attribute name
curField = null;
curName = null;
while ((curField == null) && iter.hasNext()) {
curNode = iter.next();
curToken = curNode.getToken().getTokenValue();
if (curName == null) {
curName = curToken;
} else {
curName = curName + "." + curToken;
}
curField = getFieldDesc(curName, curClassDesc);
}
if (curField == null) {
throw new QueryException("An unknown field was requested: "
+ curName + " (" + curClassDesc + ")");
}
projectionName.append(".").append(curName);
projectionInfo.addElement(curName);
curClassDesc = curField.getClassDescriptor();
if ((curClassDesc == null) && iter.hasNext()) {
throw new QueryException("An non-reference field was requested: "
+ curName + " (" + curClassDesc + ")");
}
count++;
}
field = curField;
getPathInfo().put(projection, projectionInfo);
getFieldInfo().put(projection, curField);
Class<?> theClass = curField.getFieldType();
// is it actually a Java primitive, or String, or a subclass of Number
boolean isSimple = Types.isSimpleType(theClass);
if (topLevel) {
_projectionName = projectionName.toString();
if (!isSimple) {
_projectionType = DEPENDANT_OBJECT;
} else if (count > 1) {
_projectionType = DEPENDANT_OBJECT_VALUE;
} else if (field.getContainingClassDescriptor() != _clsDesc) {
_projectionType = DEPENDANT_OBJECT_VALUE;
} else {
_projectionType = DEPENDANT_VALUE;
}
} else {
if (!isSimple && onlySimple) {
throw new QueryException("Only primitive values are allowed "
+ "to be passed as parameters to Aggregate and SQL "
+ "functions.");
}
}
break;
case TokenType.KEYWORD_COUNT:
//count a special case
_projectionType = AGGREGATE;
int type = projection.getChild(0).getToken().getTokenType();
if (type == TokenType.TIMES) {
//count(*)
} else if (type == TokenType.KEYWORD_DISTINCT) {
checkProjection(projection.getChild(1), false, false);
} else {
//can call count on object types -- recurse over child
checkProjection(projection.getChild(0), false, false);
}
break;
case TokenType.KEYWORD_SUM:
case TokenType.KEYWORD_MIN:
case TokenType.KEYWORD_MAX:
case TokenType.KEYWORD_AVG:
//an aggregate -- recurse over the child
_projectionType = AGGREGATE;
checkProjection(projection.getChild(0), false, true);
break;
default:
//we use function to describe sql expressions also, like
// SELECT s.age - 5 FROM Student s
_projectionType = FUNCTION;
for (iter = projection.children(); iter.hasNext(); ) {
checkProjection(iter.next(), false, false);
}
}
}
return field;
}
/**
* Traverses the where clause sub-tree and checks for errors. Creates a
* Hashtables with FieldInfo for fields of selected objects which are
* mentioned in the where clause (i.e. nodes with tokenType of IDENTIFIER
* or (DOT, IDENTIFIER, IDENTIFIER)). Als creates a Hashtable of paramInfo
* with type information for query parameters (i.e. $(Class)1 or $1).
*
* @param whereClause WHERE-clause to traverse.
* @throws QueryException if an error is detected.
*/
private void checkWhereClause(final ParseTreeNode whereClause)
throws QueryException {
Iterator<ParseTreeNode> iter;
int tokenType = whereClause.getToken().getTokenType();
switch (tokenType) {
case TokenType.DOT:
checkProjection(whereClause, false, false);
break;
case TokenType.IDENTIFIER:
iter = whereClause.children();
if (iter.hasNext()) {
int type = whereClause.getChild(0).getToken().getTokenType();
if (type == TokenType.LPAREN) {
// A function.
while (iter.hasNext()) {
checkWhereClause(iter.next());
}
}
} else {
checkField(whereClause);
}
break;
case TokenType.DOLLAR:
checkParameter(whereClause);
break;
case TokenType.KEYWORD_IN:
checkField(whereClause.getChild(0));
checkInClauseRightSide(whereClause.getChild(1));
default:
for (iter = whereClause.children(); iter.hasNext(); ) {
checkWhereClause(iter.next());
}
}
}
/**
* Traverses the limit clause sub-tree and checks for errors. Creates
* a Hashtable of paramInfo with type information for query parameters
* (i.e. $1).
*
* @param limitClause LIMIT-clause to traverse.
* @throws QueryException if an error is detected.
*/
private void checkLimitClause(final ParseTreeNode limitClause)
throws QueryException {
int tokenType = limitClause.getToken().getTokenType();
switch (tokenType) {
case TokenType.DOLLAR:
checkParameter(limitClause);
break;
default:
for (Iterator<ParseTreeNode> iter = limitClause.children(); iter.hasNext(); ) {
checkLimitClause(iter.next());
}
}
}
/**
* Traverses the offset clause sub-tree and checks for errors. Creates
* a Hashtable of paramInfo with type information for query parameters
* (i.e. $1).
*
* @param offsetClause OFFSET-clause to traverse.
* @throws QueryException if an error is detected.
*/
private void checkOffsetClause(final ParseTreeNode offsetClause)
throws QueryException {
int tokenType = offsetClause.getToken().getTokenType();
switch (tokenType) {
case TokenType.DOLLAR:
checkParameter(offsetClause);
break;
default:
for (Iterator<ParseTreeNode> iter = offsetClause.children(); iter.hasNext(); ) {
checkLimitClause(iter.next());
}
}
}
/**
* Checks whether the field passed in is valid within this object. Also
* adds this field to a Hashtable.
*
* @param fieldTree A leaf node containing an identifier, or a tree with DOT
* root, and two IDENTIFIER children (for fields that look like
* Person.name or Person->name)
* @return a JDOFieldDescriptor representation of this field.
* @throws QueryException if the field does not exist.
*/
private FieldDescriptor checkField(final ParseTreeNode fieldTree)
throws QueryException {
//see if we've checked this field before.
FieldDescriptor field = getFieldInfo().get(fieldTree);
if (field != null) { return field; }
if (fieldTree.getToken().getTokenType() == TokenType.DOT) {
field = checkProjection(fieldTree, false, false);
} else {
field = getFieldDesc(fieldTree.getToken().getTokenValue(), _clsDesc);
if (field != null) {
getFieldInfo().put(fieldTree, field);
}
}
if (field == null) {
throw new QueryException("The field " + fieldTree.getToken().getTokenValue()
+ " was not found.");
}
return field;
}
/**
* Checks a numbered parameter from an OQL Parse Tree. Creates a {@link ParamInfo}
* object which stores the user or system defined class for this parameter.
* If there's a user defined type for this parameter it is compared to see if
* it is castable from the system defined class. If not, an exception is
* thrown. If a user defined type is specified for a numbered parameter which
* has already been examined, and the user defined types don't match an
* exception is thrown.
*
* @param paramTree the Tree node containing DOLLAR, with children user
* defined class (if available) and parameter number.
* @throws QueryException if an invalid class is specified by the user.
*/
private void checkParameter(final ParseTreeNode paramTree) throws QueryException {
//get the parameter number and user defined type
Integer paramNumber;
String userDefinedType = "";
if (paramTree.getChildCount() == 1) {
paramNumber = Integer.decode(
paramTree.getChild(0).getToken().getTokenValue());
} else {
paramNumber = Integer.decode(
paramTree.getChild(1).getToken().getTokenValue());
userDefinedType = paramTree.getChild(0).getToken().getTokenValue();
}
//Get the system defined type
String systemType = "";
// Get the SQL type if known
FieldDescriptor desc = null;
int operation = paramTree.getParent().getToken().getTokenType();
switch (operation) {
case TokenType.PLUS: case TokenType.MINUS: case TokenType.TIMES:
case TokenType.DIVIDE: case TokenType.KEYWORD_MOD: case TokenType.KEYWORD_ABS:
case TokenType.KEYWORD_LIMIT: //Alex
systemType = "java.lang.Number";
break;
case TokenType.KEYWORD_OFFSET:
systemType = "java.lang.Number";
break;
case TokenType.KEYWORD_LIKE: case TokenType.CONCAT:
systemType = "java.lang.String";
break;
case TokenType.KEYWORD_AND: case TokenType.KEYWORD_OR: case TokenType.KEYWORD_NOT:
systemType = "java.lang.Boolean";
break;
case TokenType.EQUAL: case TokenType.NOT_EQUAL: case TokenType.GT:
case TokenType.GTE: case TokenType.LT: case TokenType.LTE:
case TokenType.KEYWORD_BETWEEN:
systemType = getParamTypeForComparison(paramTree.getParent());
desc = getJDOFieldDescriptor(paramTree.getParent());
break;
case TokenType.KEYWORD_LIST:
systemType = getParamTypeForList(paramTree.getParent());
break;
default:
break;
}
//get the param info for this numbered param
ParamInfo paramInfo = getParamInfo().get(paramNumber);
if (paramInfo == null) {
paramInfo = new ParamInfo(userDefinedType, systemType, desc, _classLoader);
getParamInfo().put(paramNumber, paramInfo);
} else {
paramInfo.check(userDefinedType, systemType);
}
}
private String getParamTypeForComparison(final ParseTreeNode comparisonTree)
throws QueryException {
for (Iterator<ParseTreeNode> iter = comparisonTree.children(); iter.hasNext(); ) {
ParseTreeNode curChild = iter.next();
int tokenType = curChild.getToken().getTokenType();
switch(tokenType) {
case TokenType.STRING_LITERAL: return "java.lang.String";
case TokenType.DOUBLE_LITERAL: return "java.lang.Double";
case TokenType.LONG_LITERAL: return "java.lang.Long";
case TokenType.BOOLEAN_LITERAL: return "java.lang.Boolean";
case TokenType.CHAR_LITERAL: return "java.lang.Character";
case TokenType.DATE_LITERAL: return "java.util.Date";
case TokenType.TIME_LITERAL:
case TokenType.TIMESTAMP_LITERAL: return "java.util.Time";
case TokenType.DOT:
case TokenType.IDENTIFIER:
FieldDescriptor field = checkField(curChild);
return field.getFieldType().getName();
default:
break;
}
}
throw new QueryException("Could not get type for comparison.");
}
/**
* Determine type of the list expression from the first contained
* constant. If there are only bind variables without user defined
* type present in the list, default to String as type.
*
* @param listTree non-empty parser tree of list expression
* @return class name of the list type
* @throws QueryException
*/
private String getParamTypeForList(final ParseTreeNode listTree)
throws QueryException {
for (Iterator<ParseTreeNode> iter = listTree.children(); iter.hasNext(); ) {
ParseTreeNode curChild = iter.next();
int tokenType = curChild.getToken().getTokenType();
switch (tokenType) {
case TokenType.STRING_LITERAL: return "java.lang.String";
case TokenType.DOUBLE_LITERAL: return "java.lang.Double";
case TokenType.LONG_LITERAL: return "java.lang.Long";
case TokenType.BOOLEAN_LITERAL: return "java.lang.Boolean";
case TokenType.CHAR_LITERAL: return "java.lang.Character";
case TokenType.DATE_LITERAL: return "java.util.Date";
case TokenType.TIME_LITERAL:
case TokenType.TIMESTAMP_LITERAL: return "java.util.Time";
case TokenType.DOLLAR: // look for bind parameters with user defined type
if (curChild.getChildCount() == 2) {
String udt = curChild.getChild(0).getToken().getTokenValue();
try {
return Types.typeFromName(_classLoader, udt).getName();
} catch (ClassNotFoundException e1) {
throw new QueryException("Could not find class " + udt);
}
}
default:
break;
}
}
// default to String, if only simple bind variables are present in the list
return "java.lang.String";
}
private FieldDescriptor getJDOFieldDescriptor(final ParseTreeNode comparisonTree)
throws QueryException {
for (Iterator<ParseTreeNode> iter = comparisonTree.children(); iter.hasNext(); ) {
ParseTreeNode curChild = iter.next();
int tokenType = curChild.getToken().getTokenType();
if ((tokenType == TokenType.DOT) || (tokenType == TokenType.IDENTIFIER)) {
return checkField(curChild);
}
}
return null;
}
/**
* Checks the right side of an IN clause. it must be a LIST, and
* the children must all be literals.
*
* @param theList the ParseTreeNode containing the list which is the
* right side argument to IN.
* @throws QueryException if theList is not a list, or the list
* contains non literals.
*/
private void checkInClauseRightSide(final ParseTreeNode theList)
throws QueryException {
if (theList.getToken().getTokenType() != TokenType.KEYWORD_LIST) {
throw new QueryException("The right side of the IN operator must be a LIST.");
}
for (Iterator<ParseTreeNode> iter = theList.children(); iter.hasNext(); ) {
ParseTreeNode node = iter.next();
int tokenType = node.getToken().getTokenType();
switch (tokenType) {
case TokenType.KEYWORD_NIL: case TokenType.KEYWORD_UNDEFINED:
case TokenType.BOOLEAN_LITERAL: case TokenType.LONG_LITERAL:
case TokenType.DOUBLE_LITERAL: case TokenType.CHAR_LITERAL:
case TokenType.STRING_LITERAL: case TokenType.DATE_LITERAL:
case TokenType.TIME_LITERAL: case TokenType.TIMESTAMP_LITERAL:
break;
case TokenType.DOLLAR: // bind variable parameter
checkParameter(node);
break;
default:
throw new QueryException("The LIST can only contain literals, bind "
+ "variables and the keywords 'nil' and 'undefined'.");
}
}
}
/**
* Traverses the order by clause sub-tree and checks for errors.
*
* @param orderClause ORDER-BY clause to traverse.
* @throws QueryException if an error is detected.
*/
private void checkOrderClause(final ParseTreeNode orderClause)
throws QueryException {
if (orderClause.getToken().getTokenType() != TokenType.KEYWORD_ORDER) {
throw new QueryException("checkOrderClause was called on a subtree which "
+ "is not an order clause.");
}
ParseTreeNode prevChild = null;
for (Iterator<ParseTreeNode> iter = orderClause.children(); iter.hasNext(); ) {
ParseTreeNode curChild = iter.next();
int tokenType = curChild.getToken().getTokenType();
switch (tokenType) {
case TokenType.KEYWORD_ASC:
case TokenType.KEYWORD_DESC:
// iterate on child
curChild = curChild.getChild(0);
tokenType = curChild.getToken().getTokenType();
break;
default:
break;
}
switch (tokenType) {
case TokenType.DOT:
checkProjection(curChild, false, false);
break;
case TokenType.IDENTIFIER:
if (curChild.children().hasNext()) {
int type = curChild.getChild(0).getToken().getTokenType();
if (type == TokenType.LPAREN) {
// A function, skip to next element
Iterator<ParseTreeNode> arguments = curChild.getChild(0).children();
while (arguments.hasNext()) {
ParseTreeNode nn = arguments.next();
checkWhereClause(nn);
}
}
} else {
checkField(curChild);
}
break;
case TokenType.LPAREN:
int type = prevChild.getToken().getTokenType();
if ((prevChild == null) || (type != TokenType.IDENTIFIER)) {
throw new QueryException("Illegal use of left parenthesis in "
+ "ORDER BY clause.");
}
break;
default:
throw new QueryException("Only identifiers, path expressions, and the "
+ "keywords ASC and DESC are allowed in the ORDER BY clause.");
}
prevChild = curChild;
}
}
/**
* Generates the QueryExpression which is an SQL representation or the OQL
* parse tree.
*
* @throws SyntaxNotSupportedException If a specific OQL feature is not supported
* by a RDBMS.
*/
private void createQueryExpression() throws SyntaxNotSupportedException {
//We use the SQLEngine buildfinder for any queries which require
//us to load the entire object from the database. Otherwise
//we use the local addSelectFromJoins method.
switch (_projectionType) {
case PARENT_OBJECT:
case DEPENDANT_OBJECT:
case DEPENDANT_OBJECT_VALUE:
_queryExpr = _engine.getFinder();
break;
default:
_queryExpr = _engine.getQueryExpression();
addSelectFromJoins();
}
_queryExpr.setDbMetaInfo(_dbInfo);
//check for DISTINCT
int type = _parseTree.getChild(0).getToken().getTokenType();
if (type == TokenType.KEYWORD_DISTINCT) {
_queryExpr.setDistinct(true);
}
//process where clause and order clause
for (Iterator<ParseTreeNode> iter = _parseTree.children(); iter.hasNext(); ) {
ParseTreeNode curChild = iter.next();
int tokenType = curChild.getToken().getTokenType();
switch (tokenType) {
case TokenType.KEYWORD_WHERE:
addWhereClause(curChild);
break;
case TokenType.KEYWORD_ORDER:
_queryExpr.addOrderClause(getOrderClause(curChild));
break;
case TokenType.KEYWORD_LIMIT:
addLimitClause(curChild);
break;
case TokenType.KEYWORD_OFFSET:
addOffsetClause(curChild);
break;
default:
break;
}
}
}
/**
* Adds the selected fields, and the necessary joins to the QueryExpression.
* This method is used when the query is for dependant values, aggregates,
* or SQL functions, where we're just gonna query the item directly.
*/
private void addSelectFromJoins() {
ParseTreeNode selectPart = null;
int type = _parseTree.getChild(0).getToken().getTokenType();
if (type == TokenType.KEYWORD_DISTINCT) {
selectPart = _parseTree.getChild(1);
} else {
selectPart = _parseTree.getChild(0);
}
String table = new ClassDescriptorJDONature(_clsDesc).getTableName();
_queryExpr.addTable(table, table);
// add table names and joins for all base classes
ClassDescriptor oldDesc = _clsDesc;
ClassDescriptor tempDesc = _clsDesc.getExtends();
while (tempDesc != null) {
String tableName = new ClassDescriptorJDONature(tempDesc).getTableName();
_queryExpr.addTable(tableName, tableName);
FieldDescriptor leftField = oldDesc.getIdentity();
FieldDescriptor rightField = tempDesc.getIdentity();
_queryExpr.addInnerJoin(
new ClassDescriptorJDONature(oldDesc).getTableName(),
new FieldDescriptorJDONature(leftField).getSQLName(),
new ClassDescriptorJDONature(oldDesc).getTableName(),
new ClassDescriptorJDONature(tempDesc).getTableName(),
new FieldDescriptorJDONature(rightField).getSQLName(),
new ClassDescriptorJDONature(tempDesc).getTableName());
oldDesc = tempDesc;
tempDesc = tempDesc.getExtends();
}
_queryExpr.addSelect(getSQLExpr(selectPart));
}
/**
* Builds the alias name for a table from the path info.
*
* @param tableName The name of the table to add to the select clause
* @param path The path info vector to build the alias with
* @param tableIndex Field index in the path info
* @return Alias name for a given table.
*/
public String buildTableAlias(final String tableName, final Vector<String> path,
final int tableIndex) {
/* FIXME This aliasing will cause problems if a (different) table
with the same name as our alias already exists (or will be added
at a later time) in the query. Catching this might become a bit
tricky ... */
String tableAlias = tableName;
int index;
/* We don't just alias all tables here, but only those where some
attribute is queried (not those where we query a whole object).
The reasons for this are impossible for me to explain right now,
as I am rather tired; I'll write some better explanation later -
I promise! :-) */
if ((path != null) && (path.size() > 2)) {
if (_allPaths == null) { _allPaths = new HashMap<ArrayList<String>, Integer>(); }
ArrayList<String> tablePath = new ArrayList<String>(path.subList(0, tableIndex + 1));
Integer i = _allPaths.get(tablePath);
if (i == null) {
index = _allPaths.size();
_allPaths.put(tablePath, new Integer(index));
} else {
index = i.intValue();
}
/* fix for oracle support. If we have a 30 character table name, adding
anything to it (_#) will cause it to be too long and will make oracle
barf. the length we are trying to evaluate is:
length of the table name + the length of the index number we are using
+ the _ character. */
String stringIndex = String.valueOf(index);
if ((tableAlias.length() + stringIndex.length() + 1) > MAX_TABLE_LENGTH) {
/* use a substring of the original table name, rather than the table alias
because if we truncate the table alias it could become "ununique"
(by truncating the _# we add) by truncating the table name
beforehand we are guaranteed uniqueness as long as the index is
not reused. */
tableAlias = tableAlias.substring(
MAX_TABLE_LENGTH - (stringIndex.length() + 1));
}
/* If table name contains '.', it should be replaced, since such names aren't
allowed for aliases */
tableAlias = tableAlias.replace('.', '_') + "_" + index;
}
return tableAlias;
}
/**
* Adds joins to the queryExpr for path expressions in the OQL.
*
* @param path Path expression to be added to JOIN clause.
*/
private void addJoinsForPathExpression(final Vector<String> path) {
if (path == null) {
throw new IllegalStateException("path = null !");
}
// the class for the join is even this class or one of the base classes
ClassDescriptor sourceClass = _clsDesc;
for (int i = 1; i < path.size() - 1; i++) {
FieldDescriptor fieldDesc = null;
// Find the sourceclass and the fielsddescriptor in the class hierachie
Object[] fieldAndClass = getFieldAndClassDesc(
path.elementAt(i), sourceClass, _queryExpr, path, i - 1);
if (fieldAndClass == null) {
throw new IllegalStateException("Field not found:" + path.elementAt(i));
}
fieldDesc = (FieldDescriptor) fieldAndClass[0];
sourceClass = (ClassDescriptor) fieldAndClass[1];
ClassDescriptor clsDesc = fieldDesc.getClassDescriptor();
FieldDescriptorJDONature fieldJDONature = new FieldDescriptorJDONature(fieldDesc);
if (clsDesc != null) {
//we must add this table as a join
ClassDescriptorJDONature sourceClassJDONature =
new ClassDescriptorJDONature(sourceClass);
if (fieldJDONature.getManyKey() == null) {
//a many -> one relationship
FieldDescriptor foreignKey = clsDesc.getIdentity();
String sourceTableAlias = sourceClassJDONature.getTableName();
if (i > 1) {
sourceTableAlias = buildTableAlias(sourceTableAlias, path, i - 1);
}
ClassDescriptorJDONature clsDescNature;
clsDescNature = new ClassDescriptorJDONature(clsDesc);
_queryExpr.addInnerJoin(
sourceClassJDONature.getTableName(),
new FieldDescriptorJDONature(fieldDesc).getSQLName(),
sourceTableAlias,
clsDescNature.getTableName(),
new FieldDescriptorJDONature(foreignKey).getSQLName(),
buildTableAlias(clsDescNature.getTableName(), path, i));
} else if (fieldJDONature.getManyTable() == null) {
//a one -> many relationship
FieldDescriptor identity = sourceClass.getIdentity();
String sourceTableAlias = sourceClassJDONature.getTableName();
if (i > 1) {
sourceTableAlias = buildTableAlias(sourceTableAlias, path, i - 1);
}
ClassDescriptorJDONature clsDescNature;
clsDescNature = new ClassDescriptorJDONature(clsDesc);
_queryExpr.addInnerJoin(
sourceClassJDONature.getTableName(),
new FieldDescriptorJDONature(identity).getSQLName(),
sourceTableAlias,
clsDescNature.getTableName(),
fieldJDONature.getManyKey(),
buildTableAlias(clsDescNature.getTableName(), path, i));
} else {
//a many -> many relationship
FieldDescriptor identity = sourceClass.getIdentity();
FieldDescriptor foreignKey = clsDesc.getIdentity();
String manyTableAlias = fieldJDONature.getManyTable();
String sourceTableAlias = sourceClassJDONature.getTableName();
if (i > 1) {
manyTableAlias = buildTableAlias(manyTableAlias, path, i - 1);
sourceTableAlias = buildTableAlias(sourceTableAlias, path, i - 1);
}
_queryExpr.addInnerJoin(
sourceClassJDONature.getTableName(),
new FieldDescriptorJDONature(identity).getSQLName(),
sourceTableAlias,
fieldJDONature.getManyTable(),
fieldJDONature.getManyKey(),
manyTableAlias);
ClassDescriptorJDONature clsDescNature;
clsDescNature = new ClassDescriptorJDONature(clsDesc);
_queryExpr.addInnerJoin(
fieldJDONature.getManyTable(),
new FieldDescriptorJDONature(fieldDesc).getSQLName(),
manyTableAlias,
clsDescNature.getTableName(),
new FieldDescriptorJDONature(foreignKey).getSQLName(),
buildTableAlias(clsDescNature.getTableName(), path, i));
}
sourceClass = clsDesc;
}
}
}
/**
* Adds a SQL version of an OQL where clause.
*
* @param whereClause the parse tree node with the where clause
*/
private void addWhereClause(final ParseTreeNode whereClause) {
String sqlExpr = getSQLExpr(whereClause.getChild(0));
_queryExpr.addWhereClause(sqlExpr);
}
/**
* Adds a SQL version of an OQL limit clause.
*
* @param limitClause the parse tree node with the limit clause
* @throws SyntaxNotSupportedException If the LIMIT clause is not supported by a
* RDBMS.
*/
private void addLimitClause(final ParseTreeNode limitClause)
throws SyntaxNotSupportedException {
String sqlExpr = getSQLExpr(limitClause);
_queryExpr.addLimitClause(sqlExpr);
}
/**
* Adds a SQL version of an OQL offset clause.
*
* @param offsetClause The parse tree node with the offset clause
* @throws SyntaxNotSupportedException If the LIMIT clause is not supported by a
* RDBMS.
*/
private void addOffsetClause(final ParseTreeNode offsetClause)
throws SyntaxNotSupportedException {
String sqlExpr = getSQLExpr(offsetClause);
_queryExpr.addOffsetClause(sqlExpr);
}
/**
* Returns a SQL version of an OQL expr.
*
* @param exprTree the parse tree node with the expr
* @return The SQL translation of the expr.
*/
private String getSQLExpr(final ParseTreeNode exprTree) {
StringBuffer sb = null;
int tokenType = exprTree.getToken().getTokenType();
switch (tokenType) {
case TokenType.LPAREN:
//Parens passed through from where clause in SQL
return "( " + getSQLExpr(exprTree.getChild(0)) + " )";
case TokenType.PLUS:
case TokenType.MINUS:
case TokenType.KEYWORD_ABS:
case TokenType.KEYWORD_NOT:
//(possible) unary operators
if (exprTree.getChildCount() == 1) {
return exprTree.getToken().getTokenValue() + " "
+ getSQLExpr(exprTree.getChild(0));
}
//this was binary PLUS or MINUS
return getSQLExpr(exprTree.getChild(0)) + " "
+ exprTree.getToken().getTokenValue() + " "
+ getSQLExpr(exprTree.getChild(1));
case TokenType.KEYWORD_AND:
case TokenType.KEYWORD_OR:
case TokenType.EQUAL:
case TokenType.NOT_EQUAL:
case TokenType.CONCAT:
case TokenType.GT:
case TokenType.GTE:
case TokenType.LT:
case TokenType.LTE:
case TokenType.TIMES:
case TokenType.DIVIDE:
case TokenType.KEYWORD_MOD:
case TokenType.KEYWORD_LIKE:
case TokenType.KEYWORD_IN:
//binary operators
return getSQLExpr(exprTree.getChild(0)) + " "
+ exprTree.getToken().getTokenValue() + " "
+ getSQLExpr(exprTree.getChild(1));
case TokenType.KEYWORD_BETWEEN:
//tertiary BETWEEN operator
return getSQLExpr(exprTree.getChild(0)) + " "
+ exprTree.getToken().getTokenValue() + " "
+ getSQLExpr(exprTree.getChild(1)) + " AND "
+ getSQLExpr(exprTree.getChild(2));
case TokenType.KEYWORD_IS_DEFINED:
//built in functions
return getSQLExpr(exprTree.getChild(0)) + " IS NOT NULL ";
case TokenType.KEYWORD_IS_UNDEFINED:
return getSQLExpr(exprTree.getChild(0)) + " IS NULL ";
case TokenType.KEYWORD_COUNT:
int type = exprTree.getChild(0).getToken().getTokenType();
if (type == TokenType.TIMES) {
return " COUNT(*) ";
} else if (type == TokenType.KEYWORD_DISTINCT) {
return " COUNT(DISTINCT " + getSQLExpr(exprTree.getChild(1)) + ") ";
} else {
return " COUNT(" + getSQLExpr(exprTree.getChild(0)) + ") ";
}
case TokenType.KEYWORD_SUM:
case TokenType.KEYWORD_MIN:
case TokenType.KEYWORD_MAX:
case TokenType.KEYWORD_AVG:
return " " + exprTree.getToken().getTokenValue() + "("
+ getSQLExpr(exprTree.getChild(0)) + ") ";
case TokenType.KEYWORD_LIST:
//List creation
sb = new StringBuffer("( ");
for (Iterator<ParseTreeNode> iter = exprTree.children(); iter.hasNext(); ) {
sb.append(getSQLExpr(iter.next())).append(" , ");
}
//replace final comma space with close paren.
sb.replace(sb.length() - 2, sb.length() - 1, " )").append(" ");
return sb.toString();
case TokenType.IDENTIFIER:
case TokenType.DOT:
//fields or SQL functions
boolean lparen = (exprTree.getChildCount() > 0);
if (lparen) {
int temp = exprTree.getChild(0).getToken().getTokenType();
lparen = lparen && (temp == TokenType.LPAREN);
}
if (lparen) {
//An SQL function
sb = new StringBuffer(exprTree.getToken().getTokenValue()).append("(");
int paramCount = 0;
Iterator<ParseTreeNode> iter = exprTree.children();
iter = (iter.next()).children(); // LPAREN's children
for (; iter.hasNext(); ) {
sb.append(getSQLExpr(iter.next())).append(" , ");
paramCount++;
}
if (paramCount > 0) {
//replace final comma space with close paren.
sb.replace(sb.length() - 2, sb.length() - 1, " )");
} else {
//there were no parameters, so no commas.
sb.append(") ");
}
return sb.toString();
}
//a field
Vector<String> path = getPathInfo().get(exprTree);
if (tokenType == TokenType.DOT) {
if (path == null) {
System.err.println("exprTree=" + exprTree.toStringEx()
+ "\npathInfo = {");
Iterator<ParseTreeNode> iter = getPathInfo().keySet().iterator();
ParseTreeNode n;
while (iter.hasNext()) {
n = iter.next();
System.err.println("\t" + n.toStringEx());
}
// Exception follows in addJoinsForPathExpression()
}
addJoinsForPathExpression(path);
}
FieldDescriptor field = getFieldInfo().get(exprTree);
if (field == null) {
throw new IllegalStateException(
"fieldInfo for " + exprTree.toStringEx() + " not found");
}
ClassDescriptor clsDesc = field.getContainingClassDescriptor();
if (clsDesc == null) {
throw new IllegalStateException(
"ContainingClass of " + field.toString() + " is null !");
}
String clsTableAlias;
ClassDescriptorJDONature classDescriptorJDONature =
new ClassDescriptorJDONature(clsDesc);
if (tokenType == TokenType.DOT && path != null && path.size() > 2) {
clsTableAlias = buildTableAlias(
classDescriptorJDONature.getTableName(), path, path.size() - 2);
ClassDescriptor srcDesc = _clsDesc;
for (int i = 1; i < path.size(); i++) {
Object[] fieldAndClass = getFieldAndClassDesc(
path.elementAt(i), srcDesc, _queryExpr, path, i - 1);
if (fieldAndClass == null) {
throw new IllegalStateException("Field not found: "
+ path.elementAt(i) + " class " + srcDesc.getJavaClass());
}
FieldDescriptor fieldDesc = (FieldDescriptor) fieldAndClass[0];
srcDesc = fieldDesc.getClassDescriptor();
}
} else {
clsTableAlias = buildTableAlias(classDescriptorJDONature.getTableName(), path, 9999);
}
return _queryExpr.encodeColumn(clsTableAlias,
new FieldDescriptorJDONature(field).getSQLName()[0]);
case TokenType.DOLLAR:
//parameters
//return a question mark with the parameter number.
//The calling function will do a mapping
int child = exprTree.getChildCount() - 1;
return "?" + exprTree.getChild(child).getToken().getTokenValue();
case TokenType.BOOLEAN_LITERAL:
case TokenType.LONG_LITERAL:
case TokenType.DOUBLE_LITERAL:
case TokenType.CHAR_LITERAL:
//literals which need no modification
return exprTree.getToken().getTokenValue();
case TokenType.STRING_LITERAL:
//String literals: change \" to ""
//char replace function should really be somewhere else first change \" to "
sb = new StringBuffer();
String copy = exprTree.getToken().getTokenValue();
int pos = copy.indexOf("\\\"", 1);
while (pos != -1) {
sb.append(copy.substring(0, pos)).append("\"\"");
copy = copy.substring(pos + 2);
pos = copy.indexOf("\\\"");
}
sb.append(copy);
//Then change surrounding double quotes to single quotes, and change ' to ''
copy = sb.deleteCharAt(0).toString();
sb.setLength(0);
sb.append("'");
pos = copy.indexOf("'", 1);
while (pos != -1) {
sb.append(copy.substring(0, pos)).append("''");
copy = copy.substring(pos + 1);
pos = copy.indexOf("'");
}
sb.append(copy);
//replace final double quote with single quote
sb.replace(sb.length() - 1, sb.length(), "'");
return sb.toString();
case TokenType.DATE_LITERAL:
case TokenType.TIME_LITERAL:
//Date, time and timestamp literals...strip off keyword (?is that all?)
//date and time both 4 chars long.
return exprTree.getToken().getTokenValue().substring(5);
case TokenType.TIMESTAMP_LITERAL:
return exprTree.getToken().getTokenValue().substring(10);
case TokenType.KEYWORD_NIL:
case TokenType.KEYWORD_UNDEFINED:
return " NULL ";
case TokenType.KEYWORD_LIMIT:
case TokenType.KEYWORD_OFFSET:
return getSQLExprForLimit(exprTree);
default:
break;
}
return "";
}
private String getSQLExprForLimit(final ParseTreeNode limitClause) {
StringBuffer sb = new StringBuffer();
for (Iterator<ParseTreeNode> iter = limitClause.children(); iter.hasNext(); ) {
ParseTreeNode exprTree = iter.next();
int tokenType = exprTree.getToken().getTokenType();
switch (tokenType) {
case TokenType.DOLLAR:
//parameters
//return a question mark with the parameter number.
//The calling function will do a mapping
int count = exprTree.getChildCount() - 1;
sb.append("?" + exprTree.getChild(count).getToken().getTokenValue());
break;
case TokenType.COMMA:
sb.append(" , ");
break;
case TokenType.BOOLEAN_LITERAL:
case TokenType.LONG_LITERAL:
case TokenType.DOUBLE_LITERAL:
case TokenType.CHAR_LITERAL:
return exprTree.getToken().getTokenValue();
default:
break;
}
}
return sb.toString();
}
/**
* Returns a SQL version of an OQL order by clause.
*
* @param orderClause the parse tree node with the order by clause
* @return The SQL translation of the order by clause.
*/
private String getOrderClause(final ParseTreeNode orderClause) {
StringBuffer sb = new StringBuffer();
for (Iterator<ParseTreeNode> iter = orderClause.children(); iter.hasNext(); ) {
sb.append(", ");
ParseTreeNode curChild = iter.next();
int tokenType = curChild.getToken().getTokenType();
switch (tokenType) {
case TokenType.KEYWORD_ASC:
sb.append(getSQLExpr(curChild.getChild(0))).append(" ASC ");
break;
case TokenType.KEYWORD_DESC:
sb.append(getSQLExpr(curChild.getChild(0))).append(" DESC ");
break;
case TokenType.DOT:
case TokenType.IDENTIFIER:
sb.append(getSQLExpr(curChild)).append(" ");
break;
default:
break;
}
}
//remove the additional comma space at the beginning
sb.deleteCharAt(0).deleteCharAt(0);
return sb.toString();
}
}