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.
* 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). *
* NOTE: The java class is generated from hql-sql.g by ANTLR. * DO NOT EDIT THE GENERATED JAVA SOURCE CODE. * @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 ;