Source code: org/hibernate/loader/hql/QueryLoader.java
1 // $Id: QueryLoader.java,v 1.9 2005/04/22 18:05:58 oneovthafew Exp $
2 package org.hibernate.loader.hql;
3
4 import java.sql.PreparedStatement;
5 import java.sql.ResultSet;
6 import java.sql.SQLException;
7 import java.util.HashMap;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.Map;
11
12 import org.apache.commons.logging.Log;
13 import org.apache.commons.logging.LogFactory;
14 import org.hibernate.HibernateException;
15 import org.hibernate.LockMode;
16 import org.hibernate.QueryException;
17 import org.hibernate.ScrollableResults;
18 import org.hibernate.dialect.Dialect;
19 import org.hibernate.engine.QueryParameters;
20 import org.hibernate.engine.SessionFactoryImplementor;
21 import org.hibernate.engine.SessionImplementor;
22 import org.hibernate.engine.TypedValue;
23 import org.hibernate.exception.JDBCExceptionHelper;
24 import org.hibernate.hql.HolderInstantiator;
25 import org.hibernate.hql.QueryTranslator;
26 import org.hibernate.hql.ast.FromElement;
27 import org.hibernate.hql.ast.SelectClause;
28 import org.hibernate.impl.IteratorImpl;
29 import org.hibernate.loader.BasicLoader;
30 import org.hibernate.persister.collection.CollectionPersister;
31 import org.hibernate.persister.collection.QueryableCollection;
32 import org.hibernate.persister.entity.Loadable;
33 import org.hibernate.persister.entity.Queryable;
34 import org.hibernate.sql.ForUpdateFragment;
35 import org.hibernate.type.EntityType;
36 import org.hibernate.type.Type;
37 import org.hibernate.util.ArrayHelper;
38
39 /**
40 * A delegate that implements the Loader part of QueryTranslator.
41 * <p/>
42 * User: josh
43 * Date: Jan 3, 2004
44 * Time: 8:29:58 PM
45 */
46 public class QueryLoader extends BasicLoader {
47
48 private static final Log log = LogFactory.getLog( QueryLoader.class );
49 /**
50 * The query translator that is delegating to this object.
51 */
52 private QueryTranslator queryTranslator;
53
54 private Queryable[] entityPersisters;
55 private String[] entityAliases;
56 private String[] sqlAliases;
57 private String[] sqlAliasSuffixes;
58 private boolean[] includeInSelect;
59
60 private boolean hasScalars;
61 private String[][] scalarColumnNames;
62 //private Type[] sqlResultTypes;
63 private Type[] queryReturnTypes;
64
65 private final Map sqlAliasByEntityAlias = new HashMap(8);
66
67 private EntityType[] ownerAssociationTypes;
68 private int[] owners;
69 private boolean[] entityEagerPropertyFetches;
70
71 private int collectionOwner = -1;
72 private QueryableCollection collectionPersister;
73
74 private int selectLength;
75 private HolderInstantiator holderInstantiator;
76
77 private LockMode[] defaultLockModes;
78
79 /**
80 * Creates a new Loader implementation.
81 *
82 * @param queryTranslator The query translator that is the delegator.
83 * @param factory The factory from which this loader is being created.
84 */
85 public QueryLoader(final QueryTranslator queryTranslator,
86 final SessionFactoryImplementor factory,
87 final SelectClause selectClause) {
88 super( factory );
89 this.queryTranslator = queryTranslator;
90 initialize( selectClause );
91 postInstantiate();
92 }
93
94 private void initialize(SelectClause selectClause) {
95
96 List fromElementList = selectClause.getFromElementsForLoad();
97
98 hasScalars = selectClause.isScalarSelect();
99 scalarColumnNames = selectClause.getColumnNames();
100 //sqlResultTypes = selectClause.getSqlResultTypes();
101 queryReturnTypes = selectClause.getQueryReturnTypes();
102
103 holderInstantiator = new HolderInstantiator(
104 selectClause.getConstructor(),
105 selectClause.isMap(),
106 selectClause.isList(),
107 selectClause.getQueryReturnAliases()
108 );
109
110 // make sure we grab the first collection persister to fully mimic old parser
111 FromElement collectionFromElement = selectClause.getCollectionFromElement();
112 if (collectionFromElement != null) {
113 collectionPersister = collectionFromElement.getQueryableCollection();
114 collectionOwner = fromElementList.indexOf( collectionFromElement.getOrigin() );
115 }
116
117 int size = fromElementList.size();
118 entityPersisters = new Queryable[size];
119 entityEagerPropertyFetches = new boolean[size];
120 entityAliases = new String[size];
121 sqlAliases = new String[size];
122 sqlAliasSuffixes = new String[size];
123 includeInSelect = new boolean[size];
124 owners = new int[size];
125 ownerAssociationTypes = new EntityType[size];
126
127 for ( int i = 0; i < size; i++ ) {
128 final FromElement element = ( FromElement ) fromElementList.get( i );
129 entityPersisters[i] = ( Queryable ) element.getEntityPersister();
130
131 if ( entityPersisters[i] == null ) {
132 throw new IllegalStateException( "No entity persister for " + element.toString() );
133 }
134
135 entityEagerPropertyFetches[i] = element.isAllPropertyFetch();
136 sqlAliases[i] = element.getTableAlias();
137 entityAliases[i] = element.getClassAlias();
138 sqlAliasByEntityAlias.put( entityAliases[i], sqlAliases[i] );
139 sqlAliasSuffixes[i] = ( size == 1 ) ? "" : Integer.toString( i ) + "_";
140 includeInSelect[i] = !element.isFetch();
141 if ( includeInSelect[i] ) selectLength++;
142
143 owners[i] = -1; //by default
144 if ( element.isFetch() ) {
145 if ( element.isCollectionJoin() || element.getQueryableCollection() != null ) {
146 // This is now handled earlier in this method.
147 }
148 else if ( element.getDataType().isEntityType() ) {
149 EntityType entityType = ( EntityType ) element.getDataType();
150 if ( entityType.isOneToOne() ) {
151 owners[i] = fromElementList.indexOf( element.getOrigin() );
152 }
153 ownerAssociationTypes[i] = entityType;
154 }
155 }
156 }
157
158 //NONE, because its the requested lock mode, not the actual!
159 defaultLockModes = ArrayHelper.fillArray(LockMode.NONE, size);
160
161 }
162
163 // -- Loader implementation --
164
165 public Loadable[] getEntityPersisters() {
166 return entityPersisters;
167 }
168
169 public String[] getAliases() {
170 return sqlAliases;
171 }
172
173 public String[] getSqlAliasSuffixes() {
174 return sqlAliasSuffixes;
175 }
176
177 public String[] getSuffixes() {
178 return getSqlAliasSuffixes();
179 }
180
181 protected String getQueryIdentifier() {
182 return queryTranslator.getQueryString();
183 }
184
185 /**
186 * The SQL query string to be called.
187 */
188 protected String getSQLString() {
189 return queryTranslator.getSQLString();
190 }
191
192 /**
193 * An (optional) persister for a collection to be initialized; only collection loaders
194 * return a non-null value
195 */
196 protected CollectionPersister getCollectionPersister() {
197 return collectionPersister;
198 }
199
200 protected int getCollectionOwner() {
201 return collectionOwner;
202 }
203
204 protected boolean[] getEntityEagerPropertyFetches() {
205 return entityEagerPropertyFetches;
206 }
207
208 /**
209 * An array of indexes of the entity that owns a one-to-one association
210 * to the entity at the given index (-1 if there is no "owner")
211 */
212 protected int[] getOwners() {
213 return owners;
214 }
215
216 protected EntityType[] getOwnerAssociationTypes() {
217 return ownerAssociationTypes;
218 }
219
220 // -- Loader overrides --
221
222 protected boolean isSubselectLoadingEnabled() {
223 return hasSubselectLoadableCollections();
224 }
225
226 protected int bindNamedParameters(final PreparedStatement ps,
227 final Map namedParams,
228 final int start,
229 final SessionImplementor session)
230 throws SQLException, HibernateException {
231
232 if ( namedParams != null ) {
233 // assumes that types are all of span 1
234 Iterator iter = namedParams.entrySet().iterator();
235 int result = 0;
236 while ( iter.hasNext() ) {
237 Map.Entry e = ( Map.Entry ) iter.next();
238 String name = ( String ) e.getKey();
239 TypedValue typedval = ( TypedValue ) e.getValue();
240 int[] locs = getNamedParameterLocs( name );
241 for ( int i = 0; i < locs.length; i++ ) {
242 if ( log.isDebugEnabled() ) {
243 log.debug( "bindNamedParameters() " +
244 typedval.getValue() + " -> " + name +
245 " [" + ( locs[i] + start ) + "]" );
246 }
247 typedval.getType().nullSafeSet( ps, typedval.getValue(), locs[i] + start, session );
248 }
249 result += locs.length;
250 }
251 return result;
252 }
253 else {
254 return 0;
255 }
256 }
257
258 /**
259 * @param lockModes a collection of lock modes specified dynamically via the Query interface
260 */
261 protected LockMode[] getLockModes(Map lockModes) {
262
263 if ( lockModes==null || lockModes.size()==0 ) {
264 return defaultLockModes;
265 }
266 else {
267 // unfortunately this stuff can't be cached because
268 // it is per-invocation, not constant for the
269 // QueryTranslator instance
270
271 LockMode[] lockModeArray = new LockMode[entityAliases.length];
272 for ( int i = 0; i < entityAliases.length; i++ ) {
273 LockMode lockMode = (LockMode) lockModes.get( entityAliases[i] );
274 if ( lockMode == null ) {
275 //NONE, because its the requested lock mode, not the actual!
276 lockMode = LockMode.NONE;
277 }
278 lockModeArray[i] = lockMode;
279 }
280 return lockModeArray;
281 }
282 }
283
284 protected String applyLocks(String sql, Map lockModes, Dialect dialect)
285 throws QueryException {
286
287 if ( lockModes == null || lockModes.size() == 0 ) {
288 return sql;
289 }
290 else {
291 // can't cache this stuff either (per-invocation)
292
293 //we are given a map of user alias -> lock mode
294 //create a new map of sql alias -> lock mode
295 final Map aliasedLockModes = new HashMap();
296 final Iterator iter = lockModes.entrySet().iterator();
297 while ( iter.hasNext() ) {
298 Map.Entry me = ( Map.Entry ) iter.next();
299 final String userAlias = ( String ) me.getKey();
300 final String sqlAlias = (String) sqlAliasByEntityAlias.get( userAlias );
301 aliasedLockModes.put( sqlAlias, me.getValue() );
302 }
303
304 //if necessary, create a map of sql alias -> key columns
305 Map keyColumnNames = null;
306 if ( dialect.forUpdateOfColumns() ) {
307 final Loadable[] persisters = getEntityPersisters();
308 keyColumnNames = new HashMap();
309 for ( int i = 0; i < sqlAliases.length; i++ ) {
310 keyColumnNames.put( sqlAliases[i], persisters[i].getIdentifierColumnNames() );
311 }
312 }
313
314 return sql + new ForUpdateFragment( dialect, aliasedLockModes, keyColumnNames ).toFragmentString();
315
316 }
317 }
318
319 protected boolean upgradeLocks() {
320 return true;
321 }
322
323 protected Object getResultColumnOrRow(Object[] row, ResultSet rs, SessionImplementor session)
324 throws SQLException, HibernateException {
325
326 row = toResultRow( row );
327 boolean isHolder = holderInstantiator.isRequired();
328 if ( hasScalars ) {
329 String[][] scalarColumns = scalarColumnNames;
330 int queryCols = queryReturnTypes.length;
331 if ( !isHolder && queryCols == 1 ) {
332 return queryReturnTypes[0].nullSafeGet( rs, scalarColumns[0], session, null );
333 }
334 else {
335 row = new Object[queryCols];
336 for ( int i = 0; i < queryCols; i++ ) {
337 row[i] = queryReturnTypes[i].nullSafeGet( rs, scalarColumns[i], session, null );
338 }
339 return row;
340 }
341 }
342 else if ( !isHolder ) {
343 return row.length == 1 ? row[0] : row;
344 }
345 else {
346 return row;
347 }
348
349 }
350
351 protected List getResultList(List results) throws QueryException {
352 // meant to handle dynamic instantiation queries...
353 if ( holderInstantiator.isRequired() ) {
354 for ( int i = 0; i < results.size(); i++ ) {
355 Object[] row = ( Object[] ) results.get( i );
356 Object result = holderInstantiator.instantiate(row);
357 results.set( i, result );
358 }
359 }
360 return results;
361 }
362
363 // --- Query translator methods ---
364
365 /**
366 * Delegats
367 *
368 * @param session
369 * @param queryParameters
370 * @return
371 * @throws HibernateException
372 */
373 public List list(SessionImplementor session, QueryParameters queryParameters)
374 throws HibernateException {
375 return list( session, queryParameters, queryTranslator.getQuerySpaces(), queryReturnTypes );
376 }
377
378 /**
379 * Return the query results as an iterator
380 */
381 public Iterator iterate(QueryParameters queryParameters, SessionImplementor session)
382 throws HibernateException {
383
384 final boolean stats = session.getFactory().getStatistics().isStatisticsEnabled();
385 long startTime = 0;
386 if ( stats ) startTime = System.currentTimeMillis();
387
388 try {
389
390 final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
391 final ResultSet rs = getResultSet( st, queryParameters.getRowSelection(), session );
392 final Iterator result = new IteratorImpl(
393 rs,
394 st,
395 session,
396 queryReturnTypes,
397 queryTranslator.getColumnNames(),
398 holderInstantiator
399 );
400
401 if ( stats ) {
402 session.getFactory().getStatisticsImplementor().queryExecuted( "HQL: " + queryTranslator.getQueryString(),
403 0,
404 System.currentTimeMillis() - startTime );
405 }
406
407 return result;
408
409 }
410 catch ( SQLException sqle ) {
411 throw JDBCExceptionHelper.convert( getFactory().getSQLExceptionConverter(),
412 sqle,
413 "could not execute query using iterate",
414 getSQLString() );
415 }
416
417 }
418
419 public ScrollableResults scroll(final QueryParameters queryParameters,
420 final SessionImplementor session)
421 throws HibernateException {
422 return scroll( queryParameters, queryReturnTypes, null, session );
423 }
424
425 // -- Implementation private methods --
426
427 private Object[] toResultRow(Object[] row) {
428 if ( selectLength == row.length ) {
429 return row;
430 }
431 else {
432 Object[] result = new Object[selectLength];
433 int j = 0;
434 for ( int i = 0; i < row.length; i++ ) {
435 if ( includeInSelect[i] ) result[j++] = row[i];
436 }
437 return result;
438 }
439 }
440
441 /**
442 * Returns the locations of all occurrences of the named parameter.
443 */
444 private int[] getNamedParameterLocs(String name) throws QueryException {
445 return queryTranslator.getNamedParameterLocs( name );
446 }
447 }