Open Source Repository

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


org/hibernate/loader/custom/sql/SQLQueryParser.java
//$Id: SQLQueryParser.java 10067 2006-06-28 13:24:59Z [email protected] $
package org.hibernate.loader.custom.sql;

import org.hibernate.QueryException;
import org.hibernate.engine.query.ParameterParser;
import org.hibernate.persister.collection.SQLLoadableCollection;
import org.hibernate.persister.entity.SQLLoadable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 @author Gavin King
 @author Max Andersen
 @author Steve Ebersole
 */
public class SQLQueryParser {

  private final String originalQueryString;
  private final ParserContext context;

  private final Map namedParameters = new HashMap();
  private long aliasesFound = 0;

  static interface ParserContext {
    boolean isEntityAlias(String aliasName);
    SQLLoadable getEntityPersisterByAlias(String alias);
    String getEntitySuffixByAlias(String alias);
    boolean isCollectionAlias(String aliasName);
    SQLLoadableCollection getCollectionPersisterByAlias(String alias);
    String getCollectionSuffixByAlias(String alias);
    Map getPropertyResultsMapByAlias(String alias);
  }

  public SQLQueryParser(String queryString, ParserContext context) {
    this.originalQueryString = queryString;
    this.context = context;
  }

  public Map getNamedParameters() {
    return namedParameters;
  }

  public boolean queryHasAliases() {
    return aliasesFound>0;
  }

