KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > hibernate > hql > ast > QueryTranslatorImpl


1 // $Id: QueryTranslatorImpl.java,v 1.62 2005/07/16 22:25:42 oneovthafew Exp $
2
package org.hibernate.hql.ast;
3
4 import antlr.ANTLRException;
5 import antlr.RecognitionException;
6 import antlr.TokenStreamException;
7 import antlr.collections.AST;
8 import org.apache.commons.logging.Log;
9 import org.apache.commons.logging.LogFactory;
10 import org.hibernate.HibernateException;
11 import org.hibernate.MappingException;
12 import org.hibernate.QueryException;
13 import org.hibernate.ScrollableResults;
14 import org.hibernate.engine.QueryParameters;
15 import org.hibernate.engine.SessionFactoryImplementor;
16 import org.hibernate.engine.SessionImplementor;
17 import org.hibernate.event.EventSource;
18 import org.hibernate.hql.FilterTranslator;
19 import org.hibernate.hql.antlr.HqlSqlTokenTypes;
20 import org.hibernate.hql.antlr.HqlTokenTypes;
21 import org.hibernate.hql.antlr.SqlTokenTypes;
22 import org.hibernate.hql.ast.exec.BasicExecutor;
23 import org.hibernate.hql.ast.exec.MultiTableDeleteExecutor;
24 import org.hibernate.hql.ast.exec.MultiTableUpdateExecutor;
25 import org.hibernate.hql.ast.exec.StatementExecutor;
26 import org.hibernate.hql.ast.tree.FromElement;
27 import org.hibernate.hql.ast.tree.InsertStatement;
28 import org.hibernate.hql.ast.tree.QueryNode;
29 import org.hibernate.hql.ast.tree.Statement;
30 import org.hibernate.hql.ast.util.ASTPrinter;
31 import org.hibernate.loader.hql.QueryLoader;
32 import org.hibernate.persister.entity.Queryable;
33 import org.hibernate.type.Type;
34 import org.hibernate.util.StringHelper;
35
36 import java.util.HashMap JavaDoc;
37 import java.util.Iterator JavaDoc;
38 import java.util.List JavaDoc;
39 import java.util.Map JavaDoc;
40 import java.util.Set JavaDoc;
41
42 /**
43  * A QueryTranslator that uses an AST based parser.
44  * <br>User: josh
45  * <br>Date: Dec 31, 2003
46  * <br>Time: 7:50:35 AM
47  *
48  * @author Joshua Davis (pgmjsd@sourceforge.net)
49  */

