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.loader.hql;
26
27 import java.sql.PreparedStatement;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34
35 import org.hibernate.HibernateException;
36 import org.hibernate.LockMode;
37 import org.hibernate.QueryException;
38 import org.hibernate.ScrollableResults;
39 import org.hibernate.dialect.Dialect;
40 import org.hibernate.engine.QueryParameters;
41 import org.hibernate.engine.SessionFactoryImplementor;
42 import org.hibernate.engine.SessionImplementor;
43 import org.hibernate.event.EventSource;
44 import org.hibernate.exception.JDBCExceptionHelper;
45 import org.hibernate.hql.HolderInstantiator;
46 import org.hibernate.hql.ast.QueryTranslatorImpl;
47 import org.hibernate.hql.ast.tree.FromElement;
48 import org.hibernate.hql.ast.tree.SelectClause;
49 import org.hibernate.hql.ast.tree.QueryNode;
50 import org.hibernate.impl.IteratorImpl;
51 import org.hibernate.loader.BasicLoader;
52 import org.hibernate.param.ParameterSpecification;
53 import org.hibernate.persister.collection.CollectionPersister;
54 import org.hibernate.persister.collection.QueryableCollection;
55 import org.hibernate.persister.entity.Loadable;
56 import org.hibernate.persister.entity.Queryable;
57 import org.hibernate.persister.entity.Lockable;
58 import org.hibernate.transform.ResultTransformer;
59 import org.hibernate.type.EntityType;
60 import org.hibernate.type.Type;
61 import org.hibernate.util.ArrayHelper;
62
63 /**
64 * A delegate that implements the Loader part of QueryTranslator.
65 *
66 * @author josh
67 */
68 public class QueryLoader extends BasicLoader {
69
70 /**
71 * The query translator that is delegating to this object.
72 */
73 private QueryTranslatorImpl queryTranslator;
74
75 private Queryable[] entityPersisters;
76 private String[] entityAliases;
77 private String[] sqlAliases;
78 private String[] sqlAliasSuffixes;
79 private boolean[] includeInSelect;
80
81 private String[] collectionSuffixes;
82
83 private boolean hasScalars;
84 private String[][] scalarColumnNames;
85 //private Type[] sqlResultTypes;
86 private Type[] queryReturnTypes;
87
88 private final Map sqlAliasByEntityAlias = new HashMap(8);
89
90 private EntityType[] ownerAssociationTypes;
91 private int[] owners;
92 private boolean[] entityEagerPropertyFetches;
93
94 private int[] collectionOwners;
95 private QueryableCollection[] collectionPersisters;
96
97 private int selectLength;
98
99 private ResultTransformer selectNewTransformer;
100 private String[] queryReturnAliases;
101
102 private LockMode[] defaultLockModes;
103
104
105 /**
106 * Creates a new Loader implementation.
107 *
108 * @param queryTranslator The query translator that is the delegator.
109 * @param factory The factory from which this loader is being created.
110 * @param selectClause The AST representing the select clause for loading.
111 */
112 public QueryLoader(
113 final QueryTranslatorImpl queryTranslator,
114 final SessionFactoryImplementor factory,
115 final SelectClause selectClause) {
116 super( factory );
117 this.queryTranslator = queryTranslator;
118 initialize( selectClause );
119 postInstantiate();
120 }
121
122 private void initialize(SelectClause selectClause) {
123
124 List fromElementList = selectClause.getFromElementsForLoad();
125
126 hasScalars = selectClause.isScalarSelect();
127 scalarColumnNames = selectClause.getColumnNames();
128 //sqlResultTypes = selectClause.getSqlResultTypes();
129 queryReturnTypes = selectClause.getQueryReturnTypes();
130
131 selectNewTransformer = HolderInstantiator.createSelectNewTransformer(
132 selectClause.getConstructor(),
133 selectClause.isMap(),
134 selectClause.isList());
135 queryReturnAliases = selectClause.getQueryReturnAliases();
136
137 List collectionFromElements = selectClause.getCollectionFromElements();
138 if ( collectionFromElements != null && collectionFromElements.size()!=0 ) {
139 int length = collectionFromElements.size();
140 collectionPersisters = new QueryableCollection[length];
141 collectionOwners = new int[length];
142 collectionSuffixes = new String[length];
143 for ( int i=0; i<length; i++ ) {
144 FromElement collectionFromElement = (FromElement) collectionFromElements.get(i);
145 collectionPersisters[i] = collectionFromElement.getQueryableCollection();
146 collectionOwners[i] = fromElementList.indexOf( collectionFromElement.getOrigin() );
147 // collectionSuffixes[i] = collectionFromElement.getColumnAliasSuffix();
148 // collectionSuffixes[i] = Integer.toString( i ) + "_";
149 collectionSuffixes[i] = collectionFromElement.getCollectionSuffix();
150 }
151 }
152
153 int size = fromElementList.size();
154 entityPersisters = new Queryable[size];
155 entityEagerPropertyFetches = new boolean[size];
156 entityAliases = new String[size];
157 sqlAliases = new String[size];
158 sqlAliasSuffixes = new String[size];
159 includeInSelect = new boolean[size];
160 owners = new int[size];
161 ownerAssociationTypes = new EntityType[size];
162
163 for ( int i = 0; i < size; i++ ) {
164 final FromElement element = ( FromElement ) fromElementList.get( i );
165 entityPersisters[i] = ( Queryable ) element.getEntityPersister();
166
167 if ( entityPersisters[i] == null ) {
168 throw new IllegalStateException( "No entity persister for " + element.toString() );
169 }
170
171 entityEagerPropertyFetches[i] = element.isAllPropertyFetch();
172 sqlAliases[i] = element.getTableAlias();
173 entityAliases[i] = element.getClassAlias();
174 sqlAliasByEntityAlias.put( entityAliases[i], sqlAliases[i] );
175 // TODO should we just collect these like with the collections above?
176 sqlAliasSuffixes[i] = ( size == 1 ) ? "" : Integer.toString( i ) + "_";
177 // sqlAliasSuffixes[i] = element.getColumnAliasSuffix();
178 includeInSelect[i] = !element.isFetch();
179 if ( includeInSelect[i] ) {
180 selectLength++;
181 }
182
183 owners[i] = -1; //by default
184 if ( element.isFetch() ) {
185 if ( element.isCollectionJoin() || element.getQueryableCollection() != null ) {
186 // This is now handled earlier in this method.
187 }
188 else if ( element.getDataType().isEntityType() ) {
189 EntityType entityType = ( EntityType ) element.getDataType();
190 if ( entityType.isOneToOne() ) {
191 owners[i] = fromElementList.indexOf( element.getOrigin() );
192 }
193 ownerAssociationTypes[i] = entityType;
194 }
195 }
196 }
197
198 //NONE, because its the requested lock mode, not the actual!
199 defaultLockModes = ArrayHelper.fillArray(LockMode.NONE, size);
200
201 }
202
203 // -- Loader implementation --
204
205 public final void validateScrollability() throws HibernateException {
206 queryTranslator.validateScrollability();
207 }
208
209 protected boolean needsFetchingScroll() {
210 return queryTranslator.containsCollectionFetches();
211 }
212
213 public Loadable[] getEntityPersisters() {
214 return entityPersisters;
215 }
216
217 public String[] getAliases() {
218 return sqlAliases;
219 }
220
221 public String[] getSqlAliasSuffixes() {
222 return sqlAliasSuffixes;
223 }
224
225 public String[] getSuffixes() {
226 return getSqlAliasSuffixes();
227 }
228
229 public String[] getCollectionSuffixes() {
230 return collectionSuffixes;
231 }
232
233 protected String getQueryIdentifier() {
234 return queryTranslator.getQueryIdentifier();
235 }
236
237 /**
238 * The SQL query string to be called.
239 */
240 protected String getSQLString() {
241 return queryTranslator.getSQLString();
242 }
243
244 /**
245 * An (optional) persister for a collection to be initialized; only collection loaders
246 * return a non-null value
247 */
248 protected CollectionPersister[] getCollectionPersisters() {
249 return collectionPersisters;
250 }
251
252 protected int[] getCollectionOwners() {
253 return collectionOwners;
254 }
255
256 protected boolean[] getEntityEagerPropertyFetches() {
257 return entityEagerPropertyFetches;
258 }
259
260 /**
261 * An array of indexes of the entity that owns a one-to-one association
262 * to the entity at the given index (-1 if there is no "owner")
263 */
264 protected int[] getOwners() {
265 return owners;
266 }
267
268 protected EntityType[] getOwnerAssociationTypes() {
269 return ownerAssociationTypes;
270 }
271
272 // -- Loader overrides --
273
274 protected boolean isSubselectLoadingEnabled() {
275 return hasSubselectLoadableCollections();
276 }
277
278 /**
279 * @param lockModes a collection of lock modes specified dynamically via the Query interface
280 */
281 protected LockMode[] getLockModes(Map lockModes) {
282
283 if ( lockModes==null || lockModes.size()==0 ) {
284 return defaultLockModes;
285 }
286 else {
287 // unfortunately this stuff can't be cached because
288 // it is per-invocation, not constant for the
289 // QueryTranslator instance
290
291 LockMode[] lockModeArray = new LockMode[entityAliases.length];
292 for ( int i = 0; i < entityAliases.length; i++ ) {
293 LockMode lockMode = (LockMode) lockModes.get( entityAliases[i] );
294 if ( lockMode == null ) {
295 //NONE, because its the requested lock mode, not the actual!
296 lockMode = LockMode.NONE;
297 }
298 lockModeArray[i] = lockMode;
299 }
300 return lockModeArray;
301 }
302 }
303
304 protected String applyLocks(String sql, Map lockModes, Dialect dialect) throws QueryException {
305 if ( lockModes == null || lockModes.size() == 0 ) {
306 return sql;
307 }
308
309 // can't cache this stuff either (per-invocation)
310 // we are given a map of user-alias -> lock mode
311 // create a new map of sql-alias -> lock mode
312 final Map aliasedLockModes = new HashMap();
313 final Map keyColumnNames = dialect.forUpdateOfColumns() ? new HashMap() : null;
314 final Iterator iter = lockModes.entrySet().iterator();
315 while ( iter.hasNext() ) {
316 Map.Entry me = ( Map.Entry ) iter.next();
317 final String userAlias = ( String ) me.getKey();
318 final String drivingSqlAlias = ( String ) sqlAliasByEntityAlias.get( userAlias );
319 if ( drivingSqlAlias == null ) {
320 throw new IllegalArgumentException( "could not locate alias to apply lock mode : " + userAlias );
321 }
322 // at this point we have (drivingSqlAlias) the SQL alias of the driving table
323 // corresponding to the given user alias. However, the driving table is not
324 // (necessarily) the table against which we want to apply locks. Mainly,
325 // the exception case here is joined-subclass hierarchies where we instead
326 // want to apply the lock against the root table (for all other strategies,
327 // it just happens that driving and root are the same).
328 final QueryNode select = ( QueryNode ) queryTranslator.getSqlAST();
329 final Lockable drivingPersister = ( Lockable ) select.getFromClause().getFromElement( userAlias ).getQueryable();
330 final String sqlAlias = drivingPersister.getRootTableAlias( drivingSqlAlias );
331 aliasedLockModes.put( sqlAlias, me.getValue() );
332 if ( keyColumnNames != null ) {
333 keyColumnNames.put( sqlAlias, drivingPersister.getRootTableIdentifierColumnNames() );
334 }
335 }
336 return dialect.applyLocksToSql( sql, aliasedLockModes, keyColumnNames );
337 }
338
339 protected boolean upgradeLocks() {
340 return true;
341 }
342
343 private boolean hasSelectNew() {
344 return selectNewTransformer!=null;
345 }
346
347 protected Object getResultColumnOrRow(Object[] row, ResultTransformer transformer, ResultSet rs, SessionImplementor session)
348 throws SQLException, HibernateException {
349
350 row = toResultRow( row );
351 boolean hasTransform = hasSelectNew() || transformer!=null;
352 if ( hasScalars ) {
353 String[][] scalarColumns = scalarColumnNames;
354 int queryCols = queryReturnTypes.length;
355 if ( !hasTransform && queryCols == 1 ) {
356 return queryReturnTypes[0].nullSafeGet( rs, scalarColumns[0], session, null );
357 }
358 else {
359 row = new Object[queryCols];
360 for ( int i = 0; i < queryCols; i++ ) {
361 row[i] = queryReturnTypes[i].nullSafeGet( rs, scalarColumns[i], session, null );
362 }
363 return row;
364 }
365 }
366 else if ( !hasTransform ) {
367 return row.length == 1 ? row[0] : row;
368 }
369 else {
370 return row;
371 }
372
373 }
374
375 protected List getResultList(List results, ResultTransformer resultTransformer) throws QueryException {
376 // meant to handle dynamic instantiation queries...
377 HolderInstantiator holderInstantiator = HolderInstantiator.getHolderInstantiator(selectNewTransformer, resultTransformer, queryReturnAliases);
378 if ( holderInstantiator.isRequired() ) {
379 for ( int i = 0; i < results.size(); i++ ) {
380 Object[] row = ( Object[] ) results.get( i );
381 Object result = holderInstantiator.instantiate(row);
382 results.set( i, result );
383 }
384
385 if(!hasSelectNew() && resultTransformer!=null) {
386 return resultTransformer.transformList(results);
387 } else {
388 return results;
389 }
390 } else {
391 return results;
392 }
393 }
394
395 // --- Query translator methods ---
396
397 public List list(
398 SessionImplementor session,
399 QueryParameters queryParameters) throws HibernateException {
400 checkQuery( queryParameters );
401 return list( session, queryParameters, queryTranslator.getQuerySpaces(), queryReturnTypes );
402 }
403
404 private void checkQuery(QueryParameters queryParameters) {
405 if ( hasSelectNew() && queryParameters.getResultTransformer() != null ) {
406 throw new QueryException( "ResultTransformer is not allowed for 'select new' queries." );
407 }
408 }
409
410 public Iterator iterate(
411 QueryParameters queryParameters,
412 EventSource session) throws HibernateException {
413 checkQuery( queryParameters );
414 final boolean stats = session.getFactory().getStatistics().isStatisticsEnabled();
415 long startTime = 0;
416 if ( stats ) {
417 startTime = System.currentTimeMillis();
418 }
419
420 try {
421
422 final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
423
424 if(queryParameters.isCallable()) {
425 throw new QueryException("iterate() not supported for callable statements");
426 }
427 final ResultSet rs = getResultSet(st, queryParameters.hasAutoDiscoverScalarTypes(), false, queryParameters.getRowSelection(), session);
428 final Iterator result = new IteratorImpl(
429 rs,
430 st,
431 session,
432 queryReturnTypes,
433 queryTranslator.getColumnNames(),
434 HolderInstantiator.getHolderInstantiator(selectNewTransformer, queryParameters.getResultTransformer(), queryReturnAliases)
435 );
436
437 if ( stats ) {
438 session.getFactory().getStatisticsImplementor().queryExecuted(
439 // "HQL: " + queryTranslator.getQueryString(),
440 getQueryIdentifier(),
441 0,
442 System.currentTimeMillis() - startTime
443 );
444 }
445
446 return result;
447
448 }
449 catch ( SQLException sqle ) {
450 throw JDBCExceptionHelper.convert(
451 getFactory().getSQLExceptionConverter(),
452 sqle,
453 "could not execute query using iterate",
454 getSQLString()
455 );
456 }
457
458 }
459
460 public ScrollableResults scroll(
461 final QueryParameters queryParameters,
462 final SessionImplementor session) throws HibernateException {
463 checkQuery( queryParameters );
464 return scroll( queryParameters, queryReturnTypes, HolderInstantiator.getHolderInstantiator(selectNewTransformer, queryParameters.getResultTransformer(), queryReturnAliases), session );
465 }
466
467 // -- Implementation private methods --
468
469 private Object[] toResultRow(Object[] row) {
470 if ( selectLength == row.length ) {
471 return row;
472 }
473 else {
474 Object[] result = new Object[selectLength];
475 int j = 0;
476 for ( int i = 0; i < row.length; i++ ) {
477 if ( includeInSelect[i] ) {
478 result[j++] = row[i];
479 }
480 }
481 return result;
482 }
483 }
484
485 /**
486 * Returns the locations of all occurrences of the named parameter.
487 */
488 public int[] getNamedParameterLocs(String name) throws QueryException {
489 return queryTranslator.getParameterTranslations().getNamedParameterSqlLocations( name );
490 }
491
492 /**
493 * We specifically override this method here, because in general we know much more
494 * about the parameters and their appropriate bind positions here then we do in
495 * our super because we track them explciitly here through the ParameterSpecification
496 * interface.
497 *
498 * @param queryParameters The encapsulation of the parameter values to be bound.
499 * @param startIndex The position from which to start binding parameter values.
500 * @param session The originating session.
501 * @return The number of JDBC bind positions actually bound during this method execution.
502 * @throws SQLException Indicates problems performing the binding.
503 */
504 protected int bindParameterValues(
505 final PreparedStatement statement,
506 final QueryParameters queryParameters,
507 final int startIndex,
508 final SessionImplementor session) throws SQLException {
509 int position = bindFilterParameterValues( statement, queryParameters, startIndex, session );
510 List parameterSpecs = queryTranslator.getSqlAST().getWalker().getParameters();
511 Iterator itr = parameterSpecs.iterator();
512 while ( itr.hasNext() ) {
513 ParameterSpecification spec = ( ParameterSpecification ) itr.next();
514 position += spec.bind( statement, queryParameters, session, position );
515 }
516 return position - startIndex;
517 }
518
519 private int bindFilterParameterValues(
520 PreparedStatement st,
521 QueryParameters queryParameters,
522 int position,
523 SessionImplementor session) throws SQLException {
524 // todo : better to handle dynamic filters through implicit DynamicFilterParameterSpecification
525 // see the discussion there in DynamicFilterParameterSpecification's javadocs as to why
526 // it is currently not done that way.
527 int filteredParamCount = queryParameters.getFilteredPositionalParameterTypes() == null
528 ? 0
529 : queryParameters.getFilteredPositionalParameterTypes().length;
530 int nonfilteredParamCount = queryParameters.getPositionalParameterTypes() == null
531 ? 0
532 : queryParameters.getPositionalParameterTypes().length;
533 int filterParamCount = filteredParamCount - nonfilteredParamCount;
534 for ( int i = 0; i < filterParamCount; i++ ) {
535 Type type = queryParameters.getFilteredPositionalParameterTypes()[i];
536 Object value = queryParameters.getFilteredPositionalParameterValues()[i];
537 type.nullSafeSet( st, value, position, session );
538 position += type.getColumnSpan( getFactory() );
539 }
540
541 return position;
542 }
543 }