FileDocCategorySizeDatePackage
QueryTranslatorImpl.javaAPI DocHibernate 3.2.519010Thu Jun 08 16:04:46 BST 2006org.hibernate.hql.ast

QueryTranslatorImpl

public class QueryTranslatorImpl extends Object implements org.hibernate.hql.FilterTranslator
A QueryTranslator that uses an Antlr-based parser.
author
Joshua Davis (pgmjsd@sourceforge.net)

Fields Summary
private static final Log
log
private static final Log
AST_LOG
private org.hibernate.engine.SessionFactoryImplementor
factory
private final String
queryIdentifier
private String
hql
private boolean
shallowQuery
private Map
tokenReplacements
private Map
enabledFilters
private boolean
compiled
private org.hibernate.loader.hql.QueryLoader
queryLoader
private org.hibernate.hql.ast.exec.StatementExecutor
statementExecutor
private org.hibernate.hql.ast.tree.Statement
sqlAst
private String
sql
private org.hibernate.hql.ParameterTranslations
paramTranslations
Constructors Summary
public QueryTranslatorImpl(String queryIdentifier, String query, Map enabledFilters, org.hibernate.engine.SessionFactoryImplementor factory)
Creates a new AST-based query translator.

param
queryIdentifier The query-identifier (used in stats collection)
param
query The hql query to translate
param
enabledFilters Currently enabled filters
param
factory The session factory constructing this translator instance.


	                                   	 
	 
			 
	         
	         
	          
		this.queryIdentifier = queryIdentifier;
		this.hql = query;
		this.compiled = false;
		this.shallowQuery = false;
		this.enabledFilters = enabledFilters;
		this.factory = factory;
	
Methods Summary
private HqlSqlWalkeranalyze(HqlParser parser, java.lang.String collectionRole)

		HqlSqlWalker w = new HqlSqlWalker( this, factory, parser, tokenReplacements, collectionRole );
		AST hqlAst = parser.getAST();

		// Transform the tree.
		w.statement( hqlAst );

		if ( AST_LOG.isDebugEnabled() ) {
			ASTPrinter printer = new ASTPrinter( SqlTokenTypes.class );
			AST_LOG.debug( printer.showAsString( w.getAST(), "--- SQL AST ---" ) );
		}

		w.getParseErrorHandler().throwQueryException();

		return w;
	
private org.hibernate.hql.ast.exec.StatementExecutorbuildAppropriateStatementExecutor(HqlSqlWalker walker)

		Statement statement = ( Statement ) walker.getAST();
		if ( walker.getStatementType() == HqlSqlTokenTypes.DELETE ) {
			FromElement fromElement = walker.getFinalFromClause().getFromElement();
			Queryable persister = fromElement.getQueryable();
			if ( persister.isMultiTable() ) {
				return new MultiTableDeleteExecutor( walker );
			}
			else {
				return new BasicExecutor( walker, persister );
			}
		}
		else if ( walker.getStatementType() == HqlSqlTokenTypes.UPDATE ) {
			FromElement fromElement = walker.getFinalFromClause().getFromElement();
			Queryable persister = fromElement.getQueryable();
			if ( persister.isMultiTable() ) {
				// even here, if only properties mapped to the "base table" are referenced
				// in the set and where clauses, this could be handled by the BasicDelegate.
				// TODO : decide if it is better performance-wise to perform that check, or to simply use the MultiTableUpdateDelegate
				return new MultiTableUpdateExecutor( walker );
			}
			else {
				return new BasicExecutor( walker, persister );
			}
		}
		else if ( walker.getStatementType() == HqlSqlTokenTypes.INSERT ) {
			return new BasicExecutor( walker, ( ( InsertStatement ) statement ).getIntoClause().getQueryable() );
		}
		else {
			throw new QueryException( "Unexpected statement type" );
		}
	
public java.util.ListcollectSqlStrings()

		ArrayList list = new ArrayList();
		if ( isManipulationStatement() ) {
			String[] sqlStatements = statementExecutor.getSqlStatements();
			for ( int i = 0; i < sqlStatements.length; i++ ) {
				list.add( sqlStatements[i] );
			}
		}
		else {
			list.add( sql );
		}
		return list;
	
