Source code: org/hibernate/hql/ast/QueryTranslatorImpl.java
1 // $Id: QueryTranslatorImpl.java,v 1.53 2005/04/26 18:08:02 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.hql.FilterTranslator;
18 import org.hibernate.hql.antlr.HqlTokenTypes;
19 import org.hibernate.hql.antlr.SqlTokenTypes;
20 import org.hibernate.loader.hql.QueryLoader;
21 import org.hibernate.type.Type;
22
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28
29 /**
30 * A QueryTranslator that uses an AST based parser.
31 * <br>User: josh
32 * <br>Date: Dec 31, 2003
33 * <br>Time: 7:50:35 AM
34 *
35 * @author Joshua Davis (pgmjsd@sourceforge.net)
36 */
37 public class QueryTranslatorImpl implements FilterTranslator {
38 private static final Log log = LogFactory.getLog( QueryTranslatorImpl.class );
39 private static final Log AST_LOG = LogFactory.getLog( "org.hibernate.hql.AST" );
40
41 private String hql;
42 private boolean shallowQuery;
43 private Map tokenReplacements;
44 private Map enabledFilters;
45 private SessionFactoryImplementor factory;
46
47 private boolean compiled;
48 private QueryLoader queryLoader;
49 private UpdateStatementExecutor updateStatementExecutor;
50
51 private QueryNode sqlAst;
52 private String sql;
53
54
55 /**
56 * Creates a new AST-based query translator.
57 *
58 * @param query The HQL query string.
59 * @param enabledFilters Any filters currently enabled for the session.
60 * @param factory The session factory constructing this translator instance.
61 */
62 public QueryTranslatorImpl(String query,
63 Map enabledFilters,
64 SessionFactoryImplementor factory) {
65 this.hql = query;
66 this.compiled = false;
67 this.shallowQuery = false;
68 this.enabledFilters = enabledFilters;
69 this.factory = factory;
70 }
71
72 /**
73 * Compile a "normal" query. This method may be called multiple
74 * times. Subsequent invocations are no-ops.
75 *
76 * @param replacements Defined query substitutions.
77 * @param shallow Does this represent a shallow (scalar or entity-id) select?
78 * @throws QueryException There was a problem parsing the query string.
79 * @throws MappingException There was a problem querying defined mappings.
80 */
81 public void compile(Map replacements, boolean shallow)
82 throws QueryException, MappingException {
83 doCompile( replacements, shallow, null );
84 }
85
86 /**
87 * Compile a filter. This method may be called multiple
88 * times. Subsequent invocations are no-ops.
89 *
90 * @param collectionRole the role name of the collection used as the basis for the filter.
91 * @param replacements Defined query substitutions.
92 * @param shallow Does this represent a shallow (scalar or entity-id) select?
93 * @throws QueryException There was a problem parsing the query string.
94 * @throws MappingException There was a problem querying defined mappings.
95 */
96 public void compile(String collectionRole, Map replacements, boolean shallow)
97 throws QueryException, MappingException {
98 doCompile( replacements, shallow, collectionRole );
99 }
100
101 /**
102 * Performs both filter and non-filter compiling.
103 *
104 * @param replacements Defined query substitutions.
105 * @param shallow Does this represent a shallow (scalar or entity-id) select?
106 * @param collectionRole the role name of the collection used as the basis for the filter, NULL if this
107 * is not a filter.
108 */
109 private synchronized void doCompile(Map replacements, boolean shallow, String collectionRole) {
110 // If the query is already compiled, skip the compilation.
111 if ( compiled ) {
112 if ( log.isDebugEnabled() ) {
113 log.debug( "compile() : The query is already compiled, skipping..." );
114 }
115 return;
116 }
117
118 // Remember the parameters for the compilation.
119 this.tokenReplacements = replacements;
120 if ( tokenReplacements == null ) {
121 tokenReplacements = new HashMap();
122 }
123 this.shallowQuery = shallow;
124
125 try {
126 // PHASE 1 : Parse the HQL into an AST.
127 HqlParser parser = parse( true );
128
129 // PHASE 2 : Analyze the HQL AST, and produce an SQL AST.
130 HqlSqlWalker w = analyze( parser, collectionRole );
131
132 sqlAst = ( QueryNode ) w.getAST();
133
134 // at some point the generate phase needs to be moved out of here,
135 // because a single object-level DML might spawn multiple SQL DML
136 // command executions.
137 //
138 // Possible to just move the sql generation for dml stuff, but for
139 // consistency-sake probably best to just move responsiblity for
140 // the generation phase completely into the delegates
141 // (QueryLoader/UpdateStatementExecutor) themselves. Also, not sure why
142 // QueryLoader currently even has a dependency on this at all; does
143 // it need it? Ideally like to see the walker itself given to the delegates directly...
144
145 // PHASE 3 : Generate the SQL.
146 generate( sqlAst );
147
148 if ( sqlAst.isDML() ) {
149 updateStatementExecutor = new UpdateStatementExecutor( sql, getWalker(), factory );
150 }
151 else {
152 queryLoader = new QueryLoader( this, factory, w.getSelectClause() );
153 }
154
155 compiled = true;
156 }
157 catch ( QueryException qe ) {
158 qe.setQueryString( hql );
159 throw qe;
160 }
161 catch ( RecognitionException e ) {
162 throw new QuerySyntaxError( e, hql );
163 }
164 catch ( ANTLRException e ) {
165 QueryException qe = new QueryException( e.getMessage(), e );
166 qe.setQueryString( hql );
167 throw qe;
168 }
169 }
170
171 private void generate(AST sqlAst) throws QueryException, RecognitionException {
172 if ( sql == null ) {
173 SqlGenerator gen = new SqlGenerator(factory);
174 gen.statement( sqlAst );
175 sql = gen.getSQL();
176 if ( log.isDebugEnabled() ) {
177 log.debug( "HQL: " + hql );
178 log.debug( "SQL: " + sql );
179 }
180 gen.getParseErrorHandler().throwQueryException();
181 }
182 }
183
184 private HqlSqlWalker analyze(HqlParser parser, String collectionRole) throws QueryException, RecognitionException {
185 HqlSqlWalker w = new HqlSqlWalker( this, factory, parser, tokenReplacements, collectionRole );
186 AST hqlAst = parser.getAST();
187
188 // Transform the tree.
189 w.statement( hqlAst );
190
191 if ( AST_LOG.isDebugEnabled() ) {
192 ASTPrinter printer = new ASTPrinter( SqlTokenTypes.class );
193 AST_LOG.debug( printer.showAsString( w.getAST(), "--- SQL AST ---" ) );
194 }
195
196 w.getParseErrorHandler().throwQueryException();
197
198 return w;
199 }
200
201 private HqlParser parse(boolean filter) throws TokenStreamException, RecognitionException {
202 // Parse the query string into an HQL AST.
203 HqlParser parser = HqlParser.getInstance( hql );
204 parser.setFilter( filter );
205
206 if ( log.isDebugEnabled() ) {
207 log.debug( "parse() - HQL: " + hql );
208 }
209 parser.statement();
210
211 AST hqlAst = parser.getAST();
212
213 showHqlAst( hqlAst );
214
215 parser.getParseErrorHandler().throwQueryException();
216 return parser;
217 }
218
219 void showHqlAst(AST hqlAst) {
220 if ( AST_LOG.isDebugEnabled() ) {
221 ASTPrinter printer = new ASTPrinter( HqlTokenTypes.class );
222 printer.setShowClassNames( false ); // The class names aren't interesting in the first tree.
223 AST_LOG.debug( printer.showAsString( hqlAst, "--- HQL AST ---" ) );
224 }
225 }
226
227 private void errorIfDML() throws HibernateException {
228 if ( sqlAst.isDML() ) {
229 throw new HibernateException( "Not supported for DML operations" );
230 }
231 }
232
233 private void errorIfSelect() throws HibernateException {
234 if ( !sqlAst.isDML() ) {
235 throw new HibernateException( "Not supported for select queries" );
236 }
237 }
238
239 private HqlSqlWalker getWalker() {
240 return sqlAst.getWalker();
241 }
242
243 /**
244 * Types of the return values of an <tt>iterate()</tt> style query.
245 *
246 * @return an array of <tt>Type</tt>s.
247 */
248 public Type[] getReturnTypes() {
249 errorIfDML();
250 return getWalker().getReturnTypes();
251 }
252
253 public String[] getReturnAliases() {
254 errorIfDML();
255 return getWalker().getReturnAliases();
256 }
257
258 public String[][] getColumnNames() {
259 errorIfDML();
260 return getWalker().getSelectClause().getColumnNames();
261 }
262
263 public Set getQuerySpaces() {
264 return getWalker().getQuerySpaces();
265 }
266
267 public List list(SessionImplementor session, QueryParameters queryParameters)
268 throws HibernateException {
269 // Delegate to the QueryLoader...
270 errorIfDML();
271 return queryLoader.list( session, queryParameters );
272 }
273
274 /**
275 * Return the query results as an iterator
276 */
277 public Iterator iterate(QueryParameters queryParameters, SessionImplementor session)
278 throws HibernateException {
279 // Delegate to the QueryLoader...
280 errorIfDML();
281 return queryLoader.iterate( queryParameters, session );
282 }
283
284 /**
285 * Return the query results, as an instance of <tt>ScrollableResults</tt>
286 */
287 public ScrollableResults scroll(QueryParameters queryParameters, SessionImplementor session)
288 throws HibernateException {
289 // Delegate to the QueryLoader...
290 errorIfDML();
291 return queryLoader.scroll( queryParameters, session );
292 }
293
294 public int executeUpdate(QueryParameters queryParameters, SessionImplementor session)
295 throws HibernateException {
296 errorIfSelect();
297 return updateStatementExecutor.execute( queryParameters, session );
298 }
299
300 /**
301 * The SQL query string to be called; implemented by all subclasses
302 */
303 public String getSQLString() {
304 return sql;
305 }
306
307 // -- Package local methods for the QueryLoader delegate --
308
309 boolean isShallowQuery() {
310 return shallowQuery;
311 }
312
313 public String getQueryString() {
314 return hql;
315 }
316
317 public Map getEnabledFilters() {
318 return enabledFilters;
319 }
320
321 public int[] getNamedParameterLocs(String name) {
322 return getWalker().getNamedParameterLocs( name );
323 }
324 }