1 package org.hibernate.engine.query;
2
3 import org.hibernate.hql.QuerySplitter;
4 import org.hibernate.hql.QueryTranslator;
5 import org.hibernate.hql.ParameterTranslations;
6 import org.hibernate.hql.FilterTranslator;
7 import org.hibernate.util.ArrayHelper;
8 import org.hibernate.util.EmptyIterator;
9 import org.hibernate.util.JoinedIterator;
10 import org.hibernate.util.IdentitySet;
11 import org.hibernate.HibernateException;
12 import org.hibernate.ScrollableResults;
13 import org.hibernate.QueryException;
14 import org.hibernate.type.Type;
15 import org.hibernate.engine.SessionFactoryImplementor;
16 import org.hibernate.engine.QueryParameters;
17 import org.hibernate.engine.SessionImplementor;
18 import org.hibernate.engine.RowSelection;
19 import org.hibernate.event.EventSource;
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22
23 import java.io.Serializable;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.ArrayList;
29 import java.util.Iterator;
30 import java.util.HashMap;
31
32 /**
33 * Defines a query execution plan for an HQL query (or filter).
34 *
35 * @author <a href="mailto:steve@hibernate.org">Steve Ebersole </a>
36 */
37 public class HQLQueryPlan implements Serializable {
38
39 // TODO : keep seperate notions of QT[] here for shallow/non-shallow queries...
40
41 private static final Log log = LogFactory.getLog( HQLQueryPlan.class );
42
43 private final String sourceQuery;
44 private final QueryTranslator[] translators;
45 private final String[] sqlStrings;
46
47 private final ParameterMetadata parameterMetadata;
48 private final ReturnMetadata returnMetadata;
49 private final Set querySpaces;
50
51 private final Set enabledFilterNames;
52 private final boolean shallow;
53
54
55 public HQLQueryPlan(String hql, boolean shallow, Map enabledFilters, SessionFactoryImplementor factory) {
56 this( hql, null, shallow, enabledFilters, factory );
57 }
58
59 protected HQLQueryPlan(String hql, String collectionRole, boolean shallow, Map enabledFilters, SessionFactoryImplementor factory) {
60 this.sourceQuery = hql;
61 this.shallow = shallow;
62
63 Set copy = new HashSet();
64 copy.addAll( enabledFilters.keySet() );
65 this.enabledFilterNames = java.util.Collections.unmodifiableSet( copy );
66
67 Set combinedQuerySpaces = new HashSet();
68 String[] concreteQueryStrings = QuerySplitter.concreteQueries( hql, factory );
69 final int length = concreteQueryStrings.length;
70 translators = new QueryTranslator[length];
71 List sqlStringList = new ArrayList();
72 for ( int i=0; i<length; i++ ) {
73 if ( collectionRole == null ) {
74 translators[i] = factory.getSettings()
75 .getQueryTranslatorFactory()
76 .createQueryTranslator( hql, concreteQueryStrings[i], enabledFilters, factory );
77 translators[i].compile( factory.getSettings().getQuerySubstitutions(), shallow );
78 }
79 else {
80 translators[i] = factory.getSettings()
81 .getQueryTranslatorFactory()
82 .createFilterTranslator( hql, concreteQueryStrings[i], enabledFilters, factory );
83 ( ( FilterTranslator ) translators[i] ).compile( collectionRole, factory.getSettings().getQuerySubstitutions(), shallow );
84 }
85 combinedQuerySpaces.addAll( translators[i].getQuerySpaces() );
86 sqlStringList.addAll( translators[i].collectSqlStrings() );
87 }
88
89 this.sqlStrings = ArrayHelper.toStringArray( sqlStringList );
90 this.querySpaces = combinedQuerySpaces;
91
92 if ( length == 0 ) {
93 parameterMetadata = new ParameterMetadata( null, null );
94 returnMetadata = null;
95 }
96 else {
97 this.parameterMetadata = buildParameterMetadata( translators[0].getParameterTranslations(), hql );
98 if ( translators[0].isManipulationStatement() ) {
99 returnMetadata = null;
100 }
101 else {
102 if ( length > 1 ) {
103 final int returns = translators[0].getReturnTypes().length;
104 returnMetadata = new ReturnMetadata( translators[0].getReturnAliases(), new Type[returns] );
105 }
106 else {
107 returnMetadata = new ReturnMetadata( translators[0].getReturnAliases(), translators[0].getReturnTypes() );
108 }
109 }
110 }
111 }
112
113 public String getSourceQuery() {
114 return sourceQuery;
115 }
116
117 public Set getQuerySpaces() {
118 return querySpaces;
119 }
120
121 public ParameterMetadata getParameterMetadata() {
122 return parameterMetadata;
123 }
124
125 public ReturnMetadata getReturnMetadata() {
126 return returnMetadata;
127 }
128
129 public Set getEnabledFilterNames() {
130 return enabledFilterNames;
131 }
132
133 public String[] getSqlStrings() {
134 return sqlStrings;
135 }
136
137 public Set getUtilizedFilterNames() {
138 // TODO : add this info to the translator and aggregate it here...
139 return null;
140 }
141
142 public boolean isShallow() {
143 return shallow;
144 }
145
146 public List performList(
147 QueryParameters queryParameters,
148 SessionImplementor session) throws HibernateException {
149 if ( log.isTraceEnabled() ) {
150 log.trace( "find: " + getSourceQuery() );
151 queryParameters.traceParameters( session.getFactory() );
152 }
153 boolean hasLimit = queryParameters.getRowSelection() != null &&
154 queryParameters.getRowSelection().definesLimits();
155 boolean needsLimit = hasLimit && translators.length > 1;
156 QueryParameters queryParametersToUse;
157 if ( needsLimit ) {
158 log.warn( "firstResult/maxResults specified on polymorphic query; applying in memory!" );
159 RowSelection selection = new RowSelection();
160 selection.setFetchSize( queryParameters.getRowSelection().getFetchSize() );
161 selection.setTimeout( queryParameters.getRowSelection().getTimeout() );
162 queryParametersToUse = queryParameters.createCopyUsing( selection );
163 }
164 else {
165 queryParametersToUse = queryParameters;
166 }
167
168 List combinedResults = new ArrayList();
169 IdentitySet distinction = new IdentitySet();
170 int includedCount = -1;
171 translator_loop: for ( int i = 0; i < translators.length; i++ ) {
172 List tmp = translators[i].list( session, queryParametersToUse );
173 if ( needsLimit ) {
174 // NOTE : firstRow is zero-based
175 int first = queryParameters.getRowSelection().getFirstRow() == null
176 ? 0
177 : queryParameters.getRowSelection().getFirstRow().intValue();
178 int max = queryParameters.getRowSelection().getMaxRows() == null
179 ? -1
180 : queryParameters.getRowSelection().getMaxRows().intValue();
181 final int size = tmp.size();
182 for ( int x = 0; x < size; x++ ) {
183 final Object result = tmp.get( x );
184 if ( distinction.add( result ) ) {
185 continue;
186 }
187 includedCount++;
188 if ( includedCount < first ) {
189 continue;
190 }
191 combinedResults.add( result );
192 if ( max >= 0 && includedCount > max ) {
193 // break the outer loop !!!
194 break translator_loop;
195 }
196 }
197 }
198 else {
199 combinedResults.addAll( tmp );
200 }
201 }
202 return combinedResults;
203 }
204
205 public Iterator performIterate(
206 QueryParameters queryParameters,
207 EventSource session) throws HibernateException {
208 if ( log.isTraceEnabled() ) {
209 log.trace( "iterate: " + getSourceQuery() );
210 queryParameters.traceParameters( session.getFactory() );
211 }
212 if ( translators.length == 0 ) {
213 return EmptyIterator.INSTANCE;
214 }
215
216 Iterator[] results = null;
217 boolean many = translators.length > 1;
218 if (many) {
219 results = new Iterator[translators.length];
220 }
221
222 Iterator result = null;
223 for ( int i = 0; i < translators.length; i++ ) {
224 result = translators[i].iterate( queryParameters, session );
225 if (many) results[i] = result;
226 }
227
228 return many ? new JoinedIterator(results) : result;
229 }
230
231 public ScrollableResults performScroll(
232 QueryParameters queryParameters,
233 SessionImplementor session) throws HibernateException {
234 if ( log.isTraceEnabled() ) {
235 log.trace( "iterate: " + getSourceQuery() );
236 queryParameters.traceParameters( session.getFactory() );
237 }
238 if ( translators.length != 1 ) {
239 throw new QueryException( "implicit polymorphism not supported for scroll() queries" );
240 }
241 if ( queryParameters.getRowSelection().definesLimits() && translators[0].containsCollectionFetches() ) {
242 throw new QueryException( "firstResult/maxResults not supported in conjunction with scroll() of a query containing collection fetches" );
243 }
244
245 return translators[0].scroll( queryParameters, session );
246 }
247
248 public int performExecuteUpdate(QueryParameters queryParameters, SessionImplementor session)
249 throws HibernateException {
250 if ( log.isTraceEnabled() ) {
251 log.trace( "executeUpdate: " + getSourceQuery() );
252 queryParameters.traceParameters( session.getFactory() );
253 }
254 if ( translators.length != 1 ) {
255 log.warn( "manipulation query [" + getSourceQuery() + "] resulted in [" + translators.length + "] split queries" );
256 }
257 int result = 0;
258 for ( int i = 0; i < translators.length; i++ ) {
259 result += translators[i].executeUpdate( queryParameters, session );
260 }
261 return result;
262 }
263
264 private ParameterMetadata buildParameterMetadata(ParameterTranslations parameterTranslations, String hql) {
265 long start = System.currentTimeMillis();
266 ParamLocationRecognizer recognizer = ParamLocationRecognizer.parseLocations( hql );
267 long end = System.currentTimeMillis();
268 if ( log.isTraceEnabled() ) {
269 log.trace( "HQL param location recognition took " + (end - start) + " mills (" + hql + ")" );
270 }
271
272 int ordinalParamCount = parameterTranslations.getOrdinalParameterCount();
273 int[] locations = ArrayHelper.toIntArray( recognizer.getOrdinalParameterLocationList() );
274 if ( parameterTranslations.supportsOrdinalParameterMetadata() && locations.length != ordinalParamCount ) {
275 throw new HibernateException( "ordinal parameter mismatch" );
276 }
277 ordinalParamCount = locations.length;
278 OrdinalParameterDescriptor[] ordinalParamDescriptors = new OrdinalParameterDescriptor[ordinalParamCount];
279 for ( int i = 1; i <= ordinalParamCount; i++ ) {
280 ordinalParamDescriptors[ i - 1 ] = new OrdinalParameterDescriptor(
281 i,
282 parameterTranslations.supportsOrdinalParameterMetadata()
283 ? parameterTranslations.getOrdinalParameterExpectedType( i )
284 : null,
285 locations[ i - 1 ]
286 );
287 }
288
289 Iterator itr = recognizer.getNamedParameterLocationMap().entrySet().iterator();
290 Map namedParamDescriptorMap = new HashMap();
291 while( itr.hasNext() ) {
292 final Map.Entry entry = ( Map.Entry ) itr.next();
293 final String name = ( String ) entry.getKey();
294 final int[] locArray = ArrayHelper.toIntArray( ( List ) entry.getValue() );
295 namedParamDescriptorMap.put(
296 name,
297 new NamedParameterDescriptor(
298 name,
299 parameterTranslations.getNamedParameterExpectedType( name ),
300 locArray
301 )
302 );
303 }
304
305 return new ParameterMetadata( ordinalParamDescriptors, namedParamDescriptorMap );
306 }
307
308 public QueryTranslator[] getTranslators() {
309 QueryTranslator[] copy = new QueryTranslator[translators.length];
310 System.arraycopy(translators, 0, copy, 0, copy.length);
311 return copy;
312 }
313 }