public voidcompile(java.util.Map replacements, boolean shallow)
Compile a "normal" query. This method may be called multiple times. Subsequent invocations are no-ops.

param
replacements Defined query substitutions.
param
shallow Does this represent a shallow (scalar or entity-id) select?
throws
QueryException There was a problem parsing the query string.
throws
MappingException There was a problem querying defined mappings.

		doCompile( replacements, shallow, null );
	
public voidcompile(java.lang.String collectionRole, java.util.Map replacements, boolean shallow)
Compile a filter. This method may be called multiple times. Subsequent invocations are no-ops.

param
collectionRole the role name of the collection used as the basis for the filter.
param
replacements Defined query substitutions.
param
shallow Does this represent a shallow (scalar or entity-id) select?
throws
QueryException There was a problem parsing the query string.
throws
MappingException There was a problem querying defined mappings.

		doCompile( replacements, shallow, collectionRole );
	
public booleancontainsCollectionFetches()

		errorIfDML();
		List collectionFetches = ( ( QueryNode ) sqlAst ).getFromClause().getCollectionFetches();
		return collectionFetches != null && collectionFetches.size() > 0;
	
private synchronized voiddoCompile(java.util.Map replacements, boolean shallow, java.lang.String collectionRole)
Performs both filter and non-filter compiling.

param
replacements Defined query substitutions.
param
shallow Does this represent a shallow (scalar or entity-id) select?
param
collectionRole the role name of the collection used as the basis for the filter, NULL if this is not a filter.

		// If the query is already compiled, skip the compilation.
		if ( compiled ) {
			if ( log.isDebugEnabled() ) {
				log.debug( "compile() : The query is already compiled, skipping..." );
			}
			return;
		}

		// Remember the parameters for the compilation.
		this.tokenReplacements = replacements;
		if ( tokenReplacements == null ) {
			tokenReplacements = new HashMap();
		}
		this.shallowQuery = shallow;

		try {
			// PHASE 1 : Parse the HQL into an AST.
			HqlParser parser = parse( true );

			// PHASE 2 : Analyze the HQL AST, and produce an SQL AST.
			HqlSqlWalker w = analyze( parser, collectionRole );

			sqlAst = ( Statement ) w.getAST();

			// at some point the generate phase needs to be moved out of here,
			// because a single object-level DML might spawn multiple SQL DML
			// command executions.
			//
			// Possible to just move the sql generation for dml stuff, but for
			// consistency-sake probably best to just move responsiblity for
			// the generation phase completely into the delegates
			// (QueryLoader/StatementExecutor) themselves.  Also, not sure why
			// QueryLoader currently even has a dependency on this at all; does
			// it need it?  Ideally like to see the walker itself given to the delegates directly...

			if ( sqlAst.needsExecutor() ) {
				statementExecutor = buildAppropriateStatementExecutor( w );
			}
			else {
				// PHASE 3 : Generate the SQL.
				generate( ( QueryNode ) sqlAst );
				queryLoader = new QueryLoader( this, factory, w.getSelectClause() );
			}

			compiled = true;
		}
		catch ( QueryException qe ) {
			qe.setQueryString( hql );
			throw qe;
		}
		catch ( RecognitionException e ) {
			// we do not actually propogate ANTLRExceptions as a cause, so
			// log it here for diagnostic purposes
			if ( log.isTraceEnabled() ) {
				log.trace( "converted antlr.RecognitionException", e );
			}
			throw QuerySyntaxException.convert( e, hql );
		}
		catch ( ANTLRException e ) {
			// we do not actually propogate ANTLRExceptions as a cause, so
			// log it here for diagnostic purposes
			if ( log.isTraceEnabled() ) {
				log.trace( "converted antlr.ANTLRException", e );
			}
			throw new QueryException( e.getMessage(), hql );
		}

		this.enabledFilters = null; //only needed during compilation phase...
	
private voiderrorIfDML()

		if ( sqlAst.needsExecutor() ) {
			throw new QueryExecutionRequestException( "Not supported for DML operations", hql );
		}
	
