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