50 public class QueryTranslatorImpl implements FilterTranslator {
51
52     private static final Log log = LogFactory.getLog( QueryTranslatorImpl.class );
53     private static final Log AST_LOG = LogFactory.getLog( "org.hibernate.hql.ast.AST" );
54
55     private SessionFactoryImplementor factory;
56
57     private String JavaDoc hql;
58     private boolean shallowQuery;
59     private Map JavaDoc tokenReplacements;
60     private Map JavaDoc enabledFilters;
61
62     private boolean compiled;
63     private QueryLoader queryLoader;
64     private StatementExecutor statementExecutor;
65
66     private Statement sqlAst;
67     private String JavaDoc sql;
68
69
70     /**
71      * Creates a new AST-based query translator.
72      *
73      * @param query The HQL query string.
74      * @param enabledFilters Any filters currently enabled for the session.
75      * @param factory The session factory constructing this translator instance.
76      */

77     public QueryTranslatorImpl(
78             String JavaDoc query,
79             Map JavaDoc enabledFilters,
80             SessionFactoryImplementor factory) {
81         this.hql = query;
82         this.compiled = false;
83         this.shallowQuery = false;
84         this.enabledFilters = enabledFilters;
85         this.factory = factory;
86     }
87
88     /**
89      * Compile a "normal" query. This method may be called multiple
90      * times. Subsequent invocations are no-ops.
91      *
92      * @param replacements Defined query substitutions.
93      * @param shallow Does this represent a shallow (scalar or entity-id) select?
94      * @throws QueryException There was a problem parsing the query string.
95      * @throws MappingException There was a problem querying defined mappings.
96      */

97     public void compile(
98             Map JavaDoc replacements,
99             boolean shallow) throws QueryException, MappingException {
100         doCompile( replacements, shallow, null );
101     }
102
103     /**
104      * Compile a filter. This method may be called multiple
105      * times. Subsequent invocations are no-ops.
106      *
107      * @param collectionRole the role name of the collection used as the basis for the filter.
108      * @param replacements Defined query substitutions.
109      * @param shallow Does this represent a shallow (scalar or entity-id) select?
110      * @throws QueryException There was a problem parsing the query string.
111      * @throws MappingException There was a problem querying defined mappings.
112      */

113     public void compile(
114             String JavaDoc collectionRole,
115             Map JavaDoc replacements,
116             boolean shallow) throws QueryException, MappingException {
117         doCompile( replacements, shallow, collectionRole );
118     }
119
120     public Statement getSqlAST() {
121         return sqlAst;
122     }
123
124     /**
125      * Performs both filter and non-filter compiling.
126      *
127      * @param replacements Defined query substitutions.
128      * @param shallow Does this represent a shallow (scalar or entity-id) select?
129      * @param collectionRole the role name of the collection used as the basis for the filter, NULL if this
130      * is not a filter.
131      */

132     private synchronized void doCompile(Map JavaDoc replacements, boolean shallow, String JavaDoc collectionRole) {
133         // If the query is already compiled, skip the compilation.
134
if ( compiled ) {
135             if ( log.isDebugEnabled() ) {
136                 log.debug( "compile() : The query is already compiled, skipping..." );
137             }
138             return;
139         }
140
141         // Remember the parameters for the compilation.
142
this.tokenReplacements = replacements;
143         if ( tokenReplacements == null ) {
144             tokenReplacements = new HashMap JavaDoc();
145         }
146         this.shallowQuery = shallow;
147
148         try {
149             // PHASE 1 : Parse the HQL into an AST.
150
HqlParser parser = parse( true );
151
152             // PHASE 2 : Analyze the HQL AST, and produce an SQL AST.
153
HqlSqlWalker w = analyze( parser, collectionRole );
154
155             sqlAst = ( Statement ) w.getAST();
156
157             // at some point the generate phase needs to be moved out of here,
158
// because a single object-level DML might spawn multiple SQL DML
159
// command executions.
160
//
161
// Possible to just move the sql generation for dml stuff, but for
162
// consistency-sake probably best to just move responsiblity for
163
// the generation phase completely into the delegates
164
// (QueryLoader/StatementExecutor) themselves. Also, not sure why
165
// QueryLoader currently even has a dependency on this at all; does
166
// it need it? Ideally like to see the walker itself given to the delegates directly...
167

168             if ( sqlAst.needsExecutor() ) {
169                 statementExecutor = buildAppropriateStatementExecutor( w );
170             }
171             else {
172                 // PHASE 3 : Generate the SQL.
173
generate( ( QueryNode ) sqlAst );
174                 queryLoader = new QueryLoader( this, factory, w.getSelectClause() );
175             }
176
177             compiled = true;
178         }
179         catch ( QueryException qe ) {
180             qe.setQueryString( hql );
181             throw qe;
182         }
183         catch ( RecognitionException e ) {
184             throw new QuerySyntaxException( e, hql );
185         }
186         catch ( ANTLRException e ) {
187             QueryException qe = new QueryException( e.getMessage(), e );
188             qe.setQueryString( hql );
189             throw qe;
190         }
191     }
192
193     private void generate(AST sqlAst) throws QueryException, RecognitionException {
194         if ( sql == null ) {
195             SqlGenerator gen = new SqlGenerator(factory);
196             gen.statement( sqlAst );
197             sql = gen.getSQL();
198             if ( log.isDebugEnabled() ) {
199                 log.debug( "HQL: " + hql );
200                 log.debug( "SQL: " + sql );
201             }
202             gen.getParseErrorHandler().throwQueryException();
203         }
204     }
205
206     private HqlSqlWalker analyze(HqlParser parser, String JavaDoc collectionRole) throws QueryException, RecognitionException {
207         HqlSqlWalker w = new HqlSqlWalker( this, factory, parser, tokenReplacements, collectionRole );
208         AST hqlAst = parser.getAST();
209
210         // Transform the tree.
211
w.statement( hqlAst );
212
213         if ( AST_LOG.isDebugEnabled() ) {
214             ASTPrinter printer = new ASTPrinter( SqlTokenTypes.class );
215             AST_LOG.debug( printer.showAsString( w.getAST(), "--- SQL AST ---" ) );
216         }
217
218         w.getParseErrorHandler().throwQueryException();
219
220         return w;
221     }
222
223     private HqlParser parse(boolean filter) throws TokenStreamException, RecognitionException {
224         // Parse the query string into an HQL AST.
225
HqlParser parser = HqlParser.getInstance( hql );
226         parser.setFilter( filter );
227
228         if ( log.isDebugEnabled() ) {
229             log.debug( "parse() - HQL: " + hql );
230         }
231         parser.statement();
232
233         AST hqlAst = parser.getAST();
234
235         showHqlAst( hqlAst );
236
237         parser.getParseErrorHandler().throwQueryException();
238         return parser;
239     }
240
241     void showHqlAst(AST hqlAst) {
242         if ( AST_LOG.isDebugEnabled() ) {
243             ASTPrinter printer = new ASTPrinter( HqlTokenTypes.class );
244             printer.setShowClassNames( false ); // The class names aren't interesting in the first tree.
245
AST_LOG.debug( printer.showAsString( hqlAst, "--- HQL AST ---" ) );
246         }
247     }
248
249     private void errorIfDML() throws HibernateException {
250         if ( sqlAst.needsExecutor() ) {
251             throw new HibernateException( "Not supported for DML operations" );
252         }
253     }
254
255     private void errorIfSelect() throws HibernateException {
256         if ( !sqlAst.needsExecutor() ) {
257             throw new HibernateException( "Not supported for select queries" );
258         }
259     }
260
261     private HqlSqlWalker getWalker() {
262         return sqlAst.getWalker();
263     }
264
265     /**
266      * Types of the return values of an <tt>iterate()</tt> style query.
267      *
268      * @return an array of <tt>Type</tt>s.
269      */

270     public Type[] getReturnTypes() {
271         errorIfDML();
272         return getWalker().getReturnTypes();
273     }
274
275     public String JavaDoc[] getReturnAliases() {
276         errorIfDML();
277         return getWalker().getReturnAliases();
278     }
279
280     public String JavaDoc[][] getColumnNames() {
281         errorIfDML();
282         return getWalker().getSelectClause().getColumnNames();
283     }
284
285     public Set JavaDoc getQuerySpaces() {
286         return getWalker().getQuerySpaces();
287     }
288
289     public List JavaDoc list(SessionImplementor session, QueryParameters queryParameters)
290             throws HibernateException {
291         // Delegate to the QueryLoader...
292
errorIfDML();
293         return queryLoader.list( session, queryParameters );
294     }
295
296     /**
297      * Return the query results as an iterator
298      */

299     public Iterator JavaDoc iterate(QueryParameters queryParameters, EventSource session)
300             throws HibernateException {
301         // Delegate to the QueryLoader...
302
errorIfDML();
303         return queryLoader.iterate( queryParameters, session );
304     }
305
306     /**
307      * Return the query results, as an instance of <tt>ScrollableResults</tt>
308      */

309     public ScrollableResults scroll(QueryParameters queryParameters, SessionImplementor session)
310             throws HibernateException {
311         // Delegate to the QueryLoader...
312
errorIfDML();
313         return queryLoader.scroll( queryParameters, session );
314     }
315
316     public int executeUpdate(QueryParameters queryParameters, SessionImplementor session)
317             throws HibernateException {
318         errorIfSelect();
319         return statementExecutor.execute( queryParameters, session );
320     }
321
322     /**
323      * The SQL query string to be called; implemented by all subclasses
324      */

325     public String JavaDoc getSQLString() {
326         return sql;
327     }
328
329     // -- Package local methods for the QueryLoader delegate --
330

331     public boolean isShallowQuery() {
332         return shallowQuery;
333     }
334
335     public String JavaDoc getQueryString() {
336         return hql;
337     }
338
339     public Map JavaDoc getEnabledFilters() {
340         return enabledFilters;
341     }
342
343     public int[] getNamedParameterLocs(String JavaDoc name) {
344         return getWalker().getNamedParameterLocations( name );
345     }
346
347     public boolean containsCollectionFetches() {
348         errorIfDML();
349         List JavaDoc collectionFetches = ( ( QueryNode ) sqlAst ).getFromClause().getCollectionFetches();
350         return collectionFetches != null && collectionFetches.size() > 0;
351     }
352
353     public void validateScrollability() throws HibernateException {
354         // Impl Note: allows multiple collection fetches as long as the
355
// entire fecthed graph still "points back" to a single
356
// root entity for return
357

358         errorIfDML();
359
360         QueryNode query = ( QueryNode ) sqlAst;
361
362         // If there are no collection fetches, then no further checks are needed
363
List JavaDoc collectionFetches = query.getFromClause().getCollectionFetches();
364         if ( collectionFetches.isEmpty() ) {
365             return;
366         }
367
368         // A shallow query is ok (although technically there should be no fetching here...)
369
if ( isShallowQuery() ) {
370             return;
371         }
372
373         // Otherwise, we have a non-scalar select with defined collection fetch(es).
374
// Make sure that there is only a single root entity in the return (no tuples)
375
if ( getReturnTypes().length > 1 ) {
376             throw new HibernateException( "cannot scroll with collection fetches and returned tuples" );
377         }
378
379         FromElement owner = null;
380         Iterator JavaDoc itr = query.getSelectClause().getFromElementsForLoad().iterator();
381         while ( itr.hasNext() ) {
382             // should be the first, but just to be safe...
383
final FromElement fromElement = ( FromElement ) itr.next();
384             if ( fromElement.getOrigin() == null ) {
385                 owner = fromElement;
386                 break;
387             }
388         }
389
390         if ( owner == null ) {
391             throw new HibernateException( "unable to locate collection fetch(es) owner for scrollability checks" );
392         }
393
394         // This is not strictly true. We actually just need to make sure that
395
// it is ordered by root-entity PK and that that order-by comes before
396
// any non-root-entity ordering...
397

398         AST primaryOrdering = query.getOrderByClause().getFirstChild();
399         if ( primaryOrdering != null ) {
400             // TODO : this is a bit dodgy, come up with a better way to check this (plus see above comment)
401
String JavaDoc [] idColNames = owner.getQueryable().getIdentifierColumnNames();
402             String JavaDoc expectedPrimaryOrderSeq = StringHelper.join(
403                     ", ",
404                     StringHelper.qualify( owner.getTableAlias(), idColNames )
405             );
406             if ( !primaryOrdering.getText().startsWith( expectedPrimaryOrderSeq ) ) {
407                 throw new HibernateException( "cannot scroll results with collection fetches which are not ordered primarily by the root entity's PK" );
408             }
409         }
410     }
411
412     private StatementExecutor buildAppropriateStatementExecutor(HqlSqlWalker walker) {
413         Statement statement = ( Statement ) walker.getAST();
414         if ( walker.getStatementType() == HqlSqlTokenTypes.DELETE ) {
415             FromElement fromElement = walker.getFinalFromClause().getFromElement();
416             Queryable persister = fromElement.getQueryable();
417             if ( persister.isMultiTable() ) {
418                 return new MultiTableDeleteExecutor( walker );
419             }
420             else {
421                 return new BasicExecutor( walker, persister );
422             }
423         }
424         else if ( walker.getStatementType() == HqlSqlTokenTypes.UPDATE ) {
425             FromElement fromElement = walker.getFinalFromClause().getFromElement();
426             Queryable persister = fromElement.getQueryable();
427             if ( persister.isMultiTable() ) {
428                 // even here, if only properties mapped to the "base table" are referenced
429
// in the set and where clauses, this could be handled by the BasicDelegate.
430
// TODO : decide if it is better performance-wise to perform that check, or to simply use the MultiTableUpdateDelegate
431
return new MultiTableUpdateExecutor( walker );
432             }
433             else {
434                 return new BasicExecutor( walker, persister );
435             }
436         }
437         else if ( walker.getStatementType() == HqlSqlTokenTypes.INSERT ) {
438             return new BasicExecutor( walker, ( ( InsertStatement ) statement ).getIntoClause().getQueryable() );
439         }
440         else {
441             throw new QueryException( "Unexpected statement type" );
442         }
443     }
444 }
445
Popular Tags