private voiderrorIfSelect()

		if ( !sqlAst.needsExecutor() ) {
			throw new QueryExecutionRequestException( "Not supported for select queries", hql );
		}
	
public intexecuteUpdate(org.hibernate.engine.QueryParameters queryParameters, org.hibernate.engine.SessionImplementor session)

		errorIfSelect();
		return statementExecutor.execute( queryParameters, session );
	
private voidgenerate(antlr.collections.AST sqlAst)

		if ( sql == null ) {
			SqlGenerator gen = new SqlGenerator(factory);
			gen.statement( sqlAst );
			sql = gen.getSQL();
			if ( log.isDebugEnabled() ) {
				log.debug( "HQL: " + hql );
				log.debug( "SQL: " + sql );
			}
			gen.getParseErrorHandler().throwQueryException();
		}
	
public java.lang.String[][]getColumnNames()

		errorIfDML();
		return getWalker().getSelectClause().getColumnNames();
	
public java.util.MapgetEnabledFilters()

		return enabledFilters;
	
public int[]getNamedParameterLocs(java.lang.String name)

		return getWalker().getNamedParameterLocations( name );
	
public org.hibernate.hql.ParameterTranslationsgetParameterTranslations()

		if ( paramTranslations == null ) {
			paramTranslations = new ParameterTranslationsImpl( getWalker().getParameters() );
		}
		return paramTranslations;
	
public java.lang.StringgetQueryIdentifier()

		return queryIdentifier;
	
public java.util.SetgetQuerySpaces()

		return getWalker().getQuerySpaces();
	
public java.lang.StringgetQueryString()

		return hql;
	
public java.lang.String[]getReturnAliases()

		errorIfDML();
		return getWalker().getReturnAliases();
	
public org.hibernate.type.Type[]getReturnTypes()
Types of the return values of an iterate() style query.

return
an array of Types.

		errorIfDML();
		return getWalker().getReturnTypes();
	
public java.lang.StringgetSQLString()
The SQL query string to be called; implemented by all subclasses

		return sql;
	
public org.hibernate.hql.ast.tree.StatementgetSqlAST()

		return sqlAst;
	
private HqlSqlWalkergetWalker()

		return sqlAst.getWalker();
	
public booleanisManipulationStatement()

		return sqlAst.needsExecutor();
	
public booleanisShallowQuery()

		return shallowQuery;
	
public java.util.Iteratoriterate(org.hibernate.engine.QueryParameters queryParameters, org.hibernate.event.EventSource session)
Return the query results as an iterator

		// Delegate to the QueryLoader...
		errorIfDML();
		return queryLoader.iterate( queryParameters, session );
	
public java.util.Listlist(org.hibernate.engine.SessionImplementor session, org.hibernate.engine.QueryParameters queryParameters)

		// Delegate to the QueryLoader...
		errorIfDML();
		QueryNode query = ( QueryNode ) sqlAst;
		boolean hasLimit = queryParameters.getRowSelection() != null && queryParameters.getRowSelection().definesLimits();
		boolean needsDistincting = ( query.getSelectClause().isDistinct() || hasLimit ) && containsCollectionFetches();

		QueryParameters queryParametersToUse;
		if ( hasLimit && containsCollectionFetches() ) {
			log.warn( "firstResult/maxResults specified with collection fetch; applying in memory!" );
			RowSelection selection = new RowSelection();
			selection.setFetchSize( queryParameters.getRowSelection().getFetchSize() );
			selection.setTimeout( queryParameters.getRowSelection().getTimeout() );
			queryParametersToUse = queryParameters.createCopyUsing( selection );
		}
		else {
			queryParametersToUse = queryParameters;
		}

		List results = queryLoader.list( session, queryParametersToUse );

		if ( needsDistincting ) {
			int includedCount = -1;
			// NOTE : firstRow is zero-based
			int first = !hasLimit || queryParameters.getRowSelection().getFirstRow() == null
						? 0
						: queryParameters.getRowSelection().getFirstRow().intValue();
			int max = !hasLimit || queryParameters.getRowSelection().getMaxRows() == null
						? -1
						: queryParameters.getRowSelection().getMaxRows().intValue();
			int size = results.size();
			List tmp = new ArrayList();
			IdentitySet distinction = new IdentitySet();
			for ( int i = 0; i < size; i++ ) {
				final Object result = results.get( i );
				if ( !distinction.add( result ) ) {
					continue;
				}
				includedCount++;
				if ( includedCount < first ) {
					continue;
				}
				tmp.add( result );
				// NOTE : ( max - 1 ) because first is zero-based while max is not...
				if ( max >= 0 && ( includedCount - first ) >= ( max - 1 ) ) {
					break;
				}
			}
			results = tmp;
		}

		return results;
	
