org.hibernate.hql.classic.WhereParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate Show documentation
Show all versions of hibernate Show documentation
Relational Persistence for Java
//$Id: WhereParser.java 7825 2005-08-10 20:23:55Z oneovthafew $
package org.hibernate.hql.classic;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.engine.JoinSequence;
import org.hibernate.hql.QueryTranslator;
import org.hibernate.persister.collection.CollectionPropertyNames;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.sql.InFragment;
import org.hibernate.type.EntityType;
import org.hibernate.type.LiteralType;
import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;
import org.hibernate.util.ReflectHelper;
import org.hibernate.util.StringHelper;
/**
* Parses the where clause of a hibernate query and translates it to an
* SQL where clause.
*/
// We should reengineer this class so that, rather than the current ad -
// hoc linear approach to processing a stream of tokens, we instead
// build up a tree of expressions.
// We would probably refactor to have LogicParser (builds a tree of simple
// expressions connected by and, or, not), ExpressionParser (translates
// from OO terms like foo, foo.Bar, foo.Bar.Baz to SQL terms like
// FOOS.ID, FOOS.BAR_ID, etc) and PathExpressionParser (which does much
// the same thing it does now)
public class WhereParser implements Parser {
private final PathExpressionParser pathExpressionParser;
{
pathExpressionParser = new PathExpressionParser();
pathExpressionParser.setUseThetaStyleJoin( true ); //Need this, since join condition can appear inside parens!
}
private static final Set EXPRESSION_TERMINATORS = new HashSet(); //tokens that close a sub expression
private static final Set EXPRESSION_OPENERS = new HashSet(); //tokens that open a sub expression
private static final Set BOOLEAN_OPERATORS = new HashSet(); //tokens that would indicate a sub expression is a boolean expression
private static final Map NEGATIONS = new HashMap();
static {
EXPRESSION_TERMINATORS.add( "and" );
EXPRESSION_TERMINATORS.add( "or" );
EXPRESSION_TERMINATORS.add( ")" );
//expressionTerminators.add(","); // deliberately excluded
EXPRESSION_OPENERS.add( "and" );
EXPRESSION_OPENERS.add( "or" );
EXPRESSION_OPENERS.add( "(" );
//expressionOpeners.add(","); // deliberately excluded
BOOLEAN_OPERATORS.add( "<" );
BOOLEAN_OPERATORS.add( "=" );
BOOLEAN_OPERATORS.add( ">" );
BOOLEAN_OPERATORS.add( "#" );
BOOLEAN_OPERATORS.add( "~" );
BOOLEAN_OPERATORS.add( "like" );
BOOLEAN_OPERATORS.add( "ilike" );
BOOLEAN_OPERATORS.add( "regexp" );
BOOLEAN_OPERATORS.add( "rlike" );
BOOLEAN_OPERATORS.add( "is" );
BOOLEAN_OPERATORS.add( "in" );
BOOLEAN_OPERATORS.add( "any" );
BOOLEAN_OPERATORS.add( "some" );
BOOLEAN_OPERATORS.add( "all" );
BOOLEAN_OPERATORS.add( "exists" );
BOOLEAN_OPERATORS.add( "between" );
BOOLEAN_OPERATORS.add( "<=" );
BOOLEAN_OPERATORS.add( ">=" );
BOOLEAN_OPERATORS.add( "=>" );
BOOLEAN_OPERATORS.add( "=<" );
BOOLEAN_OPERATORS.add( "!=" );
BOOLEAN_OPERATORS.add( "<>" );
BOOLEAN_OPERATORS.add( "!#" );
BOOLEAN_OPERATORS.add( "!~" );
BOOLEAN_OPERATORS.add( "!<" );
BOOLEAN_OPERATORS.add( "!>" );
BOOLEAN_OPERATORS.add( "is not" );
BOOLEAN_OPERATORS.add( "not like" );
BOOLEAN_OPERATORS.add( "not ilike" );
BOOLEAN_OPERATORS.add( "not regexp" );
BOOLEAN_OPERATORS.add( "not rlike" );
BOOLEAN_OPERATORS.add( "not in" );
BOOLEAN_OPERATORS.add( "not between" );
BOOLEAN_OPERATORS.add( "not exists" );
NEGATIONS.put( "and", "or" );
NEGATIONS.put( "or", "and" );
NEGATIONS.put( "<", ">=" );
NEGATIONS.put( "=", "<>" );
NEGATIONS.put( ">", "<=" );
NEGATIONS.put( "#", "!#" );
NEGATIONS.put( "~", "!~" );
NEGATIONS.put( "like", "not like" );
NEGATIONS.put( "ilike", "not ilike" );
NEGATIONS.put( "regexp", "not regexp" );
NEGATIONS.put( "rlike", "not rlike" );
NEGATIONS.put( "is", "is not" );
NEGATIONS.put( "in", "not in" );
NEGATIONS.put( "exists", "not exists" );
NEGATIONS.put( "between", "not between" );
NEGATIONS.put( "<=", ">" );
NEGATIONS.put( ">=", "<" );
NEGATIONS.put( "=>", "<" );
NEGATIONS.put( "=<", ">" );
NEGATIONS.put( "!=", "=" );
NEGATIONS.put( "<>", "=" );
NEGATIONS.put( "!#", "#" );
NEGATIONS.put( "!~", "~" );
NEGATIONS.put( "!<", "<" );
NEGATIONS.put( "!>", ">" );
NEGATIONS.put( "is not", "is" );
NEGATIONS.put( "not like", "like" );
NEGATIONS.put( "not ilike", "ilike" );
NEGATIONS.put( "not regexp", "regexp" );
NEGATIONS.put( "not rlike", "rlike" );
NEGATIONS.put( "not in", "in" );
NEGATIONS.put( "not between", "between" );
NEGATIONS.put( "not exists", "exists" );
}
// Handles things like:
// a and b or c
// a and ( b or c )
// not a and not b
// not ( a and b )
// x between y and z (overloaded "and")
// x in ( a, b, c ) (overloaded brackets)
// not not a
// a is not null (overloaded "not")
// etc......
// and expressions like
// foo = bar (maps to: foo.id = bar.id)
// foo.Bar = 'foo' (maps to: foo.bar = 'foo')
// foo.Bar.Baz = 1.0 (maps to: foo.bar = bar.id and bar.baz = 1.0)
// 1.0 = foo.Bar.Baz (maps to: bar.baz = 1.0 and foo.Bar = bar.id)
// foo.Bar.Baz = a.B.C (maps to: bar.Baz = b.C and foo.Bar = bar.id and a.B = b.id)
// foo.Bar.Baz + a.B.C (maps to: bar.Baz + b.C and foo.Bar = bar.id and a.B = b.id)
// ( foo.Bar.Baz + 1.0 ) < 2.0 (maps to: ( bar.Baz + 1.0 ) < 2.0 and foo.Bar = bar.id)
private boolean betweenSpecialCase = false; //Inside a BETWEEN ... AND ... expression
private boolean negated = false;
private boolean inSubselect = false;
private int bracketsSinceSelect = 0;
private StringBuffer subselect;
private boolean expectingPathContinuation = false;
private int expectingIndex = 0;
// The following variables are stacks that keep information about each subexpression
// in the list of nested subexpressions we are currently processing.
private LinkedList nots = new LinkedList(); //were an odd or even number of NOTs encountered
private LinkedList joins = new LinkedList(); //the join string built up by compound paths inside this expression
private LinkedList booleanTests = new LinkedList(); //a flag indicating if the subexpression is known to be boolean
private String getElementName(PathExpressionParser.CollectionElement element, QueryTranslatorImpl q) throws QueryException {
String name;
if ( element.isOneToMany ) {
name = element.alias;
}
else {
Type type = element.elementType;
if ( type.isEntityType() ) { //ie. a many-to-many
String entityName = ( ( EntityType ) type ).getAssociatedEntityName();
name = pathExpressionParser.continueFromManyToMany( entityName, element.elementColumns, q );
}
else {
throw new QueryException( "illegally dereferenced collection element" );
}
}
return name;
}
public void token(String token, QueryTranslatorImpl q) throws QueryException {
String lcToken = token.toLowerCase();
//Cope with [,]
if ( token.equals( "[" ) && !expectingPathContinuation ) {
expectingPathContinuation = false;
if ( expectingIndex == 0 ) throw new QueryException( "unexpected [" );
return;
}
else if ( token.equals( "]" ) ) {
expectingIndex--;
expectingPathContinuation = true;
return;
}
//Cope with a continued path expression (ie. ].baz)
if ( expectingPathContinuation ) {
boolean pathExpressionContinuesFurther = continuePathExpression( token, q );
if ( pathExpressionContinuesFurther ) return; //NOTE: early return
}
//Cope with a subselect
if ( !inSubselect && ( lcToken.equals( "select" ) || lcToken.equals( "from" ) ) ) {
inSubselect = true;
subselect = new StringBuffer( 20 );
}
if ( inSubselect && token.equals( ")" ) ) {
bracketsSinceSelect--;
if ( bracketsSinceSelect == -1 ) {
QueryTranslatorImpl subq = new QueryTranslatorImpl(
subselect.toString(),
q.getEnabledFilters(),
q.getFactory()
);
try {
subq.compile( q );
}
catch ( MappingException me ) {
throw new QueryException( "MappingException occurred compiling subquery", me );
}
appendToken( q, subq.getSQLString() );
inSubselect = false;
bracketsSinceSelect = 0;
}
}
if ( inSubselect ) {
if ( token.equals( "(" ) ) bracketsSinceSelect++;
subselect.append( token ).append( ' ' );
return;
}
//Cope with special cases of AND, NOT, ()
specialCasesBefore( lcToken );
//Close extra brackets we opened
if ( !betweenSpecialCase && EXPRESSION_TERMINATORS.contains( lcToken ) ) {
closeExpression( q, lcToken );
}
//take note when this is a boolean expression
if ( BOOLEAN_OPERATORS.contains( lcToken ) ) {
booleanTests.removeLast();
booleanTests.addLast( Boolean.TRUE );
}
if ( lcToken.equals( "not" ) ) {
nots.addLast( new Boolean( !( ( Boolean ) nots.removeLast() ).booleanValue() ) );
negated = !negated;
return; //NOTE: early return
}
//process a token, mapping OO path expressions to SQL expressions
doToken( token, q );
//Open any extra brackets we might need.
if ( !betweenSpecialCase && EXPRESSION_OPENERS.contains( lcToken ) ) {
openExpression( q, lcToken );
}
//Cope with special cases of AND, NOT, )
specialCasesAfter( lcToken );
}
public void start(QueryTranslatorImpl q) throws QueryException {
token( "(", q );
}
public void end(QueryTranslatorImpl q) throws QueryException {
if ( expectingPathContinuation ) {
expectingPathContinuation = false;
PathExpressionParser.CollectionElement element = pathExpressionParser.lastCollectionElement();
if ( element.elementColumns.length != 1 ) throw new QueryException( "path expression ended in composite collection element" );
appendToken( q, element.elementColumns[0] );
addToCurrentJoin( element );
}
token( ")", q );
}
private void closeExpression(QueryTranslatorImpl q, String lcToken) {
if ( ( ( Boolean ) booleanTests.removeLast() ).booleanValue() ) { //it was a boolean expression
if ( booleanTests.size() > 0 ) {
// the next one up must also be
booleanTests.removeLast();
booleanTests.addLast( Boolean.TRUE );
}
// Add any joins
appendToken( q, ( joins.removeLast() ).toString() );
}
else {
StringBuffer join = ( StringBuffer ) joins.removeLast();
( ( StringBuffer ) joins.getLast() ).append( join.toString() );
}
if ( ( ( Boolean ) nots.removeLast() ).booleanValue() ) negated = !negated;
if ( !")".equals( lcToken ) ) appendToken( q, ")" );
}
private void openExpression(QueryTranslatorImpl q, String lcToken) {
nots.addLast( Boolean.FALSE );
booleanTests.addLast( Boolean.FALSE );
joins.addLast( new StringBuffer() );
if ( !"(".equals( lcToken ) ) appendToken( q, "(" );
}
private void preprocess(String token, QueryTranslatorImpl q) throws QueryException {
// ugly hack for cases like "elements(foo.bar.collection)"
// (multi-part path expression ending in elements or indices)
String[] tokens = StringHelper.split( ".", token, true );
if (
tokens.length > 5 &&
( CollectionPropertyNames.COLLECTION_ELEMENTS.equals( tokens[tokens.length - 1] )
|| CollectionPropertyNames.COLLECTION_INDICES.equals( tokens[tokens.length - 1] ) )
) {
pathExpressionParser.start( q );
for ( int i = 0; i < tokens.length - 3; i++ ) {
pathExpressionParser.token( tokens[i], q );
}
pathExpressionParser.token( null, q );
pathExpressionParser.end( q );
addJoin( pathExpressionParser.getWhereJoin(), q );
pathExpressionParser.ignoreInitialJoin();
}
}
private void doPathExpression(String token, QueryTranslatorImpl q) throws QueryException {
preprocess( token, q );
StringTokenizer tokens = new StringTokenizer( token, ".", true );
pathExpressionParser.start( q );
while ( tokens.hasMoreTokens() ) {
pathExpressionParser.token( tokens.nextToken(), q );
}
pathExpressionParser.end( q );
if ( pathExpressionParser.isCollectionValued() ) {
openExpression( q, "" );
appendToken( q, pathExpressionParser.getCollectionSubquery( q.getEnabledFilters() ) );
closeExpression( q, "" );
// this is ugly here, but needed because its a subquery
q.addQuerySpaces( q.getCollectionPersister( pathExpressionParser.getCollectionRole() ).getCollectionSpaces() );
}
else {
if ( pathExpressionParser.isExpectingCollectionIndex() ) {
expectingIndex++;
}
else {
addJoin( pathExpressionParser.getWhereJoin(), q );
appendToken( q, pathExpressionParser.getWhereColumn() );
}
}
}
private void addJoin(JoinSequence joinSequence, QueryTranslatorImpl q) throws QueryException {
//JoinFragment fromClause = q.createJoinFragment(true);
//fromClause.addJoins( join.toJoinFragment().toFromFragmentString(), StringHelper.EMPTY_STRING );
q.addFromJoinOnly( pathExpressionParser.getName(), joinSequence );
try {
addToCurrentJoin( joinSequence.toJoinFragment( q.getEnabledFilters(), true ).toWhereFragmentString() );
}
catch ( MappingException me ) {
throw new QueryException( me );
}
}
private void doToken(String token, QueryTranslatorImpl q) throws QueryException {
if ( q.isName( StringHelper.root( token ) ) ) { //path expression
doPathExpression( q.unalias( token ), q );
}
else if ( token.startsWith( ParserHelper.HQL_VARIABLE_PREFIX ) ) { //named query parameter
q.addNamedParameter( token.substring( 1 ) );
appendToken( q, "?" );
}
else {
Queryable persister = q.getEntityPersisterUsingImports( token );
if ( persister != null ) { // the name of a class
final String discrim = persister.getDiscriminatorSQLValue();
if ( InFragment.NULL.equals(discrim) || InFragment.NOT_NULL.equals(discrim) ) {
throw new QueryException( "subclass test not allowed for null or not null discriminator" );
}
else {
appendToken( q, discrim );
}
}
else {
Object constant;
if (
token.indexOf( '.' ) > -1 &&
( constant = ReflectHelper.getConstantValue( token ) ) != null
) {
Type type;
try {
type = TypeFactory.heuristicType( constant.getClass().getName() );
}
catch ( MappingException me ) {
throw new QueryException( me );
}
if ( type == null ) throw new QueryException( QueryTranslator.ERROR_CANNOT_DETERMINE_TYPE + token );
try {
appendToken( q, ( ( LiteralType ) type ).objectToSQLString( constant, q.getFactory().getDialect() ) );
}
catch ( Exception e ) {
throw new QueryException( QueryTranslator.ERROR_CANNOT_FORMAT_LITERAL + token, e );
}
}
else { //anything else
String negatedToken = negated ? ( String ) NEGATIONS.get( token.toLowerCase() ) : null;
if ( negatedToken != null && ( !betweenSpecialCase || !"or".equals( negatedToken ) ) ) {
appendToken( q, negatedToken );
}
else {
appendToken( q, token );
}
}
}
}
}
private void addToCurrentJoin(String sql) {
( ( StringBuffer ) joins.getLast() ).append( sql );
}
private void addToCurrentJoin(PathExpressionParser.CollectionElement ce)
throws QueryException {
try {
addToCurrentJoin( ce.joinSequence.toJoinFragment().toWhereFragmentString() + ce.indexValue.toString() );
}
catch ( MappingException me ) {
throw new QueryException( me );
}
}
private void specialCasesBefore(String lcToken) {
if ( lcToken.equals( "between" ) || lcToken.equals( "not between" ) ) {
betweenSpecialCase = true;
}
}
private void specialCasesAfter(String lcToken) {
if ( betweenSpecialCase && lcToken.equals( "and" ) ) {
betweenSpecialCase = false;
}
}
void appendToken(QueryTranslatorImpl q, String token) {
if ( expectingIndex > 0 ) {
pathExpressionParser.setLastCollectionElementIndexValue( token );
}
else {
q.appendWhereToken( token );
}
}
private boolean continuePathExpression(String token, QueryTranslatorImpl q) throws QueryException {
expectingPathContinuation = false;
PathExpressionParser.CollectionElement element = pathExpressionParser.lastCollectionElement();
if ( token.startsWith( "." ) ) { // the path expression continues after a ]
doPathExpression( getElementName( element, q ) + token, q ); // careful with this!
addToCurrentJoin( element );
return true; //NOTE: EARLY EXIT!
}
else { // the path expression ends at the ]
if ( element.elementColumns.length != 1 ) {
throw new QueryException( "path expression ended in composite collection element" );
}
appendToken( q, element.elementColumns[0] );
addToCurrentJoin( element );
return false;
}
}
}