org.hibernate.hql.internal.ast.HqlParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or .
*/
package org.hibernate.hql.internal.ast;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hibernate.QueryException;
import org.hibernate.hql.internal.antlr.HqlBaseParser;
import org.hibernate.hql.internal.antlr.HqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ASTPrinter;
import org.hibernate.hql.internal.ast.util.ASTUtil;
import org.hibernate.hql.internal.ast.util.TokenPrinters;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import antlr.ASTPair;
import antlr.MismatchedTokenException;
import antlr.RecognitionException;
import antlr.Token;
import antlr.TokenStreamException;
import antlr.collections.AST;
/**
* Implements the semantic action methods defined in the HQL base parser to keep the grammar
* source file a little cleaner. Extends the parser class generated by ANTLR.
*
* @author Joshua Davis ([email protected])
*/
public final class HqlParser extends HqlBaseParser {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( HqlParser.class );
private final ParseErrorHandler parseErrorHandler;
/**
* Get a HqlParser instance for the given HQL string.
*
* @param hql The HQL query string
*
* @return The parser.
*/
public static HqlParser getInstance(String hql) {
return new HqlParser( hql );
}
private HqlParser(String hql) {
// The fix for HHH-558...
super( new HqlLexer( new StringReader( hql ) ) );
parseErrorHandler = new ErrorTracker( hql );
// Create nodes that track line and column number.
setASTFactory( new HqlASTFactory() );
}
// handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private int traceDepth;
@Override
public void traceIn(String ruleName) {
if ( !LOG.isTraceEnabled() ) {
return;
}
if ( inputState.guessing > 0 ) {
return;
}
String prefix = StringHelper.repeat( '-', ( traceDepth++ * 2 ) ) + "-> ";
LOG.trace( prefix + ruleName );
}
@Override
public void traceOut(String ruleName) {
if ( !LOG.isTraceEnabled() ) {
return;
}
if ( inputState.guessing > 0 ) {
return;
}
String prefix = "<-" + StringHelper.repeat( '-', ( --traceDepth * 2 ) ) + " ";
LOG.trace( prefix + ruleName );
}
@Override
public void reportError(RecognitionException e) {
parseErrorHandler.reportError( e ); // Use the delegate.
}
@Override
public void reportError(String s) {
parseErrorHandler.reportError( s ); // Use the delegate.
}
@Override
public void reportWarning(String s) {
parseErrorHandler.reportWarning( s );
}
public ParseErrorHandler getParseErrorHandler() {
return parseErrorHandler;
}
/**
* Overrides the base behavior to retry keywords as identifiers.
*
* @param token The token.
* @param ex The recognition exception.
*
* @return AST - The new AST.
*
* @throws antlr.RecognitionException if the substitution was not possible.
* @throws antlr.TokenStreamException if the substitution was not possible.
*/
@Override
public AST handleIdentifierError(Token token, RecognitionException ex)
throws RecognitionException, TokenStreamException {
// If the token can tell us if it could be an identifier...
if ( token instanceof HqlToken ) {
HqlToken hqlToken = (HqlToken) token;
// ... and the token could be an identifier and the error is
// a mismatched token error ...
if ( hqlToken.isPossibleID() && ( ex instanceof MismatchedTokenException ) ) {
MismatchedTokenException mte = (MismatchedTokenException) ex;
// ... and the expected token type was an identifier, then:
if ( mte.expecting == HqlTokenTypes.IDENT ) {
// Use the token as an identifier.
reportWarning(
"Keyword '"
+ token.getText()
+ "' is being interpreted as an identifier due to: " + mte.getMessage()
);
// Add the token to the AST.
ASTPair currentAST = new ASTPair();
token.setType( HqlTokenTypes.WEIRD_IDENT );
astFactory.addASTChild( currentAST, astFactory.create( token ) );
consume();
return currentAST.root;
}
} // if
} // if
// Otherwise, handle the error normally.
return super.handleIdentifierError( token, ex );
}
/**
* Returns an equivalent tree for (NOT (a relop b) ), for example:
* (NOT (GT a b) ) => (LE a b)
*
*
* @param x The sub tree to transform, the parent is assumed to be NOT.
*
* @return AST - The equivalent sub-tree.
*/
@Override
public AST negateNode(AST x) {
//TODO: switch statements are always evil! We already had bugs because
// of forgotten token types. Use polymorphism for this!
switch ( x.getType() ) {
case OR: {
x.setType( AND );
x.setText( "{and}" );
x.setFirstChild( negateNode( x.getFirstChild() ) );
x.getFirstChild().setNextSibling( negateNode( x.getFirstChild().getNextSibling() ) );
return x;
}
case AND: {
x.setType( OR );
x.setText( "{or}" );
x.setFirstChild( negateNode( x.getFirstChild() ) );
x.getFirstChild().setNextSibling( negateNode( x.getFirstChild().getNextSibling() ) );
return x;
}
case EQ: {
// (NOT (EQ a b) ) => (NE a b)
x.setType( NE );
x.setText( "{not}" + x.getText() );
return x;
}
case NE: {
// (NOT (NE a b) ) => (EQ a b)
x.setType( EQ );
x.setText( "{not}" + x.getText() );
return x;
}
case GT: {
// (NOT (GT a b) ) => (LE a b)
x.setType( LE );
x.setText( "{not}" + x.getText() );
return x;
}
case LT: {
// (NOT (LT a b) ) => (GE a b)
x.setType( GE );
x.setText( "{not}" + x.getText() );
return x;
}
case GE: {
// (NOT (GE a b) ) => (LT a b)
x.setType( LT );
x.setText( "{not}" + x.getText() );
return x;
}
case LE: {
// (NOT (LE a b) ) => (GT a b)
x.setType( GT );
x.setText( "{not}" + x.getText() );
return x;
}
case LIKE: {
// (NOT (LIKE a b) ) => (NOT_LIKE a b)
x.setType( NOT_LIKE );
x.setText( "{not}" + x.getText() );
return x;
}
case NOT_LIKE: {
// (NOT (NOT_LIKE a b) ) => (LIKE a b)
x.setType( LIKE );
x.setText( "{not}" + x.getText() );
return x;
}
case IN: {
x.setType( NOT_IN );
x.setText( "{not}" + x.getText() );
return x;
}
case NOT_IN: {
x.setType( IN );
x.setText( "{not}" + x.getText() );
return x;
}
case IS_NULL: {
// (NOT (IS_NULL a b) ) => (IS_NOT_NULL a b)
x.setType( IS_NOT_NULL );
x.setText( "{not}" + x.getText() );
return x;
}
case IS_NOT_NULL: {
// (NOT (IS_NOT_NULL a b) ) => (IS_NULL a b)
x.setType( IS_NULL );
x.setText( "{not}" + x.getText() );
return x;
}
case BETWEEN: {
// (NOT (BETWEEN a b) ) => (NOT_BETWEEN a b)
x.setType( NOT_BETWEEN );
x.setText( "{not}" + x.getText() );
return x;
}
case NOT_BETWEEN: {
// (NOT (NOT_BETWEEN a b) ) => (BETWEEN a b)
x.setType( BETWEEN );
x.setText( "{not}" + x.getText() );
return x;
}
/* This can never happen because this rule will always eliminate the child NOT.
case NOT: {
// (NOT (NOT x) ) => (x)
return x.getFirstChild();
}
*/
default: {
// Just add a 'not' parent.
AST not = super.negateNode( x );
if ( not != x ) {
// relink the next sibling to the new 'not' parent
not.setNextSibling( x.getNextSibling() );
x.setNextSibling( null );
}
return not;
}
}
}
/**
* Post process equality expressions, clean up the subtree.
*
* @param x The equality expression.
*
* @return AST - The clean sub-tree.
*/
@Override
public AST processEqualityExpression(AST x) {
if ( x == null ) {
LOG.processEqualityExpression();
return null;
}
int type = x.getType();
if ( type == EQ || type == NE ) {
boolean negated = type == NE;
if ( x.getNumberOfChildren() == 2 ) {
AST a = x.getFirstChild();
AST b = a.getNextSibling();
// (EQ NULL b) => (IS_NULL b)
if ( a.getType() == NULL && b.getType() != NULL ) {
return createIsNullParent( b, negated );
}
// (EQ a NULL) => (IS_NULL a)
else if ( b.getType() == NULL && a.getType() != NULL ) {
return createIsNullParent( a, negated );
}
else if ( b.getType() == EMPTY ) {
return processIsEmpty( a, negated );
}
else {
return x;
}
}
else {
return x;
}
}
else {
return x;
}
}
private AST createIsNullParent(AST node, boolean negated) {
node.setNextSibling( null );
int type = negated ? IS_NOT_NULL : IS_NULL;
String text = negated ? "is not null" : "is null";
return ASTUtil.createParent( astFactory, type, text, node );
}
private AST processIsEmpty(AST node, boolean negated) {
node.setNextSibling( null );
// NOTE: Because we're using ASTUtil.createParent(), the tree must be created from the bottom up.
// IS EMPTY x => (EXISTS (QUERY (SELECT_FROM (FROM x) ) ) )
AST ast = createSubquery( node );
ast = ASTUtil.createParent( astFactory, EXISTS, "exists", ast );
// Add NOT if it's negated.
if ( !negated ) {
ast = ASTUtil.createParent( astFactory, NOT, "not", ast );
}
return ast;
}
private AST createSubquery(AST node) {
AST ast = ASTUtil.createParent( astFactory, RANGE, "RANGE", node );
ast = ASTUtil.createParent( astFactory, FROM, "from", ast );
ast = ASTUtil.createParent( astFactory, SELECT_FROM, "SELECT_FROM", ast );
ast = ASTUtil.createParent( astFactory, QUERY, "QUERY", ast );
return ast;
}
public void showAst(AST ast, PrintStream out) {
showAst( ast, new PrintWriter( out ) );
}
private void showAst(AST ast, PrintWriter pw) {
TokenPrinters.HQL_TOKEN_PRINTER.showAst( ast, pw );
}
@Override
public void matchOptionalFrom() throws RecognitionException, TokenStreamException {
returnAST = null;
ASTPair currentAST = new ASTPair();
AST optionalFrom_AST = null;
if ( LA( 1 ) == FROM ) {
if ( LA( 2 ) != DOT ) {
match( FROM );
optionalFrom_AST = (AST) currentAST.root;
returnAST = optionalFrom_AST;
}
}
}
@Override
public void firstPathTokenWeakKeywords() throws TokenStreamException {
int t = LA( 1 );
switch ( t ){
case DOT:
LT(0).setType( IDENT );
}
}
@Override
public void handlePrimaryExpressionDotIdent() throws TokenStreamException {
if ( LA( 2 ) == DOT && LA( 3 ) != IDENT ) {
// See if the second lookahead token can be an identifier.
HqlToken t = (HqlToken) LT( 3 );
if ( t.isPossibleID() ) {
// Set it!
t.setType( IDENT );
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "handleDotIdent() : new LT(3) token - %s", LT( 1 ) );
}
}
}
}
@Override
public void weakKeywords() throws TokenStreamException {
int t = LA( 1 );
switch ( t ) {
case ORDER:
case GROUP:
// Case 1: Multi token keywords GROUP BY and ORDER BY
// The next token ( LT(2) ) should be 'by'... otherwise, this is just an ident.
if ( LA( 2 ) != LITERAL_by ) {
LT( 1 ).setType( IDENT );
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "weakKeywords() : new LT(1) token - %s", LT( 1 ) );
}
}
break;
default:
// Case 2: The current token is after FROM and before '.'.
if ( LA( 0 ) == FROM && t != IDENT && LA( 2 ) == DOT ) {
HqlToken hqlToken = (HqlToken) LT( 1 );
if ( hqlToken.isPossibleID() ) {
hqlToken.setType( IDENT );
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "weakKeywords() : new LT(1) token - %s", LT( 1 ) );
}
}
}
break;
}
}
@Override
public void expectNamedParameterName() throws TokenStreamException {
// we expect the token following a COLON (':') to be the name of a named parameter.
// if the following token is anything other than IDENT we convert its type if possible.
// NOTE : the LT() call is more expensive than the LA() call; so we
// use LA() first to see if LT() is needed.
if ( LA( 1 ) != IDENT ) {
final HqlToken nextToken = (HqlToken) LT( 1 );
if ( nextToken.isPossibleID() ) {
LOG.debugf(
"Converting keyword [%s] following COLON to IDENT as an expected parameter name",
nextToken.getText()
);
nextToken.setType( IDENT );
}
}
}
@Override
public void handleDotIdent() throws TokenStreamException {
// This handles HHH-354, where there is a strange property name in a where clause.
// If the lookahead contains a DOT then something that isn't an IDENT...
if ( LA( 1 ) == DOT && LA( 2 ) != IDENT ) {
// See if the second lookahead token can be an identifier.
HqlToken t = (HqlToken) LT( 2 );
if ( t.isPossibleID() ) {
// Set it!
LT( 2 ).setType( IDENT );
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "handleDotIdent() : new LT(2) token - %s", LT( 1 ) );
}
}
}
}
@Override
public void processMemberOf(Token n, AST p, ASTPair currentAST) {
// convert MEMBER OF to the equivalent IN ELEMENTS structure...
AST inNode = n == null ? astFactory.create( IN, "in" ) : astFactory.create( NOT_IN, "not in" );
astFactory.makeASTRoot( currentAST, inNode );
AST inListNode = astFactory.create( IN_LIST, "inList" );
inNode.addChild( inListNode );
AST elementsNode = astFactory.create( ELEMENTS, "elements" );
inListNode.addChild( elementsNode );
elementsNode.addChild( p );
}
private Map> treatMap;
@Override
protected void registerTreat(AST pathToTreat, AST treatAs) {
final String path = toPathText( pathToTreat );
final String subclassName = toPathText( treatAs );
LOG.debugf( "Registering discovered request to treat(%s as %s)", path, subclassName );
if ( treatMap == null ) {
treatMap = new HashMap>();
}
Set subclassNames = treatMap.get( path );
if ( subclassNames == null ) {
subclassNames = new HashSet();
treatMap.put( path, subclassNames );
}
subclassNames.add( subclassName );
}
private String toPathText(AST node) {
final String text = node.getText();
if ( text.equals( "." )
&& node.getFirstChild() != null
&& node.getFirstChild().getNextSibling() != null
&& node.getFirstChild().getNextSibling().getNextSibling() == null ) {
return toPathText( node.getFirstChild() ) + '.' + toPathText( node.getFirstChild().getNextSibling() );
}
return text;
}
public Map> getTreatMap() {
return treatMap == null ? Collections.>emptyMap() : treatMap;
}
public static void panic() {
//overriden to avoid System.exit
throw new QueryException( "Parser: panic" );
}
}