Open Source Repository

Home /hibernate/hibernate-3.2.7.ga | Repository Home


org/hibernate/hql/classic/WhereParser.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.setUseThetaStyleJointrue )//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 qthrows 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.continueFromManyToManyentityName, element.elementColumns, q );
      }
      else {
        throw new QueryException"illegally dereferenced collection element" );
      }
    }
    return name;
  }

  public void token(String token, QueryTranslatorImpl qthrows QueryException {

    String lcToken = token.toLowerCase();

    //Cope with [,]
    if token.equals"[" && !expectingPathContinuation ) {
      expectingPathContinuation = false;
      if expectingIndex == 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 = continuePathExpressiontoken, q );
      if pathExpressionContinuesFurther return//NOTE: early return
    }

    //Cope with a subselect
    if !inSubselect && lcToken.equals"select" || lcToken.equals"from" ) ) ) {
      inSubselect = true;
      subselect = new StringBuffer20 );
    }
    if inSubselect && token.equals")" ) ) {
      bracketsSinceSelect--;

      if bracketsSinceSelect == -) {
        QueryTranslatorImpl subq = new QueryTranslatorImpl(
                subselect.toString(),
            q.getEnabledFilters(),
            q.getFactory()
        );
        try {
          subq.compile);
        }
        catch MappingException me ) {
          throw new QueryException"MappingException occurred compiling subquery", me );
        }
        appendTokenq, subq.getSQLString() );
        inSubselect = false;
        bracketsSinceSelect = 0;
      }
    }
    if inSubselect ) {
      if token.equals"(" ) ) bracketsSinceSelect++;
      subselect.appendtoken ).append' ' );
      return;
    }

    //Cope with special cases of AND, NOT, ()
    specialCasesBeforelcToken );

    //Close extra brackets we opened
    if !betweenSpecialCase && EXPRESSION_TERMINATORS.containslcToken ) ) {
      closeExpressionq, lcToken );
    }

    //take note when this is a boolean expression
    if BOOLEAN_OPERATORS.containslcToken ) ) {
      booleanTests.removeLast();
      booleanTests.addLastBoolean.TRUE );
    }

    if lcToken.equals"not" ) ) {
      nots.addLastnew Boolean!( ( Boolean nots.removeLast() ).booleanValue() ) );
      negated = !negated;
      return//NOTE: early return
    }

    //process a token, mapping OO path expressions to SQL expressions
    doTokentoken, q );

    //Open any extra brackets we might need.
    if !betweenSpecialCase && EXPRESSION_OPENERS.containslcToken ) ) {
      openExpressionq, lcToken );
    }

    //Cope with special cases of AND, NOT, )
    specialCasesAfterlcToken );

  }

  public void start(QueryTranslatorImpl qthrows QueryException {
    token"(", q );
  }

  public void end(QueryTranslatorImpl qthrows QueryException {
    if expectingPathContinuation ) {
      expectingPathContinuation = false;
      PathExpressionParser.CollectionElement element = pathExpressionParser.lastCollectionElement();
      if element.elementColumns.length != throw new QueryException"path expression ended in composite collection element" );
      appendTokenq, element.elementColumns[0] );
      addToCurrentJoinelement );
    }
    token")", q );
  }

  private void closeExpression(QueryTranslatorImpl q, String lcToken) {
    if ( ( ( Boolean booleanTests.removeLast() ).booleanValue() ) { //it was a boolean expression

      if booleanTests.size() ) {
        // the next one up must also be
        booleanTests.removeLast();
        booleanTests.addLastBoolean.TRUE );
      }

      // Add any joins
      appendTokenq, joins.removeLast() ).toString() );

    }
    else {
      StringBuffer join = StringBuffer joins.removeLast();
      ( ( StringBuffer joins.getLast() ).appendjoin.toString() );
    }

    if ( ( ( Boolean nots.removeLast() ).booleanValue() ) negated = !negated;

    if !")".equalslcToken ) ) appendTokenq, ")" );
  }

  private void openExpression(QueryTranslatorImpl q, String lcToken) {
    nots.addLastBoolean.FALSE );
    booleanTests.addLastBoolean.FALSE );
    joins.addLastnew StringBuffer() );
    if !"(".equalslcToken ) ) appendTokenq, "(" );
  }

  private void preprocess(String token, QueryTranslatorImpl qthrows 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 > &&
        CollectionPropertyNames.COLLECTION_ELEMENTS.equalstokens[tokens.length - 1] )
        || CollectionPropertyNames.COLLECTION_INDICES.equalstokens[tokens.length - 1] ) )
    ) {
      pathExpressionParser.start);
      for int i = 0; i < tokens.length - 3; i++ ) {
        pathExpressionParser.tokentokens[i], q );
      }
      pathExpressionParser.tokennull, q );
      pathExpressionParser.end);
      addJoinpathExpressionParser.getWhereJoin(), q );
      pathExpressionParser.ignoreInitialJoin();
    }
  }

  private void doPathExpression(String token, QueryTranslatorImpl qthrows QueryException {

    preprocesstoken, q );

    StringTokenizer tokens = new StringTokenizertoken, "."true );
    pathExpressionParser.start);
    while tokens.hasMoreTokens() ) {
      pathExpressionParser.tokentokens.nextToken(), q );
    }
    pathExpressionParser.end);
    if pathExpressionParser.isCollectionValued() ) {
      openExpressionq, "" );
      appendTokenq, pathExpressionParser.getCollectionSubqueryq.getEnabledFilters() ) );
      closeExpressionq, "" );
      // this is ugly here, but needed because its a subquery
      q.addQuerySpacesq.getCollectionPersisterpathExpressionParser.getCollectionRole() ).getCollectionSpaces() );
    }
    else {
      if pathExpressionParser.isExpectingCollectionIndex() ) {
        expectingIndex++;
      }
      else {
        addJoinpathExpressionParser.getWhereJoin(), q );
        appendTokenq, pathExpressionParser.getWhereColumn() );
      }
    }
  }

  private void addJoin(JoinSequence joinSequence, QueryTranslatorImpl qthrows QueryException {
    //JoinFragment fromClause = q.createJoinFragment(true);
    //fromClause.addJoins( join.toJoinFragment().toFromFragmentString(), StringHelper.EMPTY_STRING );
    q.addFromJoinOnlypathExpressionParser.getName(), joinSequence );
    try {
      addToCurrentJoinjoinSequence.toJoinFragmentq.getEnabledFilters()true ).toWhereFragmentString() );
    }
    catch MappingException me ) {
      throw new QueryExceptionme );
    }
  }

  private void doToken(String token, QueryTranslatorImpl qthrows QueryException {
    if q.isNameStringHelper.roottoken ) ) ) { //path expression
      doPathExpressionq.unaliastoken ), q );
    }
    else if token.startsWithParserHelper.HQL_VARIABLE_PREFIX ) ) { //named query parameter
      q.addNamedParametertoken.substring) );
      appendTokenq, "?" );
    }
    else {
      Queryable persister = q.getEntityPersisterUsingImportstoken );
      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 {
          appendTokenq, discrim );
        }
      }
      else {
        Object constant;
        if (
            token.indexOf'.' > -&&
            constant = ReflectHelper.getConstantValuetoken ) ) != null
        ) {
          Type type;
          try {
            type = TypeFactory.heuristicTypeconstant.getClass().getName() );
          }
          catch MappingException me ) {
            throw new QueryExceptionme );
          }
          if type == null throw new QueryExceptionQueryTranslator.ERROR_CANNOT_DETERMINE_TYPE + token );
          try {
            appendTokenq, ( ( LiteralType type ).objectToSQLStringconstant, q.getFactory().getDialect() ) );
          }
          catch Exception e ) {
            throw new QueryExceptionQueryTranslator.ERROR_CANNOT_FORMAT_LITERAL + token, e );
          }
        }
        else //anything else

          String negatedToken = negated ? String NEGATIONS.gettoken.toLowerCase() ) null;
          if negatedToken != null && !betweenSpecialCase || !"or".equalsnegatedToken ) ) ) {
            appendTokenq, negatedToken );
          }
          else {
            appendTokenq, token );
          }
        }
      }
    }
  }

  private void addToCurrentJoin(String sql) {
    ( ( StringBuffer joins.getLast() ).appendsql );
  }

  private void addToCurrentJoin(PathExpressionParser.CollectionElement ce)
      throws QueryException {
    try {
      addToCurrentJoince.joinSequence.toJoinFragment().toWhereFragmentString() + ce.indexValue.toString() );
    }
    catch MappingException me ) {
      throw new QueryExceptionme );
    }
  }

  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 > ) {
      pathExpressionParser.setLastCollectionElementIndexValuetoken );
    }
    else {
      q.appendWhereTokentoken );
    }
  }

  private boolean continuePathExpression(String token, QueryTranslatorImpl qthrows QueryException {

    expectingPathContinuation = false;

    PathExpressionParser.CollectionElement element = pathExpressionParser.lastCollectionElement();

    if token.startsWith"." ) ) { // the path expression continues after a ]

      doPathExpressiongetElementNameelement, q + token, q )// careful with this!

      addToCurrentJoinelement );
      return true//NOTE: EARLY EXIT!

    }

    else // the path expression ends at the ]
      if element.elementColumns.length != ) {
        throw new QueryException"path expression ended in composite collection element" );
      }
      appendTokenq, element.elementColumns[0] );
      addToCurrentJoinelement );
      return false;
    }
  }
}