Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » hql » ast » [javadoc | source]
    1   /*
    2    * Hibernate, Relational Persistence for Idiomatic Java
    3    *
    4    * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
    5    * indicated by the @author tags or express copyright attribution
    6    * statements applied by the authors.  All third-party contributions are
    7    * distributed under license by Red Hat Middleware LLC.
    8    *
    9    * This copyrighted material is made available to anyone wishing to use, modify,
   10    * copy, or redistribute it subject to the terms and conditions of the GNU
   11    * Lesser General Public License, as published by the Free Software Foundation.
   12    *
   13    * This program is distributed in the hope that it will be useful,
   14    * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15    * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
   16    * for more details.
   17    *
   18    * You should have received a copy of the GNU Lesser General Public License
   19    * along with this distribution; if not, write to:
   20    * Free Software Foundation, Inc.
   21    * 51 Franklin Street, Fifth Floor
   22    * Boston, MA  02110-1301  USA
   23    *
   24    */
   25   package org.hibernate.hql.ast;
   26   
   27   import antlr.ANTLRException;
   28   import antlr.RecognitionException;
   29   import antlr.TokenStreamException;
   30   import antlr.collections.AST;
   31   import org.slf4j.Logger;
   32   import org.slf4j.LoggerFactory;
   33   import org.hibernate.HibernateException;
   34   import org.hibernate.MappingException;
   35   import org.hibernate.QueryException;
   36   import org.hibernate.ScrollableResults;
   37   import org.hibernate.engine.QueryParameters;
   38   import org.hibernate.engine.RowSelection;
   39   import org.hibernate.engine.SessionFactoryImplementor;
   40   import org.hibernate.engine.SessionImplementor;
   41   import org.hibernate.event.EventSource;
   42   import org.hibernate.hql.FilterTranslator;
   43   import org.hibernate.hql.QueryExecutionRequestException;
   44   import org.hibernate.hql.ParameterTranslations;
   45   import org.hibernate.hql.antlr.HqlSqlTokenTypes;
   46   import org.hibernate.hql.antlr.HqlTokenTypes;
   47   import org.hibernate.hql.antlr.SqlTokenTypes;
   48   import org.hibernate.hql.ast.exec.BasicExecutor;
   49   import org.hibernate.hql.ast.exec.MultiTableDeleteExecutor;
   50   import org.hibernate.hql.ast.exec.MultiTableUpdateExecutor;
   51   import org.hibernate.hql.ast.exec.StatementExecutor;
   52   import org.hibernate.hql.ast.tree.FromElement;
   53   import org.hibernate.hql.ast.tree.InsertStatement;
   54   import org.hibernate.hql.ast.tree.QueryNode;
   55   import org.hibernate.hql.ast.tree.Statement;
   56   import org.hibernate.hql.ast.util.ASTPrinter;
   57   import org.hibernate.hql.ast.util.NodeTraverser;
   58   import org.hibernate.hql.ast.util.ASTUtil;
   59   import org.hibernate.loader.hql.QueryLoader;
   60   import org.hibernate.persister.entity.Queryable;
   61   import org.hibernate.type.Type;
   62   import org.hibernate.util.IdentitySet;
   63   import org.hibernate.util.StringHelper;
   64   import org.hibernate.util.ReflectHelper;
   65   
   66   import java.util.HashMap;
   67   import java.util.Iterator;
   68   import java.util.List;
   69   import java.util.Map;
   70   import java.util.Set;
   71   import java.util.ArrayList;
   72   
   73   /**
   74    * A QueryTranslator that uses an Antlr-based parser.
   75    *
   76    * @author Joshua Davis (pgmjsd@sourceforge.net)
   77    */
   78   public class QueryTranslatorImpl implements FilterTranslator {
   79   
   80   	private static final Logger log = LoggerFactory.getLogger( QueryTranslatorImpl.class );
   81   	private static final Logger AST_LOG = LoggerFactory.getLogger( "org.hibernate.hql.ast.AST" );
   82   
   83   	private SessionFactoryImplementor factory;
   84   
   85   	private final String queryIdentifier;
   86   	private String hql;
   87   	private boolean shallowQuery;
   88   	private Map tokenReplacements;
   89   
   90   	private Map enabledFilters; //TODO:this is only needed during compilation .. can we eliminate the instvar?
   91   
   92   	private boolean compiled;
   93   	private QueryLoader queryLoader;
   94   	private StatementExecutor statementExecutor;
   95   
   96   	private Statement sqlAst;
   97   	private String sql;
   98   
   99   	private ParameterTranslations paramTranslations;
  100   
  101   	/**
  102   	 * Creates a new AST-based query translator.
  103   	 *
  104   	 * @param queryIdentifier The query-identifier (used in stats collection)
  105   	 * @param query The hql query to translate
  106   	 * @param enabledFilters Currently enabled filters
  107   	 * @param factory The session factory constructing this translator instance.
  108   	 */
  109   	public QueryTranslatorImpl(
  110   			String queryIdentifier,
  111   	        String query,
  112   	        Map enabledFilters,
  113   	        SessionFactoryImplementor factory) {
  114   		this.queryIdentifier = queryIdentifier;
  115   		this.hql = query;
  116   		this.compiled = false;
  117   		this.shallowQuery = false;
  118   		this.enabledFilters = enabledFilters;
  119   		this.factory = factory;
  120   	}
  121   
  122   	/**
  123   	 * Compile a "normal" query. This method may be called multiple
  124   	 * times. Subsequent invocations are no-ops.
  125   	 *
  126   	 * @param replacements Defined query substitutions.
  127   	 * @param shallow      Does this represent a shallow (scalar or entity-id) select?
  128   	 * @throws QueryException   There was a problem parsing the query string.
  129   	 * @throws MappingException There was a problem querying defined mappings.
  130   	 */
  131   	public void compile(
  132   	        Map replacements,
  133   	        boolean shallow) throws QueryException, MappingException {
  134   		doCompile( replacements, shallow, null );
  135   	}
  136   
  137   	/**
  138   	 * Compile a filter. This method may be called multiple
  139   	 * times. Subsequent invocations are no-ops.
  140   	 *
  141   	 * @param collectionRole the role name of the collection used as the basis for the filter.
  142   	 * @param replacements   Defined query substitutions.
  143   	 * @param shallow        Does this represent a shallow (scalar or entity-id) select?
  144   	 * @throws QueryException   There was a problem parsing the query string.
  145   	 * @throws MappingException There was a problem querying defined mappings.
  146   	 */
  147   	public void compile(
  148   	        String collectionRole,
  149   	        Map replacements,
  150   	        boolean shallow) throws QueryException, MappingException {
  151   		doCompile( replacements, shallow, collectionRole );
  152   	}
  153   
  154   	/**
  155   	 * Performs both filter and non-filter compiling.
  156   	 *
  157   	 * @param replacements   Defined query substitutions.
  158   	 * @param shallow        Does this represent a shallow (scalar or entity-id) select?
  159   	 * @param collectionRole the role name of the collection used as the basis for the filter, NULL if this
  160   	 *                       is not a filter.
  161   	 */
  162   	private synchronized void doCompile(Map replacements, boolean shallow, String collectionRole) {
  163   		// If the query is already compiled, skip the compilation.
  164   		if ( compiled ) {
  165   			if ( log.isDebugEnabled() ) {
  166   				log.debug( "compile() : The query is already compiled, skipping..." );
  167   			}
  168   			return;
  169   		}
  170   
  171   		// Remember the parameters for the compilation.
  172   		this.tokenReplacements = replacements;
  173   		if ( tokenReplacements == null ) {
  174   			tokenReplacements = new HashMap();
  175   		}
  176   		this.shallowQuery = shallow;
  177   
  178   		try {
  179   			// PHASE 1 : Parse the HQL into an AST.
  180   			HqlParser parser = parse( true );
  181   
  182   			// PHASE 2 : Analyze the HQL AST, and produce an SQL AST.
  183   			HqlSqlWalker w = analyze( parser, collectionRole );
  184   
  185   			sqlAst = ( Statement ) w.getAST();
  186   
  187   			// at some point the generate phase needs to be moved out of here,
  188   			// because a single object-level DML might spawn multiple SQL DML
  189   			// command executions.
  190   			//
  191   			// Possible to just move the sql generation for dml stuff, but for
  192   			// consistency-sake probably best to just move responsiblity for
  193   			// the generation phase completely into the delegates
  194   			// (QueryLoader/StatementExecutor) themselves.  Also, not sure why
  195   			// QueryLoader currently even has a dependency on this at all; does
  196   			// it need it?  Ideally like to see the walker itself given to the delegates directly...
  197   
  198   			if ( sqlAst.needsExecutor() ) {
  199   				statementExecutor = buildAppropriateStatementExecutor( w );
  200   			}
  201   			else {
  202   				// PHASE 3 : Generate the SQL.
  203   				generate( ( QueryNode ) sqlAst );
  204   				queryLoader = new QueryLoader( this, factory, w.getSelectClause() );
  205   			}
  206   
  207   			compiled = true;
  208   		}
  209   		catch ( QueryException qe ) {
  210   			qe.setQueryString( hql );
  211   			throw qe;
  212   		}
  213   		catch ( RecognitionException e ) {
  214   			// we do not actually propogate ANTLRExceptions as a cause, so
  215   			// log it here for diagnostic purposes
  216   			if ( log.isTraceEnabled() ) {
  217   				log.trace( "converted antlr.RecognitionException", e );
  218   			}
  219   			throw QuerySyntaxException.convert( e, hql );
  220   		}
  221   		catch ( ANTLRException e ) {
  222   			// we do not actually propogate ANTLRExceptions as a cause, so
  223   			// log it here for diagnostic purposes
  224   			if ( log.isTraceEnabled() ) {
  225   				log.trace( "converted antlr.ANTLRException", e );
  226   			}
  227   			throw new QueryException( e.getMessage(), hql );
  228   		}
  229   
  230   		this.enabledFilters = null; //only needed during compilation phase...
  231   	}
  232   
  233   	private void generate(AST sqlAst) throws QueryException, RecognitionException {
  234   		if ( sql == null ) {
  235   			SqlGenerator gen = new SqlGenerator(factory);
  236   			gen.statement( sqlAst );
  237   			sql = gen.getSQL();
  238   			if ( log.isDebugEnabled() ) {
  239   				log.debug( "HQL: " + hql );
  240   				log.debug( "SQL: " + sql );
  241   			}
  242   			gen.getParseErrorHandler().throwQueryException();
  243   		}
  244   	}
  245   
  246   	private HqlSqlWalker analyze(HqlParser parser, String collectionRole) throws QueryException, RecognitionException {
  247   		HqlSqlWalker w = new HqlSqlWalker( this, factory, parser, tokenReplacements, collectionRole );
  248   		AST hqlAst = parser.getAST();
  249   
  250   		// Transform the tree.
  251   		w.statement( hqlAst );
  252   
  253   		if ( AST_LOG.isDebugEnabled() ) {
  254   			ASTPrinter printer = new ASTPrinter( SqlTokenTypes.class );
  255   			AST_LOG.debug( printer.showAsString( w.getAST(), "--- SQL AST ---" ) );
  256   		}
  257   
  258   		w.getParseErrorHandler().throwQueryException();
  259   
  260   		return w;
  261   	}
  262   
  263   	private HqlParser parse(boolean filter) throws TokenStreamException, RecognitionException {
  264   		// Parse the query string into an HQL AST.
  265   		HqlParser parser = HqlParser.getInstance( hql );
  266   		parser.setFilter( filter );
  267   
  268   		if ( log.isDebugEnabled() ) {
  269   			log.debug( "parse() - HQL: " + hql );
  270   		}
  271   		parser.statement();
  272   
  273   		AST hqlAst = parser.getAST();
  274   
  275   		JavaConstantConverter converter = new JavaConstantConverter();
  276   		NodeTraverser walker = new NodeTraverser( converter );
  277   		walker.traverseDepthFirst( hqlAst );
  278   
  279   		showHqlAst( hqlAst );
  280   
  281   		parser.getParseErrorHandler().throwQueryException();
  282   		return parser;
  283   	}
  284   
  285   	void showHqlAst(AST hqlAst) {
  286   		if ( AST_LOG.isDebugEnabled() ) {
  287   			ASTPrinter printer = new ASTPrinter( HqlTokenTypes.class );
  288   			printer.setShowClassNames( false ); // The class names aren't interesting in the first tree.
  289   			AST_LOG.debug( printer.showAsString( hqlAst, "--- HQL AST ---" ) );
  290   		}
  291   	}
  292   
  293   	private void errorIfDML() throws HibernateException {
  294   		if ( sqlAst.needsExecutor() ) {
  295   			throw new QueryExecutionRequestException( "Not supported for DML operations", hql );
  296   		}
  297   	}
  298   
  299   	private void errorIfSelect() throws HibernateException {
  300   		if ( !sqlAst.needsExecutor() ) {
  301   			throw new QueryExecutionRequestException( "Not supported for select queries", hql );
  302   		}
  303   	}
  304   
  305   	public String getQueryIdentifier() {
  306   		return queryIdentifier;
  307   	}
  308   
  309   	public Statement getSqlAST() {
  310   		return sqlAst;
  311   	}
  312   
  313   	private HqlSqlWalker getWalker() {
  314   		return sqlAst.getWalker();
  315   	}
  316   
  317   	/**
  318   	 * Types of the return values of an <tt>iterate()</tt> style query.
  319   	 *
  320   	 * @return an array of <tt>Type</tt>s.
  321   	 */
  322   	public Type[] getReturnTypes() {
  323   		errorIfDML();
  324   		return getWalker().getReturnTypes();
  325   	}
  326   
  327   	public String[] getReturnAliases() {
  328   		errorIfDML();
  329   		return getWalker().getReturnAliases();
  330   	}
  331   
  332   	public String[][] getColumnNames() {
  333   		errorIfDML();
  334   		return getWalker().getSelectClause().getColumnNames();
  335   	}
  336   
  337   	public Set getQuerySpaces() {
  338   		return getWalker().getQuerySpaces();
  339   	}
  340   
  341   	public List list(SessionImplementor session, QueryParameters queryParameters)
  342   			throws HibernateException {
  343   		// Delegate to the QueryLoader...
  344   		errorIfDML();
  345   		QueryNode query = ( QueryNode ) sqlAst;
  346   		boolean hasLimit = queryParameters.getRowSelection() != null && queryParameters.getRowSelection().definesLimits();
  347   		boolean needsDistincting = ( query.getSelectClause().isDistinct() || hasLimit ) && containsCollectionFetches();
  348   
  349   		QueryParameters queryParametersToUse;
  350   		if ( hasLimit && containsCollectionFetches() ) {
  351   			log.warn( "firstResult/maxResults specified with collection fetch; applying in memory!" );
  352   			RowSelection selection = new RowSelection();
  353   			selection.setFetchSize( queryParameters.getRowSelection().getFetchSize() );
  354   			selection.setTimeout( queryParameters.getRowSelection().getTimeout() );
  355   			queryParametersToUse = queryParameters.createCopyUsing( selection );
  356   		}
  357   		else {
  358   			queryParametersToUse = queryParameters;
  359   		}
  360   
  361   		List results = queryLoader.list( session, queryParametersToUse );
  362   
  363   		if ( needsDistincting ) {
  364   			int includedCount = -1;
  365   			// NOTE : firstRow is zero-based
  366   			int first = !hasLimit || queryParameters.getRowSelection().getFirstRow() == null
  367   						? 0
  368   						: queryParameters.getRowSelection().getFirstRow().intValue();
  369   			int max = !hasLimit || queryParameters.getRowSelection().getMaxRows() == null
  370   						? -1
  371   						: queryParameters.getRowSelection().getMaxRows().intValue();
  372   			int size = results.size();
  373   			List tmp = new ArrayList();
  374   			IdentitySet distinction = new IdentitySet();
  375   			for ( int i = 0; i < size; i++ ) {
  376   				final Object result = results.get( i );
  377   				if ( !distinction.add( result ) ) {
  378   					continue;
  379   				}
  380   				includedCount++;
  381   				if ( includedCount < first ) {
  382   					continue;
  383   				}
  384   				tmp.add( result );
  385   				// NOTE : ( max - 1 ) because first is zero-based while max is not...
  386   				if ( max >= 0 && ( includedCount - first ) >= ( max - 1 ) ) {
  387   					break;
  388   				}
  389   			}
  390   			results = tmp;
  391   		}
  392   
  393   		return results;
  394   	}
  395   
  396   	/**
  397   	 * Return the query results as an iterator
  398   	 */
  399   	public Iterator iterate(QueryParameters queryParameters, EventSource session)
  400   			throws HibernateException {
  401   		// Delegate to the QueryLoader...
  402   		errorIfDML();
  403   		return queryLoader.iterate( queryParameters, session );
  404   	}
  405   
  406   	/**
  407   	 * Return the query results, as an instance of <tt>ScrollableResults</tt>
  408   	 */
  409   	public ScrollableResults scroll(QueryParameters queryParameters, SessionImplementor session)
  410   			throws HibernateException {
  411   		// Delegate to the QueryLoader...
  412   		errorIfDML();
  413   		return queryLoader.scroll( queryParameters, session );
  414   	}
  415   
  416   	public int executeUpdate(QueryParameters queryParameters, SessionImplementor session)
  417   			throws HibernateException {
  418   		errorIfSelect();
  419   		return statementExecutor.execute( queryParameters, session );
  420   	}
  421   
  422   	/**
  423   	 * The SQL query string to be called; implemented by all subclasses
  424   	 */
  425   	public String getSQLString() {
  426   		return sql;
  427   	}
  428   
  429   	public List collectSqlStrings() {
  430   		ArrayList list = new ArrayList();
  431   		if ( isManipulationStatement() ) {
  432   			String[] sqlStatements = statementExecutor.getSqlStatements();
  433   			for ( int i = 0; i < sqlStatements.length; i++ ) {
  434   				list.add( sqlStatements[i] );
  435   			}
  436   		}
  437   		else {
  438   			list.add( sql );
  439   		}
  440   		return list;
  441   	}
  442   
  443   	// -- Package local methods for the QueryLoader delegate --
  444   
  445   	public boolean isShallowQuery() {
  446   		return shallowQuery;
  447   	}
  448   
  449   	public String getQueryString() {
  450   		return hql;
  451   	}
  452   
  453   	public Map getEnabledFilters() {
  454   		return enabledFilters;
  455   	}
  456   
  457   	public int[] getNamedParameterLocs(String name) {
  458   		return getWalker().getNamedParameterLocations( name );
  459   	}
  460   
  461   	public boolean containsCollectionFetches() {
  462   		errorIfDML();
  463   		List collectionFetches = ( ( QueryNode ) sqlAst ).getFromClause().getCollectionFetches();
  464   		return collectionFetches != null && collectionFetches.size() > 0;
  465   	}
  466   
  467   	public boolean isManipulationStatement() {
  468   		return sqlAst.needsExecutor();
  469   	}
  470   
  471   	public void validateScrollability() throws HibernateException {
  472   		// Impl Note: allows multiple collection fetches as long as the
  473   		// entire fecthed graph still "points back" to a single
  474   		// root entity for return
  475   
  476   		errorIfDML();
  477   
  478   		QueryNode query = ( QueryNode ) sqlAst;
  479   
  480   		// If there are no collection fetches, then no further checks are needed
  481   		List collectionFetches = query.getFromClause().getCollectionFetches();
  482   		if ( collectionFetches.isEmpty() ) {
  483   			return;
  484   		}
  485   
  486   		// A shallow query is ok (although technically there should be no fetching here...)
  487   		if ( isShallowQuery() ) {
  488   			return;
  489   		}
  490   
  491   		// Otherwise, we have a non-scalar select with defined collection fetch(es).
  492   		// Make sure that there is only a single root entity in the return (no tuples)
  493   		if ( getReturnTypes().length > 1 ) {
  494   			throw new HibernateException( "cannot scroll with collection fetches and returned tuples" );
  495   		}
  496   
  497   		FromElement owner = null;
  498   		Iterator itr = query.getSelectClause().getFromElementsForLoad().iterator();
  499   		while ( itr.hasNext() ) {
  500   			// should be the first, but just to be safe...
  501   			final FromElement fromElement = ( FromElement ) itr.next();
  502   			if ( fromElement.getOrigin() == null ) {
  503   				owner = fromElement;
  504   				break;
  505   			}
  506   		}
  507   
  508   		if ( owner == null ) {
  509   			throw new HibernateException( "unable to locate collection fetch(es) owner for scrollability checks" );
  510   		}
  511   
  512   		// This is not strictly true.  We actually just need to make sure that
  513   		// it is ordered by root-entity PK and that that order-by comes before
  514   		// any non-root-entity ordering...
  515   
  516   		AST primaryOrdering = query.getOrderByClause().getFirstChild();
  517   		if ( primaryOrdering != null ) {
  518   			// TODO : this is a bit dodgy, come up with a better way to check this (plus see above comment)
  519   			String [] idColNames = owner.getQueryable().getIdentifierColumnNames();
  520   			String expectedPrimaryOrderSeq = StringHelper.join(
  521   			        ", ",
  522   			        StringHelper.qualify( owner.getTableAlias(), idColNames )
  523   			);
  524   			if (  !primaryOrdering.getText().startsWith( expectedPrimaryOrderSeq ) ) {
  525   				throw new HibernateException( "cannot scroll results with collection fetches which are not ordered primarily by the root entity's PK" );
  526   			}
  527   		}
  528   	}
  529   
  530   	private StatementExecutor buildAppropriateStatementExecutor(HqlSqlWalker walker) {
  531   		Statement statement = ( Statement ) walker.getAST();
  532   		if ( walker.getStatementType() == HqlSqlTokenTypes.DELETE ) {
  533   			FromElement fromElement = walker.getFinalFromClause().getFromElement();
  534   			Queryable persister = fromElement.getQueryable();
  535   			if ( persister.isMultiTable() ) {
  536   				return new MultiTableDeleteExecutor( walker );
  537   			}
  538   			else {
  539   				return new BasicExecutor( walker, persister );
  540   			}
  541   		}
  542   		else if ( walker.getStatementType() == HqlSqlTokenTypes.UPDATE ) {
  543   			FromElement fromElement = walker.getFinalFromClause().getFromElement();
  544   			Queryable persister = fromElement.getQueryable();
  545   			if ( persister.isMultiTable() ) {
  546   				// even here, if only properties mapped to the "base table" are referenced
  547   				// in the set and where clauses, this could be handled by the BasicDelegate.
  548   				// TODO : decide if it is better performance-wise to perform that check, or to simply use the MultiTableUpdateDelegate
  549   				return new MultiTableUpdateExecutor( walker );
  550   			}
  551   			else {
  552   				return new BasicExecutor( walker, persister );
  553   			}
  554   		}
  555   		else if ( walker.getStatementType() == HqlSqlTokenTypes.INSERT ) {
  556   			return new BasicExecutor( walker, ( ( InsertStatement ) statement ).getIntoClause().getQueryable() );
  557   		}
  558   		else {
  559   			throw new QueryException( "Unexpected statement type" );
  560   		}
  561   	}
  562   
  563   	public ParameterTranslations getParameterTranslations() {
  564   		if ( paramTranslations == null ) {
  565   			paramTranslations = new ParameterTranslationsImpl( getWalker().getParameters() );
  566   		}
  567   		return paramTranslations;
  568   	}
  569   
  570   	public static class JavaConstantConverter implements NodeTraverser.VisitationStrategy {
  571   		private AST dotRoot;
  572   		public void visit(AST node) {
  573   			if ( dotRoot != null ) {
  574   				// we are already processing a dot-structure
  575   				if ( ASTUtil.isSubtreeChild( dotRoot, node ) ) {
  576   					// igndore it...
  577   					return;
  578   				}
  579   				else {
  580   					// we are now at a new tree level
  581   					dotRoot = null;
  582   				}
  583   			}
  584   
  585   			if ( dotRoot == null && node.getType() == HqlTokenTypes.DOT ) {
  586   				dotRoot = node;
  587   				handleDotStructure( dotRoot );
  588   			}
  589   		}
  590   		private void handleDotStructure(AST dotStructureRoot) {
  591   			String expression = ASTUtil.getPathText( dotStructureRoot );
  592   			Object constant = ReflectHelper.getConstantValue( expression );
  593   			if ( constant != null ) {
  594   				dotStructureRoot.setFirstChild( null );
  595   				dotStructureRoot.setType( HqlTokenTypes.JAVA_CONSTANT );
  596   				dotStructureRoot.setText( expression );
  597   			}
  598   		}
  599   	}
  600   }

Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » hql » ast » [javadoc | source]