private HqlParserparse(boolean filter)

		// Parse the query string into an HQL AST.
		HqlParser parser = HqlParser.getInstance( hql );
		parser.setFilter( filter );

		if ( log.isDebugEnabled() ) {
			log.debug( "parse() - HQL: " + hql );
		}
		parser.statement();

		AST hqlAst = parser.getAST();

		JavaConstantConverter converter = new JavaConstantConverter();
		NodeTraverser walker = new NodeTraverser( converter );
		walker.traverseDepthFirst( hqlAst );

		showHqlAst( hqlAst );

		parser.getParseErrorHandler().throwQueryException();
		return parser;
	
public org.hibernate.ScrollableResultsscroll(org.hibernate.engine.QueryParameters queryParameters, org.hibernate.engine.SessionImplementor session)
Return the query results, as an instance of ScrollableResults

		// Delegate to the QueryLoader...
		errorIfDML();
		return queryLoader.scroll( queryParameters, session );
	
voidshowHqlAst(antlr.collections.AST hqlAst)

		if ( AST_LOG.isDebugEnabled() ) {
			ASTPrinter printer = new ASTPrinter( HqlTokenTypes.class );
			printer.setShowClassNames( false ); // The class names aren't interesting in the first tree.
			AST_LOG.debug( printer.showAsString( hqlAst, "--- HQL AST ---" ) );
		}
	
public voidvalidateScrollability()

		// Impl Note: allows multiple collection fetches as long as the
		// entire fecthed graph still "points back" to a single
		// root entity for return

		errorIfDML();

		QueryNode query = ( QueryNode ) sqlAst;

		// If there are no collection fetches, then no further checks are needed
		List collectionFetches = query.getFromClause().getCollectionFetches();
		if ( collectionFetches.isEmpty() ) {
			return;
		}

		// A shallow query is ok (although technically there should be no fetching here...)
		if ( isShallowQuery() ) {
			return;
		}

		// Otherwise, we have a non-scalar select with defined collection fetch(es).
		// Make sure that there is only a single root entity in the return (no tuples)
		if ( getReturnTypes().length > 1 ) {
			throw new HibernateException( "cannot scroll with collection fetches and returned tuples" );
		}

		FromElement owner = null;
		Iterator itr = query.getSelectClause().getFromElementsForLoad().iterator();
		while ( itr.hasNext() ) {
			// should be the first, but just to be safe...
			final FromElement fromElement = ( FromElement ) itr.next();
			if ( fromElement.getOrigin() == null ) {
				owner = fromElement;
				break;
			}
		}

		if ( owner == null ) {
			throw new HibernateException( "unable to locate collection fetch(es) owner for scrollability checks" );
		}

		// This is not strictly true.  We actually just need to make sure that
		// it is ordered by root-entity PK and that that order-by comes before
		// any non-root-entity ordering...

		AST primaryOrdering = query.getOrderByClause().getFirstChild();
		if ( primaryOrdering != null ) {
			// TODO : this is a bit dodgy, come up with a better way to check this (plus see above comment)
			String [] idColNames = owner.getQueryable().getIdentifierColumnNames();
			String expectedPrimaryOrderSeq = StringHelper.join(
			        ", ",
			        StringHelper.qualify( owner.getTableAlias(), idColNames )
			);
			if (  !primaryOrdering.getText().startsWith( expectedPrimaryOrderSeq ) ) {
				throw new HibernateException( "cannot scroll results with collection fetches which are not ordered primarily by the root entity's PK" );
			}
		}