blob: bafb724720f93f6cc0ba57afd291215f1403938f [file] [log] [blame]
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
/*
* CodeGeneration.g
*
* Created on March 13, 2000
*/
header
{
package com.sun.jdo.spi.persistence.support.sqlstore.query.jqlc;
import java.util.*;
import java.lang.reflect.Field;
import java.lang.IllegalAccessException;
import com.sun.jdo.api.persistence.support.JDOFatalUserException;
import com.sun.jdo.spi.persistence.support.sqlstore.PersistenceCapable;
import com.sun.jdo.spi.persistence.support.sqlstore.PersistenceManager;
import com.sun.jdo.spi.persistence.support.sqlstore.RetrieveDesc;
import com.sun.jdo.spi.persistence.support.sqlstore.StateManager;
import org.glassfish.persistence.common.I18NHelper;
import com.sun.jdo.spi.persistence.utility.FieldTypeEnumeration;
import com.sun.jdo.spi.persistence.utility.logging.Logger;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.TypeTable;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.Type;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.ClassType;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.FieldInfo;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.NumericType;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.NumericWrapperClassType;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.NumberType;
import com.sun.jdo.spi.persistence.support.sqlstore.query.util.type.StringType;
}
/**
* This class defines the code generation pass of the JQL compiler.
* Input of this pass is the typed and optimized AST as produced by optimizer.
* The result is a RetrieveDesc.
*
* @author Michael Bouschen
* @author Shing Wai Chan
* @version 0.1
*/
class CodeGeneration extends TreeParser;
options
{
importVocab = JQL;
ASTLabelType = "JQLAST"; //NOI18N
}
{
/** Name of the USE_IN property. */
public static final String USE_IN_PROPERTY =
"com.sun.jdo.spi.persistence.support.sqlstore.query.jqlc.USE_IN";
/** */
private static final boolean USE_IN = Boolean.getBoolean(USE_IN_PROPERTY);
/**
* I18N support
*/
protected final static ResourceBundle messages =
I18NHelper.loadBundle(CodeGeneration.class);
/**
* The persistence manager the query object is connected to.
*/
protected PersistenceManager pm;
/**
* type table
*/
protected TypeTable typetab;
/**
* query parameter table
*/
protected ParameterTable paramtab;
/**
*
*/
protected ErrorMsg errorMsg;
/**
* prefetchEnabled flag for RetrieveDesc.
*/
protected boolean prefetchEnabled;
/**
* The RetrieveDesc for the candidate class.
* Code gen for the CLASS_DEF AST will initilaized this variable.
* Code gen for the filter expression will add the constraints.
*/
protected RetrieveDesc candidateRD;
/**
* rd2TagMap maps RetrieveDesc to tags. A tag is either the variable name or
* the navigation path that used to create a new RetrieveDesc. This info is
* needed to identify whether two different RetrieveDescs denote the same
* variable or relationship.
*/
protected Map rd2TagMap;
/**
* Set of RetrieveDescs. CodeGeneration uses this set to prevent multiple
* addConstraint calls for the RetrieveDescs denoting a variable.
*/
protected Set boundRetrieveDescs;
/** The logger */
private static Logger logger = LogHelperQueryCompilerJDO.getLogger();
/**
* Defines the SQL wildcard character to be used in wildcard pattern
* (string methods startsWith and endsWith).
*/
protected static final String WILDCARD_PATTERN = "%"; //NOI18N
/**
*
*/
public void init(PersistenceManager pm, TypeTable typetab,
ParameterTable paramtab, ErrorMsg errorMsg,
boolean prefetchEnabled)
{
this.pm = pm;
this.typetab = typetab;
this.paramtab = paramtab;
this.errorMsg = errorMsg;
this.prefetchEnabled = prefetchEnabled;
this.rd2TagMap = new HashMap();
this.boundRetrieveDescs = new HashSet();
}
/**
*
*/
public void reportError(RecognitionException ex) {
errorMsg.fatal("CodeGeneration error", ex); //NOI18N
}
/**
*
*/
public void reportError(String s) {
errorMsg.fatal("CodeGeneration error: " + s); //NOI18N
}
/**
* Returns the RetrieveDesc that represents the current query.
*/
public RetrieveDesc getRetrieveDesc()
{
if (candidateRD instanceof DebugRetrieveDesc)
return ((DebugRetrieveDesc)candidateRD).wrapped;
return candidateRD;
}
/**
* Helper method for checkRetrieveDesc handling operators & and &&.
*/
protected void checkAndOpRetrieveDesc(JQLAST op, JQLAST left,
JQLAST right, Map usedRD) throws RecognitionException
{
if ((right.getType() == CONTAINS) || (right.getType() == NOT_CONTAINS))
{
// If right is a CONTAINS clause, start analysing the right expr.
// This ensures that the lft expression can reuse the RD defined
// for the variable from the contains clause
checkRetrieveDesc(right, usedRD);
checkRetrieveDesc(left, usedRD);
}
else
{
checkRetrieveDesc(left, usedRD);
checkRetrieveDesc(right, usedRD);
}
op.setRetrieveDesc(getCommonRetrieveDesc(left, right));
}
/**
* Check the attached RetrieveDesc of the specified binary operation and its operands.
*/
protected RetrieveDesc getCommonRetrieveDesc(JQLAST left, JQLAST right)
{
RetrieveDesc rd = null;
RetrieveDesc leftRD = left.getRetrieveDesc();
RetrieveDesc rightRD = right.getRetrieveDesc();
if ((leftRD == null) && (rightRD != null))
{
// case 1: no RetrieveDesc for left operand, but right operand returns RetrieveDesc
// attach the right RetrieveDesc to all nodes of the left subtree
propagateRetrieveDesc(left, rightRD);
rd = rightRD;
}
else if ((leftRD != null) && (rightRD == null))
{
// case 2: no RetrieveDesc for right operand, but left operand returns RetrieveDesc
// attach the left RetrieveDesc to all nodes of the right subtree
propagateRetrieveDesc(right, leftRD);
rd = leftRD;
}
else if ((leftRD != null) && (rightRD != null))
{
// case 3: both left and right operand have a RetrieveDesc attached
if (leftRD == rightRD)
{
// case 3a: left and right RetrieveDesc are identical
rd = leftRD;
}
else
{
// case 3b: left and right RetrieveDesc are NOT identical
// check navigation:
rd = getCommonRetrieveDescHelper(leftRD, findNavigationSource(left),
rightRD, findNavigationSource(right));
// use leftRD as default
if (rd == null)
{
rd = leftRD;
}
}
}
return rd;
}
/** Helper method for getCommonRetrieveDesc used to check navigation. */
protected RetrieveDesc getCommonRetrieveDescHelper(
RetrieveDesc leftRD, JQLAST leftNavSrc,
RetrieveDesc rightRD, JQLAST rightNavSrc)
{
RetrieveDesc rd = null;
String leftPath = (String)rd2TagMap.get(leftRD);
String rightPath = (String)rd2TagMap.get(rightRD);
RetrieveDesc leftNavSrcRD =
(leftNavSrc == null) ? null : leftNavSrc.getRetrieveDesc();
String leftNavSrcPath =
(leftNavSrcRD == null ) ? null: (String)rd2TagMap.get(leftNavSrcRD);
RetrieveDesc rightNavSrcRD =
(rightNavSrc == null) ? null : rightNavSrc.getRetrieveDesc();
String rightNavSrcPath =
(rightNavSrcRD == null) ? null : (String)rd2TagMap.get(rightNavSrcRD);
if ((leftNavSrcPath != null) && leftNavSrcPath.equals(rightPath))
{
// case I: left operand is a navigation and
// the navigation source is equal to the right operand
rd = rightRD;
}
else if ((rightNavSrcPath != null) && rightNavSrcPath.equals(leftPath))
{
// case II: right operand is a navigation and
// the navigation source is equal to the left operand
rd = leftRD;
}
else if ((leftNavSrcPath != null) && (rightNavSrcPath != null) &&
leftNavSrcPath.equals(rightNavSrcPath))
{
// case III: both operands are navigations and have the same source
rd = leftNavSrcRD;
}
else {
// case IV: check whether the navigation source is a bound variable.
// If yes, check the collection source
JQLAST leftConstraint = findNavigationSourceOfBoundVariable(leftNavSrc);
JQLAST rightConstraint = findNavigationSourceOfBoundVariable(rightNavSrc);
if ((leftConstraint != null) && (rightConstraint != null))
{
rd = getCommonRetrieveDescHelper(leftRD, leftConstraint,
rightRD, rightConstraint);
}
else if ((leftConstraint == null) && (rightConstraint != null))
{
rd = getCommonRetrieveDescHelper(leftRD, leftNavSrc,
rightRD, rightConstraint);
}
else if ((leftConstraint != null) && (rightConstraint == null))
{
rd = getCommonRetrieveDescHelper(leftRD, leftConstraint,
rightRD, rightNavSrc);
}
}
return rd;
}
/**
* Helper method to support getting the common RetrieveDesc for operands
* taking three arguments such as like with escape, substring, indexOf.
*/
protected RetrieveDesc getCommonRetrieveDesc(JQLAST arg1, JQLAST arg2, JQLAST arg3)
{
RetrieveDesc rd = null;
if (arg3 == null) {
// Just call the regular method for binray ops,
// if the third argument is not specified.
rd = getCommonRetrieveDesc(arg1, arg2);
}
else {
// First check args two and three.
getCommonRetrieveDesc(arg2, arg3);
// Now check the first and the second arg.
rd = getCommonRetrieveDesc(arg1, arg2);
// Propagate the common RetrieveDesc to the third arg.
// This is important, if arg two and three are literals.
// Then the first call checking arg2 and arg3 did not attach any
// RetrieveDesc. The second call checking arg1 and arg2 might have
// propagated a rd from arg1 to arg2. So this propagateRetrieveDesc
// call propagates this rd to arg3, too.
propagateRetrieveDesc(arg3, rd);
}
return rd;
}
/**
* Helper method to support getting the common RetrieveDesc for object
* comparison operators.
*/
protected RetrieveDesc getObjectComparisonRetrieveDesc(JQLAST left, JQLAST right)
{
RetrieveDesc rd = null;
if ((left.getType() == NAVIGATION) &&
(right.getType() == VALUE) && (right.getValue() == null))
{
// case obj.relship == null
// take the RetrieveDesc from the navigation source
rd = ((JQLAST)left.getFirstChild()).getRetrieveDesc();
}
else if ((left.getType() == VALUE) && (left.getValue() == null) &&
(right.getType() == NAVIGATION))
{
// case null == obj.relship
// take the RetrieveDesc from the navigation source
rd = ((JQLAST)right.getFirstChild()).getRetrieveDesc();
}
else
{
// use regular getCommonRetrieveDesc
rd = getCommonRetrieveDesc(left, right);
}
return rd;
}
/**
* Returns the source if a navigation or field access.
*/
protected JQLAST findNavigationSource(JQLAST tree)
{
JQLAST child = (JQLAST)tree.getFirstChild();
switch (tree.getType())
{
case NOT_IN:
case FIELD_ACCESS:
case NAVIGATION:
return findNavigationSource(child);
case THIS:
case VARIABLE:
return tree;
case CONTAINS:
case NOT_CONTAINS:
return null;
default:
for (JQLAST node = child; node != null; node = (JQLAST)node.getNextSibling())
{
JQLAST tmp = findNavigationSource(node);
if (tmp != null)
return tmp;
}
}
return null;
}
/**
* If the specifid node is a bound variable return the navigation source of
* it's collection.
*/
protected JQLAST findNavigationSourceOfBoundVariable(JQLAST tree)
{
if ((tree.getType() == VARIABLE) && (tree.getFirstChild() != null))
return findNavigationSource((JQLAST)tree.getFirstChild());
return null;
}
/**
* Attach the specified RetrieveDesc to all JQLAST node of the ast subtree,
* that do not have a RetrieveDesc attached.
*/
protected void propagateRetrieveDesc(JQLAST ast, RetrieveDesc rd)
{
if (ast.getRetrieveDesc() == null)
{
ast.setRetrieveDesc(rd);
}
for (JQLAST node = (JQLAST)ast.getFirstChild();
node != null;
node = (JQLAST)node.getNextSibling())
{
propagateRetrieveDesc(node, rd);
}
}
/**
* Returns an Object representing 0 according to the specified type.
*/
protected Object getZeroValue(Type type)
{
return (type instanceof NumberType) ?
((NumberType)type).getValue(new Integer(0)) :
null;
}
/**
* Returns an Object representing -1 according to the specified type.
*/
protected Object getMinusOneValue(Type type)
{
return (type instanceof NumberType) ?
((NumberType)type).getValue(new Integer(-1)) :
null;
}
/**
* Returns -value.
* The method assumes that the passed argument is a numeric wrapper class object.
* If so it negates the wrapped numeric value and wraps the negated value into a
* numeric wrapper class object.
*/
protected Object negate(Object value, Type type)
{
return (type instanceof NumberType) ?
((NumberType)type).negate((Number)value) :
null;
}
/**
* Returns the boolean operation of the equivalent relational expression
* with swapped arguments.
* expr1 > expr2 <=> expr2 < expr1
*/
protected int getSwappedOp(int operation)
{
int ret = 0;
switch (operation)
{
case RetrieveDesc.OP_EQ:
ret = RetrieveDesc.OP_EQ;
break;
case RetrieveDesc.OP_NE:
ret = RetrieveDesc.OP_NE;
break;
case RetrieveDesc.OP_LT:
ret = RetrieveDesc.OP_GT;
break;
case RetrieveDesc.OP_LE:
ret = RetrieveDesc.OP_GE;
break;
case RetrieveDesc.OP_GT:
ret = RetrieveDesc.OP_LT;
break;
case RetrieveDesc.OP_GE:
ret = RetrieveDesc.OP_LE;
break;
}
return ret;
}
/**
* Code generation for a comparison of the form field relop value,
* where field denotes a non relationship field
* This method checks for null values and generates OP_NULL / OP_NOTNULL constraints
* in the case of field relop null
*/
protected void generateSimpleFieldValueComparison(RetrieveDesc rd, String name,
int operation, Object value)
{
if (value != null)
{
rd.addConstraint(name, operation, value);
}
else if (operation == RetrieveDesc.OP_EQ)
{
rd.addConstraint(name, RetrieveDesc.OP_NULL, null);
}
else if (operation == RetrieveDesc.OP_NE)
{
rd.addConstraint(name, RetrieveDesc.OP_NOTNULL, null);
}
else
{
errorMsg.fatal(I18NHelper.getMessage(messages, "jqlc.codegeneration.generatesimplefieldvaluecomparison.invalidvalue")); //NOI18N
}
}
/**
* Code generation for a comparison of the form
* dbvalue relop constant
* where dbvalue denotes an object in the database such as
* - this
* - the result of a relationship navigation
* - variable access
* and constant is a constant value at query compile time (e.g. a literal)
*/
protected void generateDbValueConstantComparison(RetrieveDesc rd, ClassType objectType,
int operation, Object value, Type valueType)
{
int booleanOp = getKeyFieldsComparisonBooleanOp(operation);
List keyFieldNames = objectType.getKeyFieldNames();
for (Iterator i = keyFieldNames.iterator(); i.hasNext();)
{
String keyFieldName = (String)i.next();
Object keyFieldValue = null;
if (value != null)
{
keyFieldValue = getFieldValue((ClassType)valueType, value, keyFieldName);
}
generateSimpleFieldValueComparison(rd, keyFieldName, operation, keyFieldValue);
if (i.hasNext())
rd.addConstraint(null, booleanOp, null);
}
}
/**
* Code generation for a comparison of the form
* dbvalue relop dbvalue
* where dbvalue denotes an object in the database such as
* - this
* - the result of a relationship navigation
* - variable access
*/
protected void generateDbValueDbValueComparison(RetrieveDesc leftRD, ClassType leftType, int operation,
RetrieveDesc rightRD, ClassType rightType)
{
int booleanOp = getKeyFieldsComparisonBooleanOp(operation);
// Note, this code assumes that both operands are of class types that have
// the same key fields. Thus take the list of key field names of the left side.
List leftKeyFieldNames = leftType.getKeyFieldNames();
for (Iterator i = leftKeyFieldNames.iterator(); i.hasNext();)
{
String keyFieldName = (String)i.next();
leftRD.addConstraint(keyFieldName, operation, rightRD, keyFieldName);
if (i.hasNext())
leftRD.addConstraint(null, booleanOp, null);
}
}
/**
* Code generation for a comparison of the form
* parameter relop constantValue
*/
protected void generateParameterValueComparison(RetrieveDesc rd,
String paramName,
int operation, Object value)
{
if (value != null)
{
rd.addConstraint(null, RetrieveDesc.OP_VALUE, value);
rd.addConstraint(null, RetrieveDesc.OP_PARAMETER,
paramtab.getParameterInfoForParamName(paramName));
rd.addConstraint(null, operation, null);
}
else if (operation == RetrieveDesc.OP_EQ)
{
rd.addConstraint(null, RetrieveDesc.OP_PARAMETER,
paramtab.getParameterInfoForParamName(paramName));
rd.addConstraint(null, RetrieveDesc.OP_NULL, null);
}
else if (operation == RetrieveDesc.OP_NE)
{
rd.addConstraint(null, RetrieveDesc.OP_PARAMETER,
paramtab.getParameterInfoForParamName(paramName));
rd.addConstraint(null, RetrieveDesc.OP_NOTNULL, null);
}
else
{
errorMsg.fatal(I18NHelper.getMessage(messages, "jqlc.codegeneration.generateparametervaluecomparison.invalidvalue")); //NOI18N
}
}
/**
* Returns the boolean operation used to connect the key field comparison expressions:
* l == r is mapped to l.pk1 == r.pk1 & ... & l.pkn == r.pkn => return &
* l != r is mapped to l.pk1 != r.pk1 | ... | l.pkn != r.pkn => return |
*/
protected int getKeyFieldsComparisonBooleanOp(int operation)
{
switch (operation)
{
case RetrieveDesc.OP_EQ:
return RetrieveDesc.OP_AND;
case RetrieveDesc.OP_NE:
return RetrieveDesc.OP_OR;
}
errorMsg.fatal(I18NHelper.getMessage(messages,
"jqlc.codegeneration.getkeyfieldscomparisonbooleanop.invalidobj", //NOI18N
String.valueOf(operation)));
return 0;
}
/**
* Returns the value of the field access object.field.
* Uses jdoGetField for object of a persistence capable class and reflection otherwise.
*/
protected static Object getFieldValue (ClassType classType, Object object, String fieldName)
{
Object value = null;
FieldInfo fieldInfo = classType.getFieldInfo(fieldName);
if (classType.isPersistenceCapable())
{
PersistenceCapable pc = (PersistenceCapable)object;
int index = fieldInfo.getFieldNumber();
StateManager stateManager = pc.jdoGetStateManager();
if (stateManager != null)
{
// call stateManager.prepareGetField to allow the stateManager
// to mediate the field access
stateManager.prepareGetField(index);
}
value = pc.jdoGetField(index);
}
else
{
// non persistence capable class => use reflection
try
{
value = fieldInfo.getField().get(object);
}
catch (IllegalAccessException e)
{
throw new JDOFatalUserException(
I18NHelper.getMessage(messages, "jqlc.codegeneration.fieldaccess.illegal", //NOI18N
fieldName, (object==null ? "null" : object.toString())), e); //NOI18N
}
}
return value;
}
/**
* This method checks whether the result RetrieveDesc needs a DISTINCT clause or not.
* @param query the query AST
*/
protected void handleDistinct(JQLAST query, boolean distinct)
{
// candidateRD is null in the case of false filter
if (candidateRD == null)
return;
if (distinct)
candidateRD.addResult(RetrieveDesc.OP_DISTINCT, FieldTypeEnumeration.NOT_ENUMERATED);
}
/**
* This method returns true if the specified node is an AST that represensts a value.
* It returns false for CONTAINS/NOT_CONTAINS nodes and boolean operations that include
* only CONTAINS nodes
*/
protected boolean pushesValueOnStack(JQLAST node)
{
switch(node.getType())
{
case CONTAINS:
case NOT_CONTAINS:
return false;
case BAND:
case BOR:
case AND:
case OR:
JQLAST left = (JQLAST)node.getFirstChild();
JQLAST right = (JQLAST)left.getNextSibling();
return pushesValueOnStack(left) || pushesValueOnStack(right);
default:
return true;
}
}
/**
* Create a new RetrieveDesc for the specified classType and
* store this RetrieveDesc in the cache with the specified path expression attached.
* The method wraps the RetrieveDesc in a DebugRetrieveDesc, if debug mode is on.
*/
protected RetrieveDesc createRetrieveDesc(String pathExpr, ClassType classType)
{
RetrieveDesc rd = pm.getRetrieveDesc(classType.getJavaClass());
if (logger.isLoggable(Logger.FINEST))
{
rd = new DebugRetrieveDesc(rd);
logger.finest("LOG_JQLCDumpRD", "create " + JQLAST.getRetrieveDescRepr(rd)); //NOI18N
}
rd2TagMap.put(rd, pathExpr);
rd.setNavigationalId(pathExpr);
return rd;
}
/**
* Wrapper that traces the RetrieveDesc calls
*/
protected static class DebugRetrieveDesc implements RetrieveDesc
{
RetrieveDesc wrapped = null;
DebugRetrieveDesc(RetrieveDesc wrapped)
{
this.wrapped = wrapped;
}
public RetrieveDesc unwrap(RetrieveDesc rd)
{
if (rd instanceof DebugRetrieveDesc)
return ((DebugRetrieveDesc)rd).wrapped;
return rd;
}
// methods from RetrieveDesc
public void addResult(String name, RetrieveDesc desc, boolean projection)
{
if (logger.isLoggable(Logger.FINEST))
logger.finest("LOG_JQLCDumpRD", //NOI18N
JQLAST.getRetrieveDescRepr(this) + ".addResult(" + //NOI18N
name + ", " + JQLAST.getRetrieveDescRepr(desc) + ", " + //NOI18N
projection + ")"); //NOI18N
desc = unwrap(desc);
wrapped.addResult(name, desc, projection);
}
public void addResult(int opCode, int resultType)
{
if (logger.isLoggable(Logger.FINEST))
logger.finest("LOG_JQLCDumpRD", //NOI18N
JQLAST.getRetrieveDescRepr(this) + ".addResult(" + //NOI18N
opCode + ", " + resultType + ")"); //NOI18N
wrapped.addResult(opCode, resultType);
}
public void addConstraint(String name, int operation, Object value)
{
String thirdArgRepr = null;
if (value instanceof RetrieveDesc)
{
RetrieveDesc foreignConstraint = (RetrieveDesc)value;
thirdArgRepr = JQLAST.getRetrieveDescRepr(foreignConstraint);
value = unwrap(foreignConstraint);
}
else
{
thirdArgRepr = (value == null) ? "null" : value.toString();
}
if (logger.isLoggable(Logger.FINEST))
logger.finest("LOG_JQLCDumpRD", //NOI18N
JQLAST.getRetrieveDescRepr(this) + ".addConstraint(" + //NOI18N
name + ", " + operation + ", " + thirdArgRepr + ")"); //NOI18N
wrapped.addConstraint(name, operation, value);
}
public void addConstraint(String name, RetrieveDesc foreignConstraint)
{
if (logger.isLoggable(Logger.FINEST))
logger.finest("LOG_JQLCDumpRD", //NOI18N
JQLAST.getRetrieveDescRepr(this) + ".addConstraint(" + //NOI18N
name + ", " + JQLAST.getRetrieveDescRepr(foreignConstraint) + ")"); //NOI18N
foreignConstraint = unwrap(foreignConstraint);
wrapped.addConstraint(name, foreignConstraint);
}
public void addConstraint(String name, int operator, RetrieveDesc foreignConstraint, String foreignFieldName)
{
if (logger.isLoggable(Logger.FINEST))
logger.finest("LOG_JQLCDumpRD", //NOI18N
JQLAST.getRetrieveDescRepr(this) + ".addConstraint(" + //NOI18N
name + ", " + operator + ", " + JQLAST.getRetrieveDescRepr(foreignConstraint) + //NOI18N
", " + foreignFieldName + ")"); //NOI18N
foreignConstraint = unwrap(foreignConstraint);
wrapped.addConstraint(name, operator, foreignConstraint, foreignFieldName);
}
public void setNavigationalId(Object navigationalId)
{
if (logger.isLoggable(Logger.FINEST))
logger.finest("LOG_JQLCDumpRD", //NOI18N
JQLAST.getRetrieveDescRepr(this) +
".setNavigationalId(" + navigationalId + ")"); //NOI18N
wrapped.setNavigationalId(navigationalId);
}
public void setPrefetchEnabled(boolean prefetchEnabled)
{
if (logger.isLoggable(Logger.FINEST))
logger.finest("LOG_JQLCDumpRD", //NOI18N
JQLAST.getRetrieveDescRepr(this) +
".setPrefetchEnabled(" + prefetchEnabled + ")"); //NOI18N
wrapped.setPrefetchEnabled(prefetchEnabled);
}
// Methods from ActionDesc
public Class getPersistenceCapableClass()
{ return wrapped.getPersistenceCapableClass(); }
}
}
// rules
query
: q:.
{
prepareRetrieveDescs(q);
if (logger.isLoggable(Logger.FINEST))
logger.finest("LOG_JQLCDumpTree", q.getTreeRepr("RD annotated AST")); //NOI18N
doCodeGen(q);
}
;
doCodeGen
{
boolean distinct = false;
}
: #( q:QUERY
candidateClass
parameters
variables
ordering
distinct = result
filter
)
{
handleDistinct(q, distinct);
}
;
// ----------------------------------
// rules: candidate class
// ----------------------------------
candidateClass
{
errorMsg.setContext("setCandidates"); //NOI18N
}
: c:CLASS_DEF
// Note, DISTINCT is added by handleDistinct called in the rule query
;
// ----------------------------------
// rules: parameter declaration
// ----------------------------------
parameters
{
errorMsg.setContext("declareParameters"); //NOI18N
}
: ( declareParameter )*
;
declareParameter
: #( PARAMETER_DEF type IDENT )
;
// ----------------------------------
// rules: variable declaration
// ----------------------------------
variables
{
errorMsg.setContext("declareVariables"); //NOI18N
}
: ( declareVariable )*
;
declareVariable
: #( VARIABLE_DEF type i:IDENT )
;
// ----------------------------------
// rules: ordering specification
// ----------------------------------
ordering
{
errorMsg.setContext("setOrdering"); //NOI18N
}
: ( orderSpec )*
;
orderSpec
{
int op = 0;
}
: #( ORDERING_DEF
( ASCENDING { op = RetrieveDesc.OP_ORDERBY; }
| DESCENDING { op = RetrieveDesc.OP_ORDERBY_DESC; }
)
orderingExpr[op]
)
;
orderingExpr [int op]
: ( #( FIELD_ACCESS expression IDENT) )=> #( f:FIELD_ACCESS expression i:IDENT )
{
f.getRetrieveDesc().addConstraint(i.getText(), op, null);
}
| e:.
{
errorMsg.unsupported(e.getLine(), e.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.codegeneration.generic.unsupportedop", // NOI18N
e.getText()));
}
;
// ----------------------------------
// rules: result expression
// ----------------------------------
result returns [boolean distinct]
{
errorMsg.setContext("setResult"); //NOI18N
distinct = false;
}
: #( RESULT_DEF
distinct = resultExpr[true]
)
| {
// no result is equivalent to setResult("distinct this") =>
// distinct is true
distinct = true;
}
;
resultExpr [boolean outer] returns [boolean distinct]
{
String name = null;
// this should be take care at first level of recursion
distinct = false;
boolean tmp;
}
: #( d:DISTINCT tmp = resultExpr[outer] )
{
distinct = true;
}
| #( avg:AVG distinct = resultExpr[true] )
{
candidateRD.addResult(RetrieveDesc.OP_AVG,
avg.getJQLType().getEnumType());
}
| #( max:MAX distinct = resultExpr[true] )
{
candidateRD.addResult(RetrieveDesc.OP_MAX,
max.getJQLType().getEnumType());
}
| #( min:MIN distinct = resultExpr[true] )
{
candidateRD.addResult(RetrieveDesc.OP_MIN,
min.getJQLType().getEnumType());
}
| #( sum:SUM distinct = resultExpr[true] )
{
candidateRD.addResult(RetrieveDesc.OP_SUM,
sum.getJQLType().getEnumType());
}
| #( count:COUNT distinct = r:resultExpr[true] )
{
Type resultType = r.getJQLType();
if (typetab.isPersistenceCapableType(resultType)) {
List pkfields = ((ClassType)resultType).getKeyFieldNames();
if (pkfields != null) {
candidateRD.addResult(RetrieveDesc.OP_COUNT_PC,
count.getJQLType().getEnumType());
} else {
errorMsg.unsupported(r.getLine(), r.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.codegeneration.resultexpr.missingpkfields", // NOI18N
resultType.getName()));
}
} else {
candidateRD.addResult(RetrieveDesc.OP_COUNT,
count.getJQLType().getEnumType());
}
}
| #( op1:FIELD_ACCESS tmp = expr1:resultExpr[false] i1:IDENT )
{
op1.getRetrieveDesc().addResult(i1.getText(), null, true);
}
| #( op2:NAVIGATION tmp = expr2:resultExpr[false] i2:IDENT )
{
RetrieveDesc from = expr2.getRetrieveDesc();
RetrieveDesc to = op2.getRetrieveDesc();
from.addResult(i2.getText(), to, outer);
}
| #( op3:VARIABLE ( name = col3:collectionExprResult )? )
{
if (col3 != null) {
RetrieveDesc from = col3.getRetrieveDesc();
RetrieveDesc to = op3.getRetrieveDesc();
from.addResult(name, to, outer);
}
}
| THIS
;
collectionExprResult returns [String fieldName]
{
fieldName = null;
boolean tmp;
}
: #( FIELD_ACCESS tmp = resultExpr[false] name1:IDENT )
{ fieldName = name1.getText(); }
| #( NAVIGATION tmp = resultExpr[false] name2:IDENT )
{ fieldName = name2.getText(); }
| #( TYPECAST . fieldName = collectionExprResult )
| #( NOT_IN fieldName = collectionExprResult )
;
// ----------------------------------
// rules: filer expression
//
// NOTE: the code generator traverses operands of binary operations in reverse order.
// The reason is that the RetrieveDesc processes the constriant stack in a LIFO way.
// This means, the code generator has to process the right operand first,
// then the left operand and finally the operation.
// ----------------------------------
filter
{
errorMsg.setContext("setFilter"); //NOI18N
}
: #( FILTER_DEF expr:. )
{
switch (expr.getType()) {
case VALUE:
// constant filter
Object value = expr.getValue();
if (value instanceof Boolean)
{
// Note, in the case of a true filter do not add
// any constraints to the candidateRD
if (!((Boolean)value).booleanValue())
{
// false filter => unset candidateRD
candidateRD = null;
}
}
else
{
errorMsg.fatal(I18NHelper.getMessage(messages,
"jqlc.codegeneration.filter.nonbooleanvalue", //NOI18N
String.valueOf(value)));
}
break;
case FIELD_ACCESS:
// The entire filter consists of a boolean field only.
// Map this to 'booleanField <> FALSE'. Note, the runtime will
// create a JDBC parameter for the literal FALSE and call
// setBoolean to bind the value.
RetrieveDesc rd = expr.getRetrieveDesc();
rd.addConstraint(null, RetrieveDesc.OP_VALUE, Boolean.FALSE);
expression(expr);
rd.addConstraint(null, RetrieveDesc.OP_NE, null);
break;
default:
expression(expr);
break;
}
}
;
expression
: ( primary )=> primary
| bitwiseExpr
| conditionalExpr
| relationalExpr
| binaryArithmeticExpr
| unaryArithmeticExpr
| complementExpr
;
// This rule transforms an access expression of a boolean field into an
// equal operation: expr == true.
booleanOperationArgument
: e:expression
{
if (#e.getType() == FIELD_ACCESS) {
RetrieveDesc rd = #e.getRetrieveDesc();
rd.addConstraint(null, RetrieveDesc.OP_VALUE, Boolean.TRUE);
rd.addConstraint(null, RetrieveDesc.OP_EQ, null);
}
}
;
bitwiseExpr
: #( op1:BAND left1:. right1:booleanOperationArgument )
{
booleanOperationArgument(left1);
// do not generate boolean operation if one of the operands is variable constraint
if (pushesValueOnStack(left1) && pushesValueOnStack(right1))
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_AND, null);
}
| #( op2:BOR left2:. right2:booleanOperationArgument )
{
booleanOperationArgument(left2);
// do not generate boolean operation if one of the operands is variable constraint
if (pushesValueOnStack(left2) && pushesValueOnStack(right2))
op2.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_OR, null);
}
| #( op3:BXOR left3:. right3:booleanOperationArgument )
{
booleanOperationArgument(left3);
errorMsg.unsupported(op3.getLine(), op3.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.codegeneration.generic.unsupportedop", // NOI18N
op3.getText()));
}
;
conditionalExpr
: #( op1:AND left1:. right1:booleanOperationArgument )
{
booleanOperationArgument(left1);
// do not generate boolean operation if one of the operands is variable constraint
if (pushesValueOnStack(left1) && pushesValueOnStack(right1))
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_AND, null);
}
| #( op2:OR left2:. right2:booleanOperationArgument )
{
booleanOperationArgument(left2);
// do not generate boolean operation if one of the operands is variable constraint
if (pushesValueOnStack(left2) && pushesValueOnStack(right2))
op2.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_OR, null);
}
;
relationalExpr
: ( fieldComparison )=> fieldComparison
| ( objectComparison )=> objectComparison
| ( collectionComparison )=> collectionComparison
| ( parameterComparison )=> parameterComparison
| #( op1:EQUAL left1:. expression )
{
expression(left1);
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_EQ, null);
}
| #( op2:NOT_EQUAL left2:. expression )
{
expression(left2);
op2.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_NE, null);
}
| #( op3:LT left3:. expression )
{
expression(left3);
op3.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_LT, null);
}
| #( op4:GT left4:. expression )
{
expression(left4);
op4.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_GT, null);
}
| #( op5:LE left5:. expression )
{
expression(left5);
op5.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_LE, null);
}
| #( op6:GE left6:. expression )
{
expression(left6);
op6.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_GE, null);
}
;
fieldComparison
: #(EQUAL fieldComparisonOperands[RetrieveDesc.OP_EQ] )
| #(NOT_EQUAL fieldComparisonOperands[RetrieveDesc.OP_NE] )
| #(LT fieldComparisonOperands[RetrieveDesc.OP_LT] )
| #(LE fieldComparisonOperands[RetrieveDesc.OP_LE] )
| #(GT fieldComparisonOperands[RetrieveDesc.OP_GT] )
| #(GE fieldComparisonOperands[RetrieveDesc.OP_GE] )
;
fieldComparisonOperands [int operation]
{
String leftName = null;
String rightName = null;
Object value = null;
}
: value = constantValue rightName = f1:fieldAccess
{
// case constant relop field
generateSimpleFieldValueComparison(f1.getRetrieveDesc(), rightName,
getSwappedOp(operation), value);
}
| p1:PARAMETER rightName = f2:fieldAccess
{
// case parameter relop field
// Support for fixed-width char pk columns
f2.getRetrieveDesc().addConstraint(rightName, RetrieveDesc.OP_FIELD, null);
f2.getRetrieveDesc().addConstraint(null,
RetrieveDesc.OP_PARAMETER,
paramtab.getParameterInfoForParamName(p1.getText(), rightName));
f2.getRetrieveDesc().addConstraint(null, operation, null);
}
| leftName = f3:fieldAccess
( value = constantValue
{
// case field relop constant
generateSimpleFieldValueComparison(f3.getRetrieveDesc(), leftName,
operation, value);
}
| rightName = f4:fieldAccess
{
// case field relop field
f3.getRetrieveDesc().addConstraint(leftName, operation,
f4.getRetrieveDesc(), rightName);
}
| p2:PARAMETER
{
// case field relop parameter
// Support for fixed-width char pk columns
f3.getRetrieveDesc().addConstraint(null,
RetrieveDesc.OP_PARAMETER,
paramtab.getParameterInfoForParamName(p2.getText(), leftName));
f3.getRetrieveDesc().addConstraint(leftName, RetrieveDesc.OP_FIELD, null);
f3.getRetrieveDesc().addConstraint(null, operation, null);
}
)
;
objectComparison
{
Object value = null;
}
: #( OBJECT_EQUAL objectComparisonOperands[RetrieveDesc.OP_EQ] )
| #( OBJECT_NOT_EQUAL objectComparisonOperands[RetrieveDesc.OP_NE] )
;
objectComparisonOperands [int operation]
{
Object value = null;
}
: value = v1:constantValue d1:dbValue
// case constant relop dbvalue
{
if ((value == null) && (d1.getType() == NAVIGATION))
{
JQLAST expr = (JQLAST)d1.getFirstChild();
JQLAST ident = (JQLAST)expr.getNextSibling();
// now handle navigation source
expression(expr);
// now generate IS NULL constraint
generateSimpleFieldValueComparison(expr.getRetrieveDesc(), ident.getText(),
getSwappedOp(operation), value);
}
else
{
if (d1.getType() == NAVIGATION) navigation(d1);
generateDbValueConstantComparison(d1.getRetrieveDesc(), (ClassType)d1.getJQLType(),
getSwappedOp(operation), value, v1.getJQLType());
}
}
| d2:dbValue
( value = v2:constantValue
// case dbvalue relop constant
{
if ((value == null) && (d2.getType() == NAVIGATION))
{
JQLAST expr = (JQLAST)d2.getFirstChild();
JQLAST ident = (JQLAST)expr.getNextSibling();
// now handle navigation source
expression(expr);
// now generate IS NULL constraint
generateSimpleFieldValueComparison(expr.getRetrieveDesc(), ident.getText(),
operation, value);
}
else
{
if (d2.getType() == NAVIGATION) navigation(d2);
generateDbValueConstantComparison(d2.getRetrieveDesc(),
(ClassType)d2.getJQLType(),
operation, value, v2.getJQLType());
}
}
| d3:dbValue
// case dbvalue relop dbvalue
{
if (d2.getType() == NAVIGATION) navigation(d2);
if (d3.getType() == NAVIGATION) navigation(d3);
generateDbValueDbValueComparison(d2.getRetrieveDesc(),
(ClassType)d2.getJQLType(),
operation,
d3.getRetrieveDesc(),
(ClassType)d3.getJQLType());
}
)
;
parameterComparison
: #(EQUAL parameterComparisonOperands[RetrieveDesc.OP_EQ] )
| #(NOT_EQUAL parameterComparisonOperands[RetrieveDesc.OP_NE] )
| #(OBJECT_EQUAL parameterComparisonOperands[RetrieveDesc.OP_EQ] )
| #(OBJECT_NOT_EQUAL parameterComparisonOperands[RetrieveDesc.OP_NE] )
;
parameterComparisonOperands [int operation]
{
Object value = null;
}
: p1:PARAMETER value = v1:constantValue
{
generateParameterValueComparison(v1.getRetrieveDesc(), p1.getText(),
operation, value);
}
| value = v2:constantValue p2:PARAMETER
{
generateParameterValueComparison(v2.getRetrieveDesc(), p2.getText(),
operation, value);
}
;
dbValue
{
String name = null;
}
: THIS
| variableAccess
| #( NAVIGATION . IDENT )
// do not use non-terminal navigation here, because navigation
// creates a RetrieveDesc for the relationship navigation and
// we must not create this in the case of relship == null
;
collectionComparison
: #( eq:COLLECTION_EQUAL . . )
{
errorMsg.unsupported(eq.getLine(), eq.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.codegeneration.collectioncomparison.nonnull")); // NOI18N
}
| #( ne:COLLECTION_NOT_EQUAL . . )
{
errorMsg.unsupported(ne.getLine(), ne.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.codegeneration.collectioncomparison.nonnull")); // NOI18N
}
;
binaryArithmeticExpr
: #( op1:PLUS left1:. right1:. )
{
// Optimize indexOf + <intValue>:
// The SQL database returns an index starting with 1, so we need
// to decrement the returned index. We can do the derement at compile
// timeCombine, if the other operand is a constant int value.
if ((left1.getType() == INDEXOF) &&
(right1.getType() == VALUE) &&
(right1.getValue() instanceof Integer))
{
// case: indexOf() + intValue
indexOf(left1, ((Integer)right1.getValue()).intValue());
}
else if ((right1.getType() == INDEXOF) &&
(left1.getType() == VALUE) &&
(left1.getValue() instanceof Integer))
{
// case: intValue + indexOf()
indexOf(right1, ((Integer)left1.getValue()).intValue());
}
else
{
expression(right1);
expression(left1);
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_ADD, null);
}
}
| #( op2:CONCAT left2:. expression )
{
expression(left2);
op2.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_CONCAT, null);
}
| #( op3:MINUS left3:. right3:. )
{
// Optimize indexOf + <intValue>:
// The SQL database returns an index starting with 1, so we need
// to decrement the returned index. We can do the derement at compile
// timeCombine, if the other operand is a constant int value.
if ((left3.getType() == INDEXOF) &&
(right3.getType() == VALUE) &&
(right3.getValue() instanceof Integer))
{
// case: indexOf - intValue
// treated as indexOf + -intValue
indexOf(left3, -((Integer)right3.getValue()).intValue());
}
else
{
expression(right3);
expression(left3);
op3.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_SUB, null);
}
}
| #( op4:STAR left4:. expression )
{
expression(left4);
op4.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_MUL, null);
}
| #( op5:DIV left5:. expression )
{
expression(left5);
op5.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_DIV, null);
}
| #( op6:MOD left6:. expression )
{
expression(left6);
op6.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_MOD, null);
}
;
unaryArithmeticExpr
{
Object value = null;
}
: #( UNARY_PLUS expression )
// no action needed, just ignore the unary plus
| #( op2:UNARY_MINUS
(
( constantValue )=> value = constantValue
{
value = negate(value, op2.getJQLType());
op2.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE, value);
}
|
expression
{
// map -value to 0 - value
op2.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE,
getZeroValue(op2.getJQLType()));
op2.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_SUB, null);
}
)
)
;
complementExpr
: #( op1:BNOT expression )
{
// map ~value to -1 - value (which is equivalent to (-value)-1)
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE,
getMinusOneValue(op1.getJQLType()));
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_SUB, null);
}
| #( op2:LNOT expr:. )
{ if (expr.getType() == FIELD_ACCESS) {
// The NOT operand is a boolean field.
// Map this to 'booleanField = FALSE'. Note, the runtime will
// create a JDBC parameter for the literal FALSE and call
// setBoolean to bind the value.
RetrieveDesc rd = op2.getRetrieveDesc();
rd.addConstraint(null, RetrieveDesc.OP_VALUE, Boolean.FALSE);
expression(expr);
rd.addConstraint(null, RetrieveDesc.OP_EQ, null);
}
else {
expression(expr);
op2.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_NOT, null);
}
}
;
primary
{
Object value;
String name;
}
: #( TYPECAST type expression )
{ /* code gen for cast? */ }
| value = v:constantValue
{
if (value == null)
{
errorMsg.fatal(I18NHelper.getMessage(messages, "jqlc.codegeneration.primary.null")); //NOI18N
}
else if (value instanceof Boolean)
{
boolean booleanValue = ((Boolean)value).booleanValue();
RetrieveDesc rd = v.getRetrieveDesc();
rd.addConstraint(null, RetrieveDesc.OP_VALUE, new Integer(0));
rd.addConstraint(null, RetrieveDesc.OP_VALUE, new Integer(0));
rd.addConstraint(null, (booleanValue?RetrieveDesc.OP_EQ:RetrieveDesc.OP_NE), null);
}
else
{
v.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE, value);
}
}
| p:PARAMETER
{
p.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_PARAMETER,
paramtab.getParameterInfoForParamName(p.getText()));
}
| THIS
| name = f:fieldAccess
{
f.getRetrieveDesc().addConstraint(name, RetrieveDesc.OP_FIELD, null);
}
| navigation
| variableAccess
| #( CONTAINS . VARIABLE )
// code moved to variable access
| #( NOT_CONTAINS . VARIABLE )
// code moved to variable access
| startsWith
| endsWith
| isEmpty
| like
| substring
| indexOf[0]
| length
| abs
| sqrt
;
constantValue returns [Object value]
{
value = null;
}
: v:VALUE
{
value = v.getValue();
}
;
fieldAccess returns [String fieldName]
{
fieldName = null;
}
: #( FIELD_ACCESS expression name:IDENT )
{ fieldName = name.getText(); }
;
navigation
: #( n:NAVIGATION expr:expression i:IDENT )
{
RetrieveDesc from = expr.getRetrieveDesc();
RetrieveDesc to = n.getRetrieveDesc();
from.addConstraint(i.getText(), to);
}
;
variableAccess
{
String name = null;
}
: #( var:VARIABLE ( name = col:collectionExpr )? )
{
RetrieveDesc varRD = var.getRetrieveDesc();
if (!boundRetrieveDescs.contains(varRD))
{
if (col != null)
{
if (col.getType() == NOT_IN)
col.getRetrieveDesc().addConstraint(name, RetrieveDesc.OP_NOTIN, varRD);
else if (USE_IN)
// generate OP_IN if USE_IN property is set
col.getRetrieveDesc().addConstraint(name, RetrieveDesc.OP_IN, varRD);
else
// otherwise generate regular join
col.getRetrieveDesc().addConstraint(name, varRD);
}
else
{
candidateRD.addConstraint(null, varRD);
}
boundRetrieveDescs.add(varRD);
}
}
;
collectionExpr returns [String fieldName]
{
fieldName = null;
}
: #( FIELD_ACCESS expression name1:IDENT )
{ fieldName = name1.getText(); }
| #( NAVIGATION expression name2:IDENT )
{ fieldName = name2.getText(); }
| #( TYPECAST . fieldName = collectionExpr )
| #( NOT_IN fieldName = collectionExpr )
;
startsWith
{
Object value = null;
JQLAST pattern = null;
}
: #( op1:STARTS_WITH string:.
{
// I need to store a pointer to the second operand of startsWith here.
// See second alternative below.
pattern = (JQLAST)string.getNextSibling();
}
(
( constantValue )=> value = constantValue
{
if (string.getType() == FIELD_ACCESS)
{
// case 1 fieldAccess constantValue
String fieldName = fieldAccess(string);
op1.getRetrieveDesc().addConstraint(fieldName, RetrieveDesc.OP_LIKE,
((String)value) + WILDCARD_PATTERN);
}
else
{
// case 2 expression constantValue
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE,
((String)value) + WILDCARD_PATTERN);
expression(string);
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_LIKE, null);
}
}
| {
// I have to access the tree matched by rule expression before
// the rule is entered. Variable pattern points to that tree and
// needs to be initilaized before!
pattern.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE,
WILDCARD_PATTERN);
}
expression
{
// case 3 expression expression
pattern.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_CONCAT, null);
expression(string);
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_LIKE, null);
}
)
)
;
endsWith
{
Object value = null;
}
: #( op1:ENDS_WITH string:.
(
( constantValue )=> value = constantValue
{
if (string.getType() == FIELD_ACCESS)
{
// case 1 fieldAccess constantValue
String fieldName = fieldAccess(string);
op1.getRetrieveDesc().addConstraint(fieldName, RetrieveDesc.OP_LIKE,
WILDCARD_PATTERN + ((String)value));
}
else
{
// case 2 expression constantValue
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE,
WILDCARD_PATTERN + ((String)value));
expression(string);
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_LIKE, null);
}
}
| pattern:expression
{
// case 3 expression expression
pattern.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE,
WILDCARD_PATTERN);
pattern.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_CONCAT, null);
expression(string);
op1.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_LIKE, null);
}
)
)
;
isEmpty
{
String name = null;
}
: #(op:IS_EMPTY name = collectionExpr)
{
op.getRetrieveDesc().addConstraint(name, RetrieveDesc.OP_NULL, null);
}
;
like
{
int opCode = RetrieveDesc.OP_LIKE;
}
: #( op:LIKE string:. pattern:. opCode = escape )
{
expression(pattern);
expression(string);
op.getRetrieveDesc().addConstraint(null, opCode, null);
}
;
escape returns [int opCode]
{
// The default is no ESCAPE definition => OP_LIKE
opCode = RetrieveDesc.OP_LIKE;
}
: expression
{
opCode = RetrieveDesc.OP_LIKE_ESCAPE;
}
| // empty rule
;
substring
: // JDOQL: string.substring(begin, end) ->
// RetrieveDesc: SUBSTRING(string, begin + 1, end - begin)
#( op:SUBSTRING string:. begin:. end:. )
{
RetrieveDesc rd = op.getRetrieveDesc();
if ((begin.getType() == VALUE) && (end.getType() == VALUE))
{
// Optimization: begin and end are constant values =>
// calculate start and length of SQL SUBSTRING function
// at compile time.
// Note, Semantic ensures begin and end are int or Integer values.
int beginValue = (begin.getValue() != null) ?
((Integer)begin.getValue()).intValue() : 0;
int endValue = (end.getValue() != null) ?
((Integer)end.getValue()).intValue() : 0;
if (beginValue < 0)
{
errorMsg.error(begin.getLine(), begin.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.codegeneration.substring.beginnegative", // NOI18N
String.valueOf(beginValue)));
}
else if (endValue < beginValue)
{
errorMsg.error(op.getLine(), op.getColumn(),
I18NHelper.getMessage(messages,
"jqlc.codegeneration.substring.beginlargerend", // NOI18N
String.valueOf(beginValue), String.valueOf(endValue)));
}
// SQL length = end - begin
rd.addConstraint(null, RetrieveDesc.OP_VALUE,
new Integer(endValue-beginValue));
// SQL start index = begin + 1
rd.addConstraint(null, RetrieveDesc.OP_VALUE,
new Integer(beginValue+1));
}
else
{
// At least one of begin or end is a non constant value =>
// generate the arguments start and length of the SQL SUBSTRING
// function as binary plus/minus expressions.
// The next 3 line denote the SQL length = end - begin
expression(begin);
expression(end);
rd.addConstraint(null, RetrieveDesc.OP_SUB, null);
// The next 3 lines denote the SQL start index = begin + 1
rd.addConstraint(null, RetrieveDesc.OP_VALUE, new Integer(1));
expression(begin);
rd.addConstraint(null, RetrieveDesc.OP_ADD, null);
}
// now push the string on the constraint stack
expression(string);
rd.addConstraint(null, RetrieveDesc.OP_SUBSTRING, null);
}
;
// incr denotes the value that need to be added to result of POSITION
indexOf [int incr]
{
int opCode = RetrieveDesc.OP_POSITION;
}
: // JDOQL: string.indexOf(pattern) ->
// RetrieveDesc: POSITION(string, pattern) - 1
// JDOQL: string.indexOf(pattern, begin) ->
// RetrieveDesc: POSITION_START(string, pattern, begin + 1) - 1
#( op:INDEXOF string:. pattern:. opCode = fromIndex )
{
RetrieveDesc rd = op.getRetrieveDesc();
// the 3 lines denote the SQL function POSITION OR POSITION_START
expression(pattern);
expression(string);
rd.addConstraint(null, opCode, null);
// SQL handles indexes starting from 1 =>
// decrement the returned value to make it Java like!
incr--;
if (incr != 0)
{
rd.addConstraint(null, RetrieveDesc.OP_VALUE, new Integer(incr));
rd.addConstraint(null, RetrieveDesc.OP_ADD, null);
}
}
;
fromIndex returns [int opCode]
{
// The default is no start definition => OP_POSITION
opCode = RetrieveDesc.OP_POSITION;
}
: e:.
{
opCode = RetrieveDesc.OP_POSITION_START;
// Java indexOf method use indexes starting with 0,
// where SQL starts with 1, so we need to add 1
if (e.getType() == VALUE)
{
// Optimization: calulate index at compile time,
// if start is a constant value.
// Note, Semantic ensures begin and end are int or Integer values.
int value = (e.getValue() != null) ?
((Integer)e.getValue()).intValue() : 0;
e.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE,
new Integer(value + 1));
}
else
{
e.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_VALUE,
new Integer(1));
expression(e);
e.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_ADD, null);
}
}
| // empty rule
;
length
: #( op:LENGTH expression )
{
op.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_LENGTH, null);
}
;
abs
: #( op:ABS expression )
{
op.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_ABS, null);
}
;
sqrt
: #( op:SQRT expression )
{
op.getRetrieveDesc().addConstraint(null, RetrieveDesc.OP_SQRT, null);
}
;
type
: TYPENAME
| primitiveType
;
primitiveType
: BOOLEAN
| BYTE
| CHAR
| SHORT
| INT
| FLOAT
| LONG
| DOUBLE
;
// ----------------------------------
// rules: RetrieveDesc handling
// ----------------------------------
prepareRetrieveDescs
{
Map usedRD = new HashMap();
}
: #( q:QUERY
checkRetrieveDesc[usedRD] // candidate class
( #( PARAMETER_DEF . . ) )*
( #( VARIABLE_DEF . . ) )*
( #( ORDERING_DEF
( ASCENDING | DESCENDING )
ordering:checkRetrieveDesc[usedRD]
{ propagateRetrieveDesc(ordering, candidateRD); }
)
)*
( #( RESULT_DEF result:checkRetrieveDesc[usedRD]
{ propagateRetrieveDesc(result, candidateRD); }
)
)?
#( FILTER_DEF
filter:checkRetrieveDesc[usedRD]
{ propagateRetrieveDesc(filter, candidateRD); }
)
)
;
checkRetrieveDesc [Map usedRD]
: c:CLASS_DEF
{
// check persistence capable
ClassType candidateClass = (ClassType)c.getJQLType();
candidateRD = createRetrieveDesc("this", candidateClass); //NOI18N
candidateRD.setPrefetchEnabled(prefetchEnabled);
}
| #( cast:TYPECAST type expr1:checkRetrieveDesc[usedRD] )
{
cast.setRetrieveDesc(expr1.getRetrieveDesc());
}
// constantValue not necessary here, this is covered by the last rule
| t:THIS
{
t.setRetrieveDesc(candidateRD);
}
| #(var:VARIABLE ( checkRetrieveDesc[usedRD] )? )
{
RetrieveDesc to = (RetrieveDesc)usedRD.get(var.getText());
if (to == null)
{
to = createRetrieveDesc(var.getText(), (ClassType)var.getJQLType());
usedRD.put(var.getText(), to);
}
var.setRetrieveDesc(to);
}
| #(notIn:NOT_IN notInArg:checkRetrieveDesc[usedRD])
{
#notIn.setRetrieveDesc(#notInArg.getRetrieveDesc());
}
| #(fa:FIELD_ACCESS expr4:checkRetrieveDesc[usedRD] i:IDENT)
{
fa.setRetrieveDesc(expr4.getRetrieveDesc());
i.setRetrieveDesc(expr4.getRetrieveDesc());
}
| #(n:NAVIGATION checkRetrieveDesc[usedRD] IDENT)
{
RetrieveDesc to = (RetrieveDesc)usedRD.get(n.getText());
if (to == null)
{
to = createRetrieveDesc(n.getText(), (ClassType)n.getJQLType());
usedRD.put(n.getText(), to);
}
n.setRetrieveDesc(to);
}
| #(CONTAINS checkRetrieveDesc[usedRD] checkRetrieveDesc[usedRD])
| #(NOT_CONTAINS checkRetrieveDesc[usedRD] checkRetrieveDesc[usedRD])
| #(sw:STARTS_WITH expr7:checkRetrieveDesc[usedRD] checkRetrieveDesc[usedRD])
{
sw.setRetrieveDesc(expr7.getRetrieveDesc());
}
| #(ew:ENDS_WITH expr8:checkRetrieveDesc[usedRD] checkRetrieveDesc[usedRD])
{
ew.setRetrieveDesc(expr8.getRetrieveDesc());
}
| #(ie:IS_EMPTY expr9:checkRetrieveDesc[usedRD])
{
ie.setRetrieveDesc(expr9.getRetrieveDesc());
}
| #(like:LIKE string10:checkRetrieveDesc[usedRD]
pattern10:checkRetrieveDesc[usedRD] ( escape10:checkRetrieveDesc[usedRD] )? )
{
like.setRetrieveDesc(getCommonRetrieveDesc(string10, pattern10, escape10));
}
| #(substr:SUBSTRING string11:checkRetrieveDesc[usedRD]
lower11:checkRetrieveDesc[usedRD] upper11:checkRetrieveDesc[usedRD] )
{
substr.setRetrieveDesc(getCommonRetrieveDesc(string11, lower11, upper11));
}
| #(indexOf:INDEXOF string12:checkRetrieveDesc[usedRD]
pattern12:checkRetrieveDesc[usedRD] ( start12:checkRetrieveDesc[usedRD] )? )
{
indexOf.setRetrieveDesc(getCommonRetrieveDesc(string12, pattern12, start12));
}
| #(len:LENGTH expr13:checkRetrieveDesc[usedRD])
{
len.setRetrieveDesc(expr13.getRetrieveDesc());
}
| #(abs:ABS expr14:checkRetrieveDesc[usedRD])
{
abs.setRetrieveDesc(expr14.getRetrieveDesc());
}
| #(sqrt:SQRT expr15:checkRetrieveDesc[usedRD])
{
sqrt.setRetrieveDesc(expr15.getRetrieveDesc());
}
// binary operations
| #( op1:BAND left1:. right1:. )
{ checkAndOpRetrieveDesc(op1, left1, right1, usedRD); }
| #( op2:BOR left2:checkRetrieveDesc[new HashMap(usedRD)]
right2:checkRetrieveDesc[new HashMap(usedRD)] )
{ op2.setRetrieveDesc(getCommonRetrieveDesc(left2, right2)); }
| #( op3:BXOR left3:checkRetrieveDesc[new HashMap()]
right3:checkRetrieveDesc[new HashMap()] )
{ op3.setRetrieveDesc(getCommonRetrieveDesc(left3, right3)); }
| #( op4:AND left4:. right4:. )
{ checkAndOpRetrieveDesc(op4, left4, right4, usedRD); }
| #( op5:OR left5:checkRetrieveDesc[new HashMap(usedRD)]
right5:checkRetrieveDesc[new HashMap(usedRD)] )
{ op5.setRetrieveDesc(getCommonRetrieveDesc(left5, right5)); }
| #( op6:EQUAL left6:checkRetrieveDesc[usedRD] right6:checkRetrieveDesc[usedRD] )
{ op6.setRetrieveDesc(getCommonRetrieveDesc(left6, right6)); }
| #( op7:NOT_EQUAL left7:checkRetrieveDesc[usedRD] right7:checkRetrieveDesc[usedRD] )
{ op7.setRetrieveDesc(getCommonRetrieveDesc(left7, right7)); }
| #( op8:LT left8:checkRetrieveDesc[usedRD] right8:checkRetrieveDesc[usedRD] )
{ op8.setRetrieveDesc(getCommonRetrieveDesc(left8, right8)); }
| #( op9:GT left9:checkRetrieveDesc[usedRD] right9:checkRetrieveDesc[usedRD] )
{ op9.setRetrieveDesc(getCommonRetrieveDesc(left9, right9)); }
| #( op10:LE left10:checkRetrieveDesc[usedRD] right10:checkRetrieveDesc[usedRD] )
{ op10.setRetrieveDesc(getCommonRetrieveDesc(left10, right10)); }
| #( op11:GE left11:checkRetrieveDesc[usedRD] right11:checkRetrieveDesc[usedRD] )
{ op11.setRetrieveDesc(getCommonRetrieveDesc(left11, right11)); }
| #( op12:OBJECT_EQUAL left12:checkRetrieveDesc[usedRD] right12:checkRetrieveDesc[usedRD] )
{ op12.setRetrieveDesc(getObjectComparisonRetrieveDesc(left12, right12)); }
| #( op13:OBJECT_NOT_EQUAL left13:checkRetrieveDesc[usedRD] right13:checkRetrieveDesc[usedRD] )
{ op13.setRetrieveDesc(getObjectComparisonRetrieveDesc(left13, right13)); }
| #( op14:COLLECTION_EQUAL left14:checkRetrieveDesc[usedRD] right14:checkRetrieveDesc[usedRD] )
{ op14.setRetrieveDesc(getCommonRetrieveDesc(left14, right14)); }
| #( op15:COLLECTION_NOT_EQUAL left15:checkRetrieveDesc[usedRD] right15:checkRetrieveDesc[usedRD] )
{ op15.setRetrieveDesc(getCommonRetrieveDesc(left15, right15)); }
| #( op16:PLUS left16:checkRetrieveDesc[usedRD] right16:checkRetrieveDesc[usedRD] )
{ op16.setRetrieveDesc(getCommonRetrieveDesc(left16, right16)); }
| #( op17:CONCAT left17:checkRetrieveDesc[usedRD] right17:checkRetrieveDesc[usedRD] )
{ op17.setRetrieveDesc(getCommonRetrieveDesc(left17, right17)); }
| #( op18:MINUS left18:checkRetrieveDesc[usedRD] right18:checkRetrieveDesc[usedRD] )
{ op18.setRetrieveDesc(getCommonRetrieveDesc(left18, right18)); }
| #( op19:STAR left19:checkRetrieveDesc[usedRD] right19:checkRetrieveDesc[usedRD] )
{ op19.setRetrieveDesc(getCommonRetrieveDesc(left19, right19)); }
| #( op20:DIV left20:checkRetrieveDesc[usedRD] right20:checkRetrieveDesc[usedRD] )
{ op20.setRetrieveDesc(getCommonRetrieveDesc(left20, right20)); }
| #( op21:MOD left21:checkRetrieveDesc[usedRD] right21:checkRetrieveDesc[usedRD] )
{ op21.setRetrieveDesc(getCommonRetrieveDesc(left21, right21)); }
// unary operations
| #( uop1:UNARY_PLUS arg1:checkRetrieveDesc[usedRD] )
{ uop1.setRetrieveDesc(arg1.getRetrieveDesc()); }
| #( uop2:UNARY_MINUS arg2:checkRetrieveDesc[usedRD] )
{ uop2.setRetrieveDesc(arg2.getRetrieveDesc()); }
| #( uop3:BNOT arg3:checkRetrieveDesc[usedRD] )
{ uop3.setRetrieveDesc(arg3.getRetrieveDesc()); }
| #( uop4:LNOT arg4:checkRetrieveDesc[usedRD] )
{ uop4.setRetrieveDesc(arg4.getRetrieveDesc()); }
| #( d:DISTINCT arg5:checkRetrieveDesc[usedRD] )
{ d.setRetrieveDesc(arg5.getRetrieveDesc()); }
| #( avg:AVG arg6:checkRetrieveDesc[usedRD] )
{ avg.setRetrieveDesc(arg6.getRetrieveDesc()); }
| #( max:MAX arg7:checkRetrieveDesc[usedRD] )
{ max.setRetrieveDesc(arg7.getRetrieveDesc()); }
| #( min:MIN arg8:checkRetrieveDesc[usedRD] )
{ min.setRetrieveDesc(arg8.getRetrieveDesc()); }
| #( sum:SUM arg9:checkRetrieveDesc[usedRD] )
{ sum.setRetrieveDesc(arg9.getRetrieveDesc()); }
| #( count:COUNT arg10:checkRetrieveDesc[usedRD] )
{ count.setRetrieveDesc(arg10.getRetrieveDesc()); }
| .
;