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