  public String process() {
    return substituteParamssubstituteBracketsoriginalQueryString ) );
  }

  // TODO: should "record" how many properties we have reffered to - and if we 
  //       don't get'em'all we throw an exception! Way better than trial and error ;)
  private String substituteBrackets(String sqlQuerythrows QueryException {

    StringBuffer result = new StringBuffersqlQuery.length() 20 );
    int left, right;

    // replace {....} with corresponding column aliases
    for int curr = 0; curr < sqlQuery.length(); curr = right + ) {
      if ( ( left = sqlQuery.indexOf'{', curr ) ) ) {
        // No additional open braces found in the string, append the
        // rest of the string in its entirty and quit this loop
        result.appendsqlQuery.substringcurr ) );
        break;
      }

      // apend everything up until the next encountered open brace
      result.appendsqlQuery.substringcurr, left ) );

      if ( ( right = sqlQuery.indexOf'}', left + ) ) ) {
        throw new QueryException"Unmatched braces for alias path", sqlQuery );
      }

      String aliasPath = sqlQuery.substringleft + 1, right );
      int firstDot = aliasPath.indexOf'.' );
      if firstDot == -) {
        if context.isEntityAliasaliasPath ) ) {
          // it is a simple table alias {foo}
          result.appendaliasPath );
          aliasesFound++;
        
        else {
          // passing through anything we do not know : to support jdbc escape sequences HB-898
          result.append'{' ).append(aliasPath).append'}' );          
        }
      }
      else {
        String aliasName = aliasPath.substring(0, firstDot);
        boolean isCollection = context.isCollectionAliasaliasName );
        boolean isEntity = context.isEntityAliasaliasName );
        
        if isCollection ) {
          // The current alias is referencing the collection to be eagerly fetched
          String propertyName = aliasPath.substringfirstDot + );
          result.appendresolveCollectionPropertiesaliasName, propertyName ) );
          aliasesFound++;
        
        else if isEntity ) {
          // it is a property reference {foo.bar}
          String propertyName = aliasPath.substringfirstDot + );
          result.appendresolvePropertiesaliasName, propertyName ) );
          aliasesFound++;
        }
        else {
          // passing through anything we do not know : to support jdbc escape sequences HB-898
          result.append'{' ).append(aliasPath).append'}' );
        }
  
      }
    }

    // Possibly handle :something parameters for the query ?

    return result.toString();
  }  

  private String resolveCollectionProperties(
      String aliasName,
      String propertyName) {

    Map fieldResults = context.getPropertyResultsMapByAliasaliasName );
    SQLLoadableCollection collectionPersister = context.getCollectionPersisterByAliasaliasName );
    String collectionSuffix = context.getCollectionSuffixByAliasaliasName );

    if "*".equalspropertyName ) ) {
      if!fieldResults.isEmpty() ) {
        throw new QueryException("Using return-propertys together with * syntax is not supported.");
      }
      
      String selectFragment = collectionPersister.selectFragmentaliasName, collectionSuffix );
      aliasesFound++;
      return selectFragment 
            ", " 
            + resolvePropertiesaliasName, propertyName );
    }
    else if "element.*".equalspropertyName ) ) {
      return resolvePropertiesaliasName, "*" );
    }
    else {
      String[] columnAliases;

      // Let return-propertys override whatever the persister has for aliases.
      columnAliases = String[] ) fieldResults.get(propertyName);
      if columnAliases==null ) {
        columnAliases = collectionPersister.getCollectionPropertyColumnAliasespropertyName, collectionSuffix );
      }
      
      if columnAliases == null || columnAliases.length == ) {
        throw new QueryException(
            "No column name found for property [" + propertyName + "] for alias [" + aliasName + "]",
            originalQueryString
        );
      }
      if columnAliases.length != ) {
        // TODO: better error message since we actually support composites if names are explicitly listed.
        throw new QueryException(
            "SQL queries only support properties mapped to a single column - property [" +
            propertyName + "] is mapped to " + columnAliases.length + " columns.",
            originalQueryString
        );
      }
      aliasesFound++;
      return columnAliases[0];
    
    }
  }
  private String resolveProperties(
      String aliasName,
          String propertyName) {
    Map fieldResults = context.getPropertyResultsMapByAliasaliasName );
    SQLLoadable persister = context.getEntityPersisterByAliasaliasName );
    String suffix = context.getEntitySuffixByAliasaliasName );

    if "*".equalspropertyName ) ) {
      if!fieldResults.isEmpty() ) {
        throw new QueryException("Using return-propertys together with * syntax is not supported.");
      }      
      aliasesFound++;
      return persister.selectFragmentaliasName, suffix ;
    }
    else {

      String[] columnAliases;

      // Let return-propertys override whatever the persister has for aliases.
      columnAliases = (String[]) fieldResults.getpropertyName );
      if columnAliases == null ) {
        columnAliases = persister.getSubclassPropertyColumnAliasespropertyName, suffix );
      }

      if columnAliases == null || columnAliases.length == ) {
        throw new QueryException(
            "No column name found for property [" + propertyName + "] for alias [" + aliasName + "]",
            originalQueryString
        );
      }
      if columnAliases.length != ) {
        // TODO: better error message since we actually support composites if names are explicitly listed.
        throw new QueryException(
            "SQL queries only support properties mapped to a single column - property [" + propertyName + "] is mapped to " + columnAliases.length + " columns.",
            originalQueryString
        );
      }      
      aliasesFound++;
      return columnAliases[0];
    }
  }

  /**
   * Substitues JDBC parameter placeholders (?) for all encountered
   * parameter specifications.  It also tracks the positions of these
   * parameter specifications within the query string.  This accounts for
   * ordinal-params, named-params, and ejb3-positional-params.
   *
   @param sqlString The query string.
   @return The SQL query with parameter substitution complete.
   */
  private String substituteParams(String sqlString) {
    ParameterSubstitutionRecognizer recognizer = new ParameterSubstitutionRecognizer();
    ParameterParser.parsesqlString, recognizer );

    namedParameters.clear();
    namedParameters.putAllrecognizer.namedParameterBindPoints );

    return recognizer.result.toString();
  }

  public static class ParameterSubstitutionRecognizer implements ParameterParser.Recognizer {
    StringBuffer result = new StringBuffer();
    Map namedParameterBindPoints = new HashMap();
    int parameterCount = 0;

    public void outParameter(int position) {
      result.append'?' );
    }

    public void ordinalParameter(int position) {
      result.append'?' );
    }

    public void namedParameter(String name, int position) {
      addNamedParametername );
      result.append'?' );
    }

    public void jpaPositionalParameter(String name, int position) {
      namedParametername, position );
    }

    public void other(char character) {
      result.appendcharacter );
    }

    private void addNamedParameter(String name) {
      Integer loc = new IntegerparameterCount++ );
      Object o = namedParameterBindPoints.getname );
      if o == null ) {
        namedParameterBindPoints.putname, loc );
      }
      else if instanceof Integer ) {
        ArrayList list = new ArrayList);
        list.add);
        list.addloc );
        namedParameterBindPoints.putname, list );
      }
      else {
        ( ( List ).addloc );
      }
    }
  }
}