CodeGeneration.g revision ada1678a4262b208a7b87391f520a7767d25287c
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
/*
* CodeGeneration.g
*
* Created on March 13, 2000
*/
{
}
/**
* 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;
{
importVocab = JQL;
}
{
/** Name of the USE_IN property. */
public static final String USE_IN_PROPERTY =
"com.sun.jdo.spi.persistence.support.sqlstore.query.jqlc.USE_IN";
/** */
/**
* I18N support
*/
protected final static ResourceBundle messages =
/**
* The persistence manager the query object is connected to.
*/
protected PersistenceManager pm;
/**
* type table
*/
/**
* query parameter table
*/
protected ParameterTable paramtab;
/**
*
*/
/**
* 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.
*/
/**
* Set of RetrieveDescs. CodeGeneration uses this set to prevent multiple
* addConstraint calls for the RetrieveDescs denoting a variable.
*/
protected Set boundRetrieveDescs;
/** The logger */
/**
* Defines the SQL wildcard character to be used in wildcard pattern
* (string methods startsWith and endsWith).
*/
/**
*
*/
boolean prefetchEnabled)
{
this.prefetchEnabled = prefetchEnabled;
this.boundRetrieveDescs = new HashSet();
}
/**
*
*/
}
/**
*
*/
public void reportError(String s) {
}
/**
* Returns the RetrieveDesc that represents the current query.
*/
public RetrieveDesc getRetrieveDesc()
{
if (candidateRD instanceof DebugRetrieveDesc)
return candidateRD;
}
/**
* Helper method for checkRetrieveDesc handling operators & and &&.
*/
{
{
// 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
}
else
{
}
}
/**
* Check the attached RetrieveDesc of the specified binary operation and its operands.
*/
{
{
// case 1: no RetrieveDesc for left operand, but right operand returns RetrieveDesc
// attach the right RetrieveDesc to all nodes of the left subtree
}
{
// case 2: no RetrieveDesc for right operand, but left operand returns RetrieveDesc
// attach the left RetrieveDesc to all nodes of the right subtree
}
{
// case 3: both left and right operand have a RetrieveDesc attached
{
// case 3a: left and right RetrieveDesc are identical
}
else
{
// case 3b: left and right RetrieveDesc are NOT identical
// check navigation:
// use leftRD as default
{
}
}
}
return rd;
}
/** Helper method for getCommonRetrieveDesc used to check navigation. */
protected RetrieveDesc getCommonRetrieveDescHelper(
{
{
// case I: left operand is a navigation and
// the navigation source is equal to the right operand
}
{
// case II: right operand is a navigation and
// the navigation source is equal to the left operand
}
{
// 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
{
}
{
}
{
}
}
return rd;
}
/**
* Helper method to support getting the common RetrieveDesc for operands
* taking three arguments such as like with escape, substring, indexOf.
*/
{
// Just call the regular method for binray ops,
// if the third argument is not specified.
}
else {
// First check args two and three.
// Now check the first and the second arg.
// 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.
}
return rd;
}
/**
* Helper method to support getting the common RetrieveDesc for object
* comparison operators.
*/
{
{
// case obj.relship == null
// take the RetrieveDesc from the navigation source
}
{
// case null == obj.relship
// take the RetrieveDesc from the navigation source
}
else
{
// use regular getCommonRetrieveDesc
}
return rd;
}
/**
* Returns the source if a navigation or field access.
*/
{
{
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:
{
return tmp;
}
}
return null;
}
/**
* If the specifid node is a bound variable return the navigation source of
* it's collection.
*/
{
return null;
}
/**
* Attach the specified RetrieveDesc to all JQLAST node of the ast subtree,
* that do not have a RetrieveDesc attached.
*/
{
{
}
{
}
}
/**
* Returns an Object representing 0 according to the specified type.
*/
{
return (type instanceof NumberType) ?
null;
}
/**
* Returns an Object representing -1 according to the specified type.
*/
{
return (type instanceof NumberType) ?
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.
*/
{
return (type instanceof NumberType) ?
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:
break;
case RetrieveDesc.OP_NE:
break;
case RetrieveDesc.OP_LT:
break;
case RetrieveDesc.OP_LE:
break;
case RetrieveDesc.OP_GT:
break;
case RetrieveDesc.OP_GE:
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
*/
{
{
}
{
}
{
}
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)
*/
{
{
{
}
if (i.hasNext())
}
}
/**
* 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,
{
// 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.
{
if (i.hasNext())
}
}
/**
* Code generation for a comparison of the form
* parameter relop constantValue
*/
{
{
}
{
}
{
}
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;
}
"jqlc.codegeneration.getkeyfieldscomparisonbooleanop.invalidobj", //NOI18N
return 0;
}
/**
* Returns the value of the field access object.field.
* Uses jdoGetField for object of a persistence capable class and reflection otherwise.
*/
{
if (classType.isPersistenceCapable())
{
if (stateManager != null)
{
// call stateManager.prepareGetField to allow the stateManager
// to mediate the field access
}
}
else
{
// non persistence capable class => use reflection
try
{
}
catch (IllegalAccessException e)
{
throw new JDOFatalUserException(
}
}
return value;
}
/**
* This method checks whether the result RetrieveDesc needs a DISTINCT clause or not.
* @param query the query AST
*/
{
// candidateRD is null in the case of false filter
if (candidateRD == null)
return;
if (distinct)
}
/**
* 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
*/
{
{
case CONTAINS:
case NOT_CONTAINS:
return false;
case BAND:
case BOR:
case AND:
case OR:
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.
*/
{
{
}
return rd;
}
/**
* Wrapper that traces the RetrieveDesc calls
*/
protected static class DebugRetrieveDesc implements RetrieveDesc
{
{
}
{
if (rd instanceof DebugRetrieveDesc)
return rd;
}
// methods from RetrieveDesc
{
}
{
}
{
if (value instanceof RetrieveDesc)
{
}
else
{
}
}
{
}
public void addConstraint(String name, int operator, RetrieveDesc foreignConstraint, String foreignFieldName)
{
}
{
JQLAST.getRetrieveDescRepr(this) +
}
public void setPrefetchEnabled(boolean prefetchEnabled)
{
JQLAST.getRetrieveDescRepr(this) +
}
// Methods from ActionDesc
public Class getPersistenceCapableClass()
{ return wrapped.getPersistenceCapableClass(); }
}
}
// rules
: q:.
{
doCodeGen(q);
}
;
{
boolean distinct = false;
}
: #( q:QUERY
)
{
handleDistinct(q, distinct);
}
;
// ----------------------------------
// rules: candidate class
// ----------------------------------
{
}
: c:CLASS_DEF
// Note, DISTINCT is added by handleDistinct called in the rule query
;
// ----------------------------------
// rules: parameter declaration
// ----------------------------------
{
}
: ( declareParameter )*
;
;
// ----------------------------------
// rules: variable declaration
// ----------------------------------
{
}
: ( declareVariable )*
;
;
// ----------------------------------
// rules: ordering specification
// ----------------------------------
{
}
: ( orderSpec )*
;
{
int op = 0;
}
: #( ORDERING_DEF
)
)
;
orderingExpr [int op]
{
}
| e:.
{
"jqlc.codegeneration.generic.unsupportedop", // NOI18N
e.getText()));
}
;
// ----------------------------------
// rules: result expression
// ----------------------------------
{
distinct = false;
}
: #( RESULT_DEF
distinct = resultExpr[true]
)
| {
// no result is equivalent to setResult("distinct this") =>
// distinct is true
distinct = true;
}
;
{
// this should be take care at first level of recursion
distinct = false;
boolean tmp;
}
{
distinct = true;
}
{
}
{
}
{
}
{
}
{
} else {
"jqlc.codegeneration.resultexpr.missingpkfields", // NOI18N
resultType.getName()));
}
} else {
}
}
{
}
{
}
{
}
}
| THIS
;
{
boolean tmp;
}
;
// ----------------------------------
// 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_DEF expr:. )
{
case VALUE:
// constant filter
{
// Note, in the case of a true filter do not add
// any constraints to the candidateRD
{
// false filter => unset candidateRD
candidateRD = null;
}
}
else
{
"jqlc.codegeneration.filter.nonbooleanvalue", //NOI18N
}
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.
break;
default:
break;
}
}
;
;
// This rule transforms an access expression of a boolean field into an
// equal operation: expr == true.
: e:expression
{
if (#e.getType() == FIELD_ACCESS) {
}
}
;
{
// do not generate boolean operation if one of the operands is variable constraint
}
{
// do not generate boolean operation if one of the operands is variable constraint
}
{
"jqlc.codegeneration.generic.unsupportedop", // NOI18N
}
;
{
// do not generate boolean operation if one of the operands is variable constraint
}
{
// do not generate boolean operation if one of the operands is variable constraint
}
;
: ( fieldComparison )=> fieldComparison
| ( objectComparison )=> objectComparison
{
}
{
}
{
}
{
}
{
}
{
}
;
;
{
}
{
// case constant relop field
}
{
// case parameter relop field
// Support for fixed-width char pk columns
}
( value = constantValue
{
// case field relop constant
}
{
// case field relop field
}
{
// case field relop parameter
// Support for fixed-width char pk columns
}
)
;
{
}
;
{
}
// case constant relop dbvalue
{
{
// now handle navigation source
// now generate IS NULL constraint
}
else
{
}
}
// case dbvalue relop constant
{
{
// now handle navigation source
// now generate IS NULL constraint
}
else
{
}
}
// case dbvalue relop dbvalue
{
}
)
;
;
{
}
{
}
{
}
;
{
}
: THIS
| #( 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
;
: #( eq:COLLECTION_EQUAL . . )
{
"jqlc.codegeneration.collectioncomparison.nonnull")); // NOI18N
}
| #( ne:COLLECTION_NOT_EQUAL . . )
{
"jqlc.codegeneration.collectioncomparison.nonnull")); // NOI18N
}
;
{
// 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.
{
// case: indexOf() + intValue
}
{
// case: intValue + indexOf()
}
else
{
}
}
{
}
{
// 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.
{
// case: indexOf - intValue
// treated as indexOf + -intValue
}
else
{
}
}
{
}
{
}
{
}
;
{
}
: #( UNARY_PLUS expression )
// no action needed, just ignore the unary plus
| #( op2:UNARY_MINUS
(
{
}
|
{
// map -value to 0 - value
}
)
)
;
{
// map ~value to -1 - value (which is equivalent to (-value)-1)
}
// 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.
}
else {
}
}
;
{
}
{ /* code gen for cast? */ }
| value = v:constantValue
{
{
}
{
}
else
{
}
}
| p:PARAMETER
{
}
| THIS
| name = f:fieldAccess
{
}
// code moved to variable access
| #( NOT_CONTAINS . VARIABLE )
// code moved to variable access
| endsWith
| isEmpty
| like
| indexOf[0]
| length
| abs
| sqrt
;
{
}
: v:VALUE
{
}
;
{
}
;
{
}
;
{
}
{
{
{
else if (USE_IN)
// generate OP_IN if USE_IN property is set
else
// otherwise generate regular join
}
else
{
}
}
}
;
{
}
;
{
}
{
// I need to store a pointer to the second operand of startsWith here.
// See second alternative below.
}
(
{
{
// case 1 fieldAccess constantValue
}
else
{
// case 2 expression constantValue
}
}
| {
// 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!
}
{
// case 3 expression expression
}
)
)
;
{
}
(
{
{
// case 1 fieldAccess constantValue
}
else
{
// case 2 expression constantValue
}
}
{
// case 3 expression expression
}
)
)
;
{
}
{
}
;
{
}
{
}
;
{
// The default is no ESCAPE definition => OP_LIKE
}
{
}
| // empty rule
;
: // JDOQL: string.substring(begin, end) ->
// RetrieveDesc: SUBSTRING(string, begin + 1, end - begin)
{
{
// 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.
if (beginValue < 0)
{
"jqlc.codegeneration.substring.beginnegative", // NOI18N
}
else if (endValue < beginValue)
{
"jqlc.codegeneration.substring.beginlargerend", // NOI18N
}
// SQL length = end - begin
// SQL start index = begin + 1
}
else
{
// At least one of begin or end is a non constant value =>
// generate the arguments start and length of the SQL SUBSTRING
// The next 3 line denote the SQL length = end - begin
// The next 3 lines denote the SQL start index = begin + 1
}
// now push the string on the constraint stack
}
;
// incr denotes the value that need to be added to result of POSITION
{
}
: // JDOQL: string.indexOf(pattern) ->
// RetrieveDesc: POSITION(string, pattern) - 1
// JDOQL: string.indexOf(pattern, begin) ->
// RetrieveDesc: POSITION_START(string, pattern, begin + 1) - 1
{
// the 3 lines denote the SQL function POSITION OR POSITION_START
// SQL handles indexes starting from 1 =>
// decrement the returned value to make it Java like!
incr--;
if (incr != 0)
{
}
}
;
{
// The default is no start definition => OP_POSITION
}
: e:.
{
// Java indexOf method use indexes starting with 0,
// where SQL starts with 1, so we need to add 1
{
// Optimization: calulate index at compile time,
// if start is a constant value.
// Note, Semantic ensures begin and end are int or Integer values.
}
else
{
new Integer(1));
expression(e);
}
}
| // empty rule
;
{
}
;
{
}
;
{
}
;
: TYPENAME
;
: BOOLEAN
| BYTE
| CHAR
| SHORT
| INT
| FLOAT
| LONG
| DOUBLE
;
// ----------------------------------
// rules: RetrieveDesc handling
// ----------------------------------
{
}
: #( q:QUERY
( #( PARAMETER_DEF . . ) )*
( #( VARIABLE_DEF . . ) )*
( #( ORDERING_DEF
( ASCENDING | DESCENDING )
)
)*
)
)?
#( FILTER_DEF
)
)
;
: c:CLASS_DEF
{
// check persistence capable
}
{
}
// constantValue not necessary here, this is covered by the last rule
| t:THIS
{
}
{
{
}
}
{
}
{
}
{
{
}
n.setRetrieveDesc(to);
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
// binary operations
// unary operations
| .
;