699 lines
20 KiB
Plaintext
699 lines
20 KiB
Plaintext
|
header
|
||
|
{
|
||
|
// $Id$
|
||
|
package org.hibernate.hql.antlr;
|
||
|
|
||
|
import org.apache.commons.logging.Log;
|
||
|
import org.apache.commons.logging.LogFactory;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hibernate Query Language to SQL Tree Transform.<br>
|
||
|
* This is a tree grammar that transforms an HQL AST into a intermediate SQL AST
|
||
|
* with bindings to Hibernate interfaces (Queryable, etc.). The Hibernate specific methods
|
||
|
* are all implemented in the HqlSqlWalker subclass, allowing the ANTLR-generated class
|
||
|
* to have only the minimum dependencies on the Hibernate code base. This will also allow
|
||
|
* the sub-class to be easily edited using an IDE (most IDE's don't support ANTLR).
|
||
|
* <br>
|
||
|
* <i>NOTE:</i> The java class is generated from hql-sql.g by ANTLR.
|
||
|
* <i>DO NOT EDIT THE GENERATED JAVA SOURCE CODE.</i>
|
||
|
* @author Joshua Davis (joshua@hibernate.org)
|
||
|
*/
|
||
|
class HqlSqlBaseWalker extends TreeParser;
|
||
|
|
||
|
options
|
||
|
{
|
||
|
// Note: importVocab and exportVocab cause ANTLR to share the token type numbers between the
|
||
|
// two grammars. This means that the token type constants from the source tree are the same
|
||
|
// as those in the target tree. If this is not the case, tree translation can result in
|
||
|
// token types from the *source* tree being present in the target tree.
|
||
|
importVocab=Hql; // import definitions from "Hql"
|
||
|
exportVocab=HqlSql; // Call the resulting definitions "HqlSql"
|
||
|
buildAST=true;
|
||
|
}
|
||
|
|
||
|
tokens
|
||
|
{
|
||
|
FROM_FRAGMENT; // A fragment of SQL that represents a table reference in a FROM clause.
|
||
|
IMPLIED_FROM; // An implied FROM element.
|
||
|
JOIN_FRAGMENT; // A JOIN fragment.
|
||
|
SELECT_CLAUSE;
|
||
|
LEFT_OUTER;
|
||
|
RIGHT_OUTER;
|
||
|
ALIAS_REF; // An IDENT that is a reference to an entity via it's alias.
|
||
|
PROPERTY_REF; // A DOT that is a reference to a property in an entity.
|
||
|
SQL_TOKEN; // A chunk of SQL that is 'rendered' already.
|
||
|
SELECT_COLUMNS; // A chunk of SQL representing a bunch of select columns.
|
||
|
SELECT_EXPR; // A select expression, generated from a FROM element.
|
||
|
THETA_JOINS; // Root of theta join condition subtree.
|
||
|
FILTERS; // Root of the filters condition subtree.
|
||
|
METHOD_NAME; // An IDENT that is a method name.
|
||
|
NAMED_PARAM; // A named parameter (:foo).
|
||
|
BOGUS; // Used for error state detection, etc.
|
||
|
}
|
||
|
|
||
|
// -- Declarations --
|
||
|
{
|
||
|
private static Log log = LogFactory.getLog( HqlSqlBaseWalker.class );
|
||
|
|
||
|
private int level = 0;
|
||
|
private boolean inSelect = false;
|
||
|
private boolean inFunctionCall = false;
|
||
|
private boolean inCase = false;
|
||
|
private boolean inFrom = false;
|
||
|
private int statementType;
|
||
|
private String statementTypeName;
|
||
|
// Note: currentClauseType tracks the current clause within the current
|
||
|
// statement, regardless of level; currentTopLevelClauseType, on the other
|
||
|
// hand, tracks the current clause within the top (or primary) statement.
|
||
|
// Thus, currentTopLevelClauseType ignores the clauses from any subqueries.
|
||
|
private int currentClauseType;
|
||
|
private int currentTopLevelClauseType;
|
||
|
private int currentStatementType;
|
||
|
|
||
|
public final boolean isSubQuery() {
|
||
|
return level > 1;
|
||
|
}
|
||
|
|
||
|
public final boolean isInFrom() {
|
||
|
return inFrom;
|
||
|
}
|
||
|
|
||
|
public final boolean isInFunctionCall() {
|
||
|
return inFunctionCall;
|
||
|
}
|
||
|
|
||
|
public final boolean isInSelect() {
|
||
|
return inSelect;
|
||
|
}
|
||
|
|
||
|
public final boolean isInCase() {
|
||
|
return inCase;
|
||
|
}
|
||
|
|
||
|
public final int getStatementType() {
|
||
|
return statementType;
|
||
|
}
|
||
|
|
||
|
public final int getCurrentClauseType() {
|
||
|
return currentClauseType;
|
||
|
}
|
||
|
|
||
|
public final int getCurrentTopLevelClauseType() {
|
||
|
return currentTopLevelClauseType;
|
||
|
}
|
||
|
|
||
|
public final int getCurrentStatementType() {
|
||
|
return currentStatementType;
|
||
|
}
|
||
|
|
||
|
public final boolean isComparativeExpressionClause() {
|
||
|
// Note: once we add support for "JOIN ... ON ...",
|
||
|
// the ON clause needs to get included here
|
||
|
return getCurrentClauseType() == WHERE ||
|
||
|
getCurrentClauseType() == WITH ||
|
||
|
isInCase();
|
||
|
}
|
||
|
|
||
|
public final boolean isSelectStatement() {
|
||
|
return statementType == SELECT;
|
||
|
}
|
||
|
|
||
|
private void beforeStatement(String statementName, int statementType) {
|
||
|
inFunctionCall = false;
|
||
|
level++;
|
||
|
if ( level == 1 ) {
|
||
|
this.statementTypeName = statementName;
|
||
|
this.statementType = statementType;
|
||
|
}
|
||
|
currentStatementType = statementType;
|
||
|
if ( log.isDebugEnabled() ) {
|
||
|
log.debug( statementName + " << begin [level=" + level + ", statement=" + this.statementTypeName + "]" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void beforeStatementCompletion(String statementName) {
|
||
|
if ( log.isDebugEnabled() ) {
|
||
|
log.debug( statementName + " : finishing up [level=" + level + ", statement=" + statementTypeName + "]" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void afterStatementCompletion(String statementName) {
|
||
|
if ( log.isDebugEnabled() ) {
|
||
|
log.debug( statementName + " >> end [level=" + level + ", statement=" + statementTypeName + "]" );
|
||
|
}
|
||
|
level--;
|
||
|
}
|
||
|
|
||
|
private void handleClauseStart(int clauseType) {
|
||
|
currentClauseType = clauseType;
|
||
|
if ( level == 1 ) {
|
||
|
currentTopLevelClauseType = clauseType;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
// NOTE: The real implementations for the following are in the subclass.
|
||
|
|
||
|
protected void evaluateAssignment(AST eq) throws SemanticException { }
|
||
|
|
||
|
/** Pre-process the from clause input tree. **/
|
||
|
protected void prepareFromClauseInputTree(AST fromClauseInput) {}
|
||
|
|
||
|
/** Sets the current 'FROM' context. **/
|
||
|
protected void pushFromClause(AST fromClause,AST inputFromNode) {}
|
||
|
|
||
|
protected AST createFromElement(String path,AST alias,AST propertyFetch) throws SemanticException {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
protected void createFromJoinElement(AST path,AST alias,int joinType,AST fetch,AST propertyFetch,AST with) throws SemanticException {}
|
||
|
|
||
|
protected AST createFromFilterElement(AST filterEntity,AST alias) throws SemanticException {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
protected void processQuery(AST select,AST query) throws SemanticException { }
|
||
|
|
||
|
protected void postProcessUpdate(AST update) throws SemanticException { }
|
||
|
|
||
|
protected void postProcessDelete(AST delete) throws SemanticException { }
|
||
|
|
||
|
protected void postProcessInsert(AST insert) throws SemanticException { }
|
||
|
|
||
|
protected void beforeSelectClause() throws SemanticException { }
|
||
|
|
||
|
protected void processIndex(AST indexOp) throws SemanticException { }
|
||
|
|
||
|
protected void processConstant(AST constant) throws SemanticException { }
|
||
|
|
||
|
protected void processBoolean(AST constant) throws SemanticException { }
|
||
|
|
||
|
protected void processNumericLiteral(AST literal) throws SemanticException { }
|
||
|
|
||
|
protected void resolve(AST node) throws SemanticException { }
|
||
|
|
||
|
protected void resolveSelectExpression(AST dotNode) throws SemanticException { }
|
||
|
|
||
|
protected void processFunction(AST functionCall,boolean inSelect) throws SemanticException { }
|
||
|
|
||
|
protected void processConstructor(AST constructor) throws SemanticException { }
|
||
|
|
||
|
protected AST generateNamedParameter(AST delimiterNode, AST nameNode) throws SemanticException {
|
||
|
return #( [NAMED_PARAM, nameNode.getText()] );
|
||
|
}
|
||
|
|
||
|
protected AST generatePositionalParameter(AST inputNode) throws SemanticException {
|
||
|
return #( [PARAM, "?"] );
|
||
|
}
|
||
|
|
||
|
protected void lookupAlias(AST ident) throws SemanticException { }
|
||
|
|
||
|
protected void setAlias(AST selectExpr, AST ident) { }
|
||
|
|
||
|
protected AST lookupProperty(AST dot,boolean root,boolean inSelect) throws SemanticException {
|
||
|
return dot;
|
||
|
}
|
||
|
|
||
|
protected boolean isNonQualifiedPropertyRef(AST ident) { return false; }
|
||
|
|
||
|
protected AST lookupNonQualifiedProperty(AST property) throws SemanticException { return property; }
|
||
|
|
||
|
protected void setImpliedJoinType(int joinType) { }
|
||
|
|
||
|
protected AST createIntoClause(String path, AST propertySpec) throws SemanticException {
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
protected void prepareVersioned(AST updateNode, AST versionedNode) throws SemanticException {}
|
||
|
|
||
|
protected void prepareLogicOperator(AST operator) throws SemanticException { }
|
||
|
|
||
|
protected void prepareArithmeticOperator(AST operator) throws SemanticException { }
|
||
|
}
|
||
|
|
||
|
// The main statement rule.
|
||
|
statement
|
||
|
: selectStatement | updateStatement | deleteStatement | insertStatement
|
||
|
;
|
||
|
|
||
|
selectStatement
|
||
|
: query
|
||
|
;
|
||
|
|
||
|
// Cannot use just the fromElement rule here in the update and delete queries
|
||
|
// because fromElement essentially relies on a FromClause already having been
|
||
|
// built :(
|
||
|
updateStatement!
|
||
|
: #( u:UPDATE { beforeStatement( "update", UPDATE ); } (v:VERSIONED)? f:fromClause s:setClause (w:whereClause)? ) {
|
||
|
#updateStatement = #(#u, #f, #s, #w);
|
||
|
beforeStatementCompletion( "update" );
|
||
|
prepareVersioned( #updateStatement, #v );
|
||
|
postProcessUpdate( #updateStatement );
|
||
|
afterStatementCompletion( "update" );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
deleteStatement
|
||
|
: #( DELETE { beforeStatement( "delete", DELETE ); } fromClause (whereClause)? ) {
|
||
|
beforeStatementCompletion( "delete" );
|
||
|
postProcessDelete( #deleteStatement );
|
||
|
afterStatementCompletion( "delete" );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
insertStatement
|
||
|
// currently only "INSERT ... SELECT ..." statements supported;
|
||
|
// do we also need support for "INSERT ... VALUES ..."?
|
||
|
//
|
||
|
: #( INSERT { beforeStatement( "insert", INSERT ); } intoClause query ) {
|
||
|
beforeStatementCompletion( "insert" );
|
||
|
postProcessInsert( #insertStatement );
|
||
|
afterStatementCompletion( "insert" );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
intoClause! {
|
||
|
String p = null;
|
||
|
}
|
||
|
: #( INTO { handleClauseStart( INTO ); } (p=path) ps:insertablePropertySpec ) {
|
||
|
#intoClause = createIntoClause(p, ps);
|
||
|
}
|
||
|
;
|
||
|
|
||
|
insertablePropertySpec
|
||
|
: #( RANGE (IDENT)+ )
|
||
|
;
|
||
|
|
||
|
setClause
|
||
|
: #( SET { handleClauseStart( SET ); } (assignment)* )
|
||
|
;
|
||
|
|
||
|
assignment
|
||
|
// Note: the propertyRef here needs to be resolved
|
||
|
// *before* we evaluate the newValue rule...
|
||
|
: #( EQ (p:propertyRef) { resolve(#p); } (newValue) ) {
|
||
|
evaluateAssignment( #assignment );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
// For now, just use expr. Revisit after ejb3 solidifies this.
|
||
|
newValue
|
||
|
: expr | query
|
||
|
;
|
||
|
|
||
|
// The query / subquery rule. Pops the current 'from node' context
|
||
|
// (list of aliases).
|
||
|
query!
|
||
|
: #( QUERY { beforeStatement( "select", SELECT ); }
|
||
|
// The first phase places the FROM first to make processing the SELECT simpler.
|
||
|
#(SELECT_FROM
|
||
|
f:fromClause
|
||
|
(s:selectClause)?
|
||
|
)
|
||
|
(w:whereClause)?
|
||
|
(g:groupClause)?
|
||
|
(o:orderClause)?
|
||
|
) {
|
||
|
// Antlr note: #x_in refers to the input AST, #x refers to the output AST
|
||
|
#query = #([SELECT,"SELECT"], #s, #f, #w, #g, #o);
|
||
|
beforeStatementCompletion( "select" );
|
||
|
processQuery( #s, #query );
|
||
|
afterStatementCompletion( "select" );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
orderClause
|
||
|
: #(ORDER { handleClauseStart( ORDER ); } orderExprs)
|
||
|
;
|
||
|
|
||
|
orderExprs
|
||
|
: expr ( ASCENDING | DESCENDING )? (orderExprs)?
|
||
|
;
|
||
|
|
||
|
groupClause
|
||
|
: #(GROUP { handleClauseStart( GROUP ); } (expr)+ ( #(HAVING logicalExpr) )? )
|
||
|
;
|
||
|
|
||
|
selectClause!
|
||
|
: #(SELECT { handleClauseStart( SELECT ); beforeSelectClause(); } (d:DISTINCT)? x:selectExprList ) {
|
||
|
#selectClause = #([SELECT_CLAUSE,"{select clause}"], #d, #x);
|
||
|
}
|
||
|
;
|
||
|
|
||
|
selectExprList {
|
||
|
boolean oldInSelect = inSelect;
|
||
|
inSelect = true;
|
||
|
}
|
||
|
: ( selectExpr | aliasedSelectExpr )+ {
|
||
|
inSelect = oldInSelect;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
aliasedSelectExpr!
|
||
|
: #(AS se:selectExpr i:identifier) {
|
||
|
setAlias(#se,#i);
|
||
|
#aliasedSelectExpr = #se;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
selectExpr
|
||
|
: p:propertyRef { resolveSelectExpression(#p); }
|
||
|
| #(ALL ar2:aliasRef) { resolveSelectExpression(#ar2); #selectExpr = #ar2; }
|
||
|
| #(OBJECT ar3:aliasRef) { resolveSelectExpression(#ar3); #selectExpr = #ar3; }
|
||
|
| con:constructor { processConstructor(#con); }
|
||
|
| functionCall
|
||
|
| count
|
||
|
| collectionFunction // elements() or indices()
|
||
|
| literal
|
||
|
| arithmeticExpr
|
||
|
| query
|
||
|
;
|
||
|
|
||
|
count
|
||
|
: #(COUNT ( DISTINCT | ALL )? ( aggregateExpr | ROW_STAR ) )
|
||
|
;
|
||
|
|
||
|
constructor
|
||
|
{ String className = null; }
|
||
|
: #(CONSTRUCTOR className=path ( selectExpr | aliasedSelectExpr )* )
|
||
|
;
|
||
|
|
||
|
aggregateExpr
|
||
|
: expr //p:propertyRef { resolve(#p); }
|
||
|
| collectionFunction
|
||
|
;
|
||
|
|
||
|
// Establishes the list of aliases being used by this query.
|
||
|
fromClause {
|
||
|
// NOTE: This references the INPUT AST! (see http://www.antlr.org/doc/trees.html#Action%20Translation)
|
||
|
// the ouput AST (#fromClause) has not been built yet.
|
||
|
prepareFromClauseInputTree(#fromClause_in);
|
||
|
}
|
||
|
: #(f:FROM { pushFromClause(#fromClause,f); handleClauseStart( FROM ); } fromElementList )
|
||
|
;
|
||
|
|
||
|
fromElementList {
|
||
|
boolean oldInFrom = inFrom;
|
||
|
inFrom = true;
|
||
|
}
|
||
|
: (fromElement)+ {
|
||
|
inFrom = oldInFrom;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
fromElement! {
|
||
|
String p = null;
|
||
|
}
|
||
|
// A simple class name, alias element.
|
||
|
: #(RANGE p=path (a:ALIAS)? (pf:FETCH)? ) {
|
||
|
#fromElement = createFromElement(p,a, pf);
|
||
|
}
|
||
|
| je:joinElement {
|
||
|
#fromElement = #je;
|
||
|
}
|
||
|
// A from element created due to filter compilation
|
||
|
| fe:FILTER_ENTITY a3:ALIAS {
|
||
|
#fromElement = createFromFilterElement(fe,a3);
|
||
|
}
|
||
|
;
|
||
|
|
||
|
joinElement! {
|
||
|
int j = INNER;
|
||
|
}
|
||
|
// A from element with a join. This time, the 'path' should be treated as an AST
|
||
|
// and resolved (like any path in a WHERE clause). Make sure all implied joins
|
||
|
// generated by the property ref use the join type, if it was specified.
|
||
|
: #(JOIN (j=joinType { setImpliedJoinType(j); } )? (f:FETCH)? ref:propertyRef (a:ALIAS)? (pf:FETCH)? (with:WITH)? ) {
|
||
|
//createFromJoinElement(#ref,a,j,f, pf);
|
||
|
createFromJoinElement(#ref,a,j,f, pf, with);
|
||
|
setImpliedJoinType(INNER); // Reset the implied join type.
|
||
|
}
|
||
|
;
|
||
|
|
||
|
// Returns an node type integer that represents the join type
|
||
|
// tokens.
|
||
|
joinType returns [int j] {
|
||
|
j = INNER;
|
||
|
}
|
||
|
: ( (left:LEFT | right:RIGHT) (outer:OUTER)? ) {
|
||
|
if (left != null) j = LEFT_OUTER;
|
||
|
else if (right != null) j = RIGHT_OUTER;
|
||
|
else if (outer != null) j = RIGHT_OUTER;
|
||
|
}
|
||
|
| FULL {
|
||
|
j = FULL;
|
||
|
}
|
||
|
| INNER {
|
||
|
j = INNER;
|
||
|
}
|
||
|
;
|
||
|
|
||
|
// Matches a path and returns the normalized string for the path (usually
|
||
|
// fully qualified a class name).
|
||
|
path returns [String p] {
|
||
|
p = "???";
|
||
|
String x = "?x?";
|
||
|
}
|
||
|
: a:identifier { p = a.getText(); }
|
||
|
| #(DOT x=path y:identifier) {
|
||
|
StringBuffer buf = new StringBuffer();
|
||
|
buf.append(x).append(".").append(y.getText());
|
||
|
p = buf.toString();
|
||
|
}
|
||
|
;
|
||
|
|
||
|
// Returns a path as a single identifier node.
|
||
|
pathAsIdent {
|
||
|
String text = "?text?";
|
||
|
}
|
||
|
: text=path {
|
||
|
#pathAsIdent = #([IDENT,text]);
|
||
|
}
|
||
|
;
|
||
|
|
||
|
withClause
|
||
|
// Note : this is used internally from the HqlSqlWalker to
|
||
|
// parse the node recognized with the with keyword earlier.
|
||
|
// Done this way because it relies on the join it "qualifies"
|
||
|
// already having been processed, which would not be the case
|
||
|
// if withClause was simply referenced from the joinElement
|
||
|
// rule during recognition...
|
||
|
: #(w:WITH { handleClauseStart( WITH ); } b:logicalExpr ) {
|
||
|
#withClause = #(w , #b);
|
||
|
}
|
||
|
;
|
||
|
|
||
|
whereClause
|
||
|
: #(w:WHERE { handleClauseStart( WHERE ); } b:logicalExpr ) {
|
||
|
// Use the *output* AST for the boolean expression!
|
||
|
#whereClause = #(w , #b);
|
||
|
}
|
||
|
;
|
||
|
|
||
|
logicalExpr
|
||
|
: #(AND logicalExpr logicalExpr)
|
||
|
| #(OR logicalExpr logicalExpr)
|
||
|
| #(NOT logicalExpr)
|
||
|
| comparisonExpr
|
||
|
;
|
||
|
|
||
|
// TODO: Add any other comparison operators here.
|
||
|
comparisonExpr
|
||
|
:
|
||
|
( #(EQ exprOrSubquery exprOrSubquery)
|
||
|
| #(NE exprOrSubquery exprOrSubquery)
|
||
|
| #(LT exprOrSubquery exprOrSubquery)
|
||
|
| #(GT exprOrSubquery exprOrSubquery)
|
||
|
| #(LE exprOrSubquery exprOrSubquery)
|
||
|
| #(GE exprOrSubquery exprOrSubquery)
|
||
|
| #(LIKE exprOrSubquery expr ( #(ESCAPE expr) )? )
|
||
|
| #(NOT_LIKE exprOrSubquery expr ( #(ESCAPE expr) )? )
|
||
|
| #(BETWEEN exprOrSubquery exprOrSubquery exprOrSubquery)
|
||
|
| #(NOT_BETWEEN exprOrSubquery exprOrSubquery exprOrSubquery)
|
||
|
| #(IN exprOrSubquery inRhs )
|
||
|
| #(NOT_IN exprOrSubquery inRhs )
|
||
|
| #(IS_NULL exprOrSubquery)
|
||
|
| #(IS_NOT_NULL exprOrSubquery)
|
||
|
// | #(IS_TRUE expr)
|
||
|
// | #(IS_FALSE expr)
|
||
|
| #(EXISTS ( expr | collectionFunctionOrSubselect ) )
|
||
|
) {
|
||
|
prepareLogicOperator( #comparisonExpr );
|
||
|
}
|
||
|
;
|
||
|
|
||
|
inRhs
|
||
|
: #(IN_LIST ( collectionFunctionOrSubselect | ( (expr)* ) ) )
|
||
|
;
|
||
|
|
||
|
exprOrSubquery
|
||
|
: expr
|
||
|
| query
|
||
|
| #(ANY collectionFunctionOrSubselect)
|
||
|
| #(ALL collectionFunctionOrSubselect)
|
||
|
| #(SOME collectionFunctionOrSubselect)
|
||
|
;
|
||
|
|
||
|
collectionFunctionOrSubselect
|
||
|
: collectionFunction
|
||
|
| query
|
||
|
;
|
||
|
|
||
|
expr
|
||
|
: ae:addrExpr [ true ] { resolve(#ae); } // Resolve the top level 'address expression'
|
||
|
| #( VECTOR_EXPR (expr)* )
|
||
|
| constant
|
||
|
| arithmeticExpr
|
||
|
| functionCall // Function call, not in the SELECT clause.
|
||
|
| parameter
|
||
|
| count // Count, not in the SELECT clause.
|
||
|
;
|
||
|
|
||
|
arithmeticExpr
|
||
|
: #(PLUS expr expr) { prepareArithmeticOperator( #arithmeticExpr ); }
|
||
|
| #(MINUS expr expr) { prepareArithmeticOperator( #arithmeticExpr ); }
|
||
|
| #(DIV expr expr) { prepareArithmeticOperator( #arithmeticExpr ); }
|
||
|
| #(STAR expr expr) { prepareArithmeticOperator( #arithmeticExpr ); }
|
||
|
// | #(CONCAT expr (expr)+ ) { prepareArithmeticOperator( #arithmeticExpr ); }
|
||
|
| #(UNARY_MINUS expr) { prepareArithmeticOperator( #arithmeticExpr ); }
|
||
|
| caseExpr
|
||
|
;
|
||
|
|
||
|
caseExpr
|
||
|
: #(CASE { inCase = true; } (#(WHEN logicalExpr expr))+ (#(ELSE expr))?) { inCase = false; }
|
||
|
| #(CASE2 { inCase = true; } expr (#(WHEN expr expr))+ (#(ELSE expr))?) { inCase = false; }
|
||
|
;
|
||
|
|
||
|
//TODO: I don't think we need this anymore .. how is it different to
|
||
|
// maxelements, etc, which are handled by functionCall
|
||
|
collectionFunction
|
||
|
: #(e:ELEMENTS {inFunctionCall=true;} p1:propertyRef { resolve(#p1); } )
|
||
|
{ processFunction(#e,inSelect); } {inFunctionCall=false;}
|
||
|
| #(i:INDICES {inFunctionCall=true;} p2:propertyRef { resolve(#p2); } )
|
||
|
{ processFunction(#i,inSelect); } {inFunctionCall=false;}
|
||
|
;
|
||
|
|
||
|
functionCall
|
||
|
: #(METHOD_CALL {inFunctionCall=true;} pathAsIdent ( #(EXPR_LIST (expr)* ) )? )
|
||
|
{ processFunction(#functionCall,inSelect); } {inFunctionCall=false;}
|
||
|
| #(AGGREGATE aggregateExpr )
|
||
|
;
|
||
|
|
||
|
constant
|
||
|
: literal
|
||
|
| NULL
|
||
|
| TRUE { processBoolean(#constant); }
|
||
|
| FALSE { processBoolean(#constant); }
|
||
|
| JAVA_CONSTANT
|
||
|
;
|
||
|
|
||
|
literal
|
||
|
: NUM_INT { processNumericLiteral( #literal ); }
|
||
|
| NUM_LONG { processNumericLiteral( #literal ); }
|
||
|
| NUM_FLOAT { processNumericLiteral( #literal ); }
|
||
|
| NUM_DOUBLE { processNumericLiteral( #literal ); }
|
||
|
| QUOTED_STRING
|
||
|
;
|
||
|
|
||
|
identifier
|
||
|
: (IDENT | WEIRD_IDENT)
|
||
|
;
|
||
|
|
||
|
addrExpr! [ boolean root ]
|
||
|
: #(d:DOT lhs:addrExprLhs rhs:propertyName ) {
|
||
|
// This gives lookupProperty() a chance to transform the tree
|
||
|
// to process collection properties (.elements, etc).
|
||
|
#addrExpr = #(#d, #lhs, #rhs);
|
||
|
#addrExpr = lookupProperty(#addrExpr,root,false);
|
||
|
}
|
||
|
| #(i:INDEX_OP lhs2:addrExprLhs rhs2:expr) {
|
||
|
#addrExpr = #(#i, #lhs2, #rhs2);
|
||
|
processIndex(#addrExpr);
|
||
|
}
|
||
|
| p:identifier {
|
||
|
// #addrExpr = #p;
|
||
|
// resolve(#addrExpr);
|
||
|
// In many cases, things other than property-refs are recognized
|
||
|
// by this addrExpr rule. Some of those I have seen:
|
||
|
// 1) select-clause from-aliases
|
||
|
// 2) sql-functions
|
||
|
if ( isNonQualifiedPropertyRef(#p) ) {
|
||
|
#addrExpr = lookupNonQualifiedProperty(#p);
|
||
|
}
|
||
|
else {
|
||
|
resolve(#p);
|
||
|
#addrExpr = #p;
|
||
|
}
|
||
|
}
|
||
|
;
|
||
|
|
||
|
addrExprLhs
|
||
|
: addrExpr [ false ]
|
||
|
;
|
||
|
|
||
|
propertyName
|
||
|
: identifier
|
||
|
| CLASS
|
||
|
| ELEMENTS
|
||
|
| INDICES
|
||
|
;
|
||
|
|
||
|
propertyRef!
|
||
|
: #(d:DOT lhs:propertyRefLhs rhs:propertyName ) {
|
||
|
// This gives lookupProperty() a chance to transform the tree to process collection properties (.elements, etc).
|
||
|
#propertyRef = #(#d, #lhs, #rhs);
|
||
|
#propertyRef = lookupProperty(#propertyRef,false,true);
|
||
|
}
|
||
|
|
|
||
|
p:identifier {
|
||
|
// In many cases, things other than property-refs are recognized
|
||
|
// by this propertyRef rule. Some of those I have seen:
|
||
|
// 1) select-clause from-aliases
|
||
|
// 2) sql-functions
|
||
|
if ( isNonQualifiedPropertyRef(#p) ) {
|
||
|
#propertyRef = lookupNonQualifiedProperty(#p);
|
||
|
}
|
||
|
else {
|
||
|
resolve(#p);
|
||
|
#propertyRef = #p;
|
||
|
}
|
||
|
}
|
||
|
;
|
||
|
|
||
|
propertyRefLhs
|
||
|
: propertyRef
|
||
|
;
|
||
|
|
||
|
aliasRef!
|
||
|
: i:identifier {
|
||
|
#aliasRef = #([ALIAS_REF,i.getText()]); // Create an ALIAS_REF node instead of an IDENT node.
|
||
|
lookupAlias(#aliasRef);
|
||
|
}
|
||
|
;
|
||
|
|
||
|
parameter!
|
||
|
: #(c:COLON a:identifier) {
|
||
|
// Create a NAMED_PARAM node instead of (COLON IDENT).
|
||
|
#parameter = generateNamedParameter( c, a );
|
||
|
// #parameter = #([NAMED_PARAM,a.getText()]);
|
||
|
// namedParameter(#parameter);
|
||
|
}
|
||
|
| #(p:PARAM (n:NUM_INT)?) {
|
||
|
if ( n != null ) {
|
||
|
// An ejb3-style "positional parameter", which we handle internally as a named-param
|
||
|
#parameter = generateNamedParameter( p, n );
|
||
|
// #parameter = #([NAMED_PARAM,n.getText()]);
|
||
|
// namedParameter(#parameter);
|
||
|
}
|
||
|
else {
|
||
|
#parameter = generatePositionalParameter( p );
|
||
|
// #parameter = #([PARAM,"?"]);
|
||
|
// positionalParameter(#parameter);
|
||
|
}
|
||
|
}
|
||
|
;
|
||
|
|
||
|
numericInteger
|
||
|
: NUM_INT
|
||
|
;
|