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.hql.ast.tree;
26
27 import java.lang.reflect.Constructor;
28 import java.util.ArrayList;
29 import java.util.Iterator;
30 import java.util.List;
31
32 import org.hibernate.hql.antlr.SqlTokenTypes;
33 import org.hibernate.hql.ast.util.ASTAppender;
34 import org.hibernate.hql.ast.util.ASTIterator;
35 import org.hibernate.hql.ast.util.ASTPrinter;
36 import org.hibernate.type.Type;
37 import org.hibernate.QueryException;
38
39 import antlr.SemanticException;
40 import antlr.collections.AST;
41
42 /**
43 * Represents the list of expressions in a SELECT clause.
44 *
45 * @author josh
46 */
47 public class SelectClause extends SelectExpressionList {
48
49 private boolean prepared = false;
50 private boolean scalarSelect;
51
52 private List fromElementsForLoad = new ArrayList();
53 //private Type[] sqlResultTypes;
54 private Type[] queryReturnTypes;
55 private String[][] columnNames;
56 private ConstructorNode constructorNode;
57 private List collectionFromElements;
58 private String[] aliases;
59
60 /**
61 * Does this SelectClause represent a scalar query
62 *
63 * @return True if this is a scalara select clause; false otherwise.
64 */
65 public boolean isScalarSelect() {
66 return scalarSelect;
67 }
68
69 public boolean isDistinct() {
70 return getFirstChild() != null && getFirstChild().getType() == SqlTokenTypes.DISTINCT;
71 }
72
73 /**
74 * FromElements which need to be accounted for in the load phase (either for return or for fetch).
75 *
76 * @return List of appropriate FromElements.
77 */
78 public List getFromElementsForLoad() {
79 return fromElementsForLoad;
80 }
81
82 /*
83 * The types represented in the SQL result set.
84 *
85 * @return The types represented in the SQL result set.
86 */
87 /*public Type[] getSqlResultTypes() {
88 return sqlResultTypes;
89 }*/
90
91 /**
92 * The types actually being returned from this query at the "object level".
93 *
94 * @return The query return types.
95 */
96 public Type[] getQueryReturnTypes() {
97 return queryReturnTypes;
98 }
99
100 /**
101 * The HQL aliases, or generated aliases
102 */
103 public String[] getQueryReturnAliases() {
104 return aliases;
105 }
106
107 /**
108 * The column alias names being used in the generated SQL.
109 *
110 * @return The SQL column aliases.
111 */
112 public String[][] getColumnNames() {
113 return columnNames;
114 }
115
116 /**
117 * The constructor to use for dynamic instantiation queries.
118 *
119 * @return The appropriate Constructor reference, or null if not a
120 * dynamic instantiation query.
121 */
122 public Constructor getConstructor() {
123 return constructorNode == null ? null : constructorNode.getConstructor();
124 }
125
126 public boolean isMap() {
127 return constructorNode == null ? false : constructorNode.isMap();
128 }
129
130 public boolean isList() {
131 return constructorNode == null ? false : constructorNode.isList();
132 }
133
134 /**
135 * Prepares an explicitly defined select clause.
136 *
137 * @param fromClause The from clause linked to this select clause.
138 * @throws SemanticException
139 */
140 public void initializeExplicitSelectClause(FromClause fromClause) throws SemanticException {
141 if ( prepared ) {
142 throw new IllegalStateException( "SelectClause was already prepared!" );
143 }
144
145 //explicit = true; // This is an explict Select.
146 //ArrayList sqlResultTypeList = new ArrayList();
147 ArrayList queryReturnTypeList = new ArrayList();
148
149 // First, collect all of the select expressions.
150 // NOTE: This must be done *before* invoking setScalarColumnText() because setScalarColumnText()
151 // changes the AST!!!
152 SelectExpression[] selectExpressions = collectSelectExpressions();
153
154 for ( int i = 0; i < selectExpressions.length; i++ ) {
155 SelectExpression expr = selectExpressions[i];
156
157 if ( expr.isConstructor() ) {
158 constructorNode = ( ConstructorNode ) expr;
159 List constructorArgumentTypeList = constructorNode.getConstructorArgumentTypeList();
160 //sqlResultTypeList.addAll( constructorArgumentTypeList );
161 queryReturnTypeList.addAll( constructorArgumentTypeList );
162 scalarSelect = true;
163 }
164 else {
165 Type type = expr.getDataType();
166 if ( type == null ) {
167 throw new IllegalStateException( "No data type for node: " + expr.getClass().getName() + " "
168 + new ASTPrinter( SqlTokenTypes.class ).showAsString( ( AST ) expr, "" ) );
169 }
170 //sqlResultTypeList.add( type );
171
172 // If the data type is not an association type, it could not have been in the FROM clause.
173 if ( expr.isScalar() ) {
174 scalarSelect = true;
175 }
176
177 if ( isReturnableEntity( expr ) ) {
178 fromElementsForLoad.add( expr.getFromElement() );
179 }
180
181 // Always add the type to the return type list.
182 queryReturnTypeList.add( type );
183 }
184 }
185
186 //init the aliases, after initing the constructornode
187 initAliases(selectExpressions);
188
189 if ( !getWalker().isShallowQuery() ) {
190 // add the fetched entities
191 List fromElements = fromClause.getProjectionList();
192
193 ASTAppender appender = new ASTAppender( getASTFactory(), this ); // Get ready to start adding nodes.
194 int size = fromElements.size();
195
196 Iterator iterator = fromElements.iterator();
197 for ( int k = 0; iterator.hasNext(); k++ ) {
198 FromElement fromElement = ( FromElement ) iterator.next();
199
200 if ( fromElement.isFetch() ) {
201 FromElement origin = null;
202 if ( fromElement.getRealOrigin() == null ) {
203 // work around that crazy issue where the tree contains
204 // "empty" FromElements (no text); afaict, this is caused
205 // by FromElementFactory.createCollectionJoin()
206 if ( fromElement.getOrigin() == null ) {
207 throw new QueryException( "Unable to determine origin of join fetch [" + fromElement.getDisplayText() + "]" );
208 }
209 else {
210 origin = fromElement.getOrigin();
211 }
212 }
213 else {
214 origin = fromElement.getRealOrigin();
215 }
216 if ( !fromElementsForLoad.contains( origin ) ) {
217 throw new QueryException(
218 "query specified join fetching, but the owner " +
219 "of the fetched association was not present in the select list " +
220 "[" + fromElement.getDisplayText() + "]"
221 );
222 }
223 Type type = fromElement.getSelectType();
224 addCollectionFromElement( fromElement );
225 if ( type != null ) {
226 boolean collectionOfElements = fromElement.isCollectionOfValuesOrComponents();
227 if ( !collectionOfElements ) {
228 // Add the type to the list of returned sqlResultTypes.
229 fromElement.setIncludeSubclasses( true );
230 fromElementsForLoad.add( fromElement );
231 //sqlResultTypeList.add( type );
232 // Generate the select expression.
233 String text = fromElement.renderIdentifierSelect( size, k );
234 SelectExpressionImpl generatedExpr = ( SelectExpressionImpl ) appender.append( SqlTokenTypes.SELECT_EXPR, text, false );
235 if ( generatedExpr != null ) {
236 generatedExpr.setFromElement( fromElement );
237 }
238 }
239 }
240 }
241 }
242
243 // generate id select fragment and then property select fragment for
244 // each expression, just like generateSelectFragments().
245 renderNonScalarSelects( collectSelectExpressions(), fromClause );
246 }
247
248 if ( scalarSelect || getWalker().isShallowQuery() ) {
249 // If there are any scalars (non-entities) selected, render the select column aliases.
250 renderScalarSelects( selectExpressions, fromClause );
251 }
252
253 finishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList );
254 }
255
256 private void finishInitialization(/*ArrayList sqlResultTypeList,*/ ArrayList queryReturnTypeList) {
257 //sqlResultTypes = ( Type[] ) sqlResultTypeList.toArray( new Type[sqlResultTypeList.size()] );
258 queryReturnTypes = ( Type[] ) queryReturnTypeList.toArray( new Type[queryReturnTypeList.size()] );
259 initializeColumnNames();
260 prepared = true;
261 }
262
263 private void initializeColumnNames() {
264 // Generate an 2d array of column names, the first dimension is parallel with the
265 // return types array. The second dimension is the list of column names for each
266 // type.
267
268 // todo: we should really just collect these from the various SelectExpressions, rather than regenerating here
269 columnNames = getSessionFactoryHelper().generateColumnNames( queryReturnTypes );
270 }
271
272 /**
273 * Prepares a derived (i.e., not explicitly defined in the query) select clause.
274 *
275 * @param fromClause The from clause to which this select clause is linked.
276 */
277 public void initializeDerivedSelectClause(FromClause fromClause) throws SemanticException {
278 if ( prepared ) {
279 throw new IllegalStateException( "SelectClause was already prepared!" );
280 }
281 //Used to be tested by the TCK but the test is no longer here
282 // if ( getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() && !getWalker().isSubQuery() ) {
283 // // NOTE : the isSubQuery() bit is a temporary hack...
284 // throw new QuerySyntaxException( "JPA-QL compliance requires select clause" );
285 // }
286 List fromElements = fromClause.getProjectionList();
287
288 ASTAppender appender = new ASTAppender( getASTFactory(), this ); // Get ready to start adding nodes.
289 int size = fromElements.size();
290 ArrayList sqlResultTypeList = new ArrayList( size );
291 ArrayList queryReturnTypeList = new ArrayList( size );
292
293 Iterator iterator = fromElements.iterator();
294 for ( int k = 0; iterator.hasNext(); k++ ) {
295 FromElement fromElement = ( FromElement ) iterator.next();
296 Type type = fromElement.getSelectType();
297
298 addCollectionFromElement( fromElement );
299
300 if ( type != null ) {
301 boolean collectionOfElements = fromElement.isCollectionOfValuesOrComponents();
302 if ( !collectionOfElements ) {
303 if ( !fromElement.isFetch() ) {
304 // Add the type to the list of returned sqlResultTypes.
305 queryReturnTypeList.add( type );
306 }
307 fromElementsForLoad.add( fromElement );
308 sqlResultTypeList.add( type );
309 // Generate the select expression.
310 String text = fromElement.renderIdentifierSelect( size, k );
311 SelectExpressionImpl generatedExpr = ( SelectExpressionImpl ) appender.append( SqlTokenTypes.SELECT_EXPR, text, false );
312 if ( generatedExpr != null ) {
313 generatedExpr.setFromElement( fromElement );
314 }
315 }
316 }
317 }
318
319 // Get all the select expressions (that we just generated) and render the select.
320 SelectExpression[] selectExpressions = collectSelectExpressions();
321
322 if ( getWalker().isShallowQuery() ) {
323 renderScalarSelects( selectExpressions, fromClause );
324 }
325 else {
326 renderNonScalarSelects( selectExpressions, fromClause );
327 }
328 finishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList );
329 }
330
331 public static boolean VERSION2_SQL = false;
332
333 private void addCollectionFromElement(FromElement fromElement) {
334 if ( fromElement.isFetch() ) {
335 if ( fromElement.isCollectionJoin() || fromElement.getQueryableCollection() != null ) {
336 String suffix;
337 if (collectionFromElements==null) {
338 collectionFromElements = new ArrayList();
339 suffix = VERSION2_SQL ? "__" : "0__";
340 }
341 else {
342 suffix = Integer.toString( collectionFromElements.size() ) + "__";
343 }
344 collectionFromElements.add( fromElement );
345 fromElement.setCollectionSuffix( suffix );
346 }
347 }
348 }
349
350 protected AST getFirstSelectExpression() {
351 AST n = getFirstChild();
352 // Skip 'DISTINCT' and 'ALL', so we return the first expression node.
353 while ( n != null && ( n.getType() == SqlTokenTypes.DISTINCT || n.getType() == SqlTokenTypes.ALL ) ) {
354 n = n.getNextSibling();
355 }
356 return n;
357 }
358
359 private boolean isReturnableEntity(SelectExpression selectExpression) throws SemanticException {
360 FromElement fromElement = selectExpression.getFromElement();
361 boolean isFetchOrValueCollection = fromElement != null &&
362 ( fromElement.isFetch() || fromElement.isCollectionOfValuesOrComponents() );
363 if ( isFetchOrValueCollection ) {
364 return false;
365 }
366 else {
367 return selectExpression.isReturnableEntity();
368 }
369 }
370
371 private void renderScalarSelects(SelectExpression[] se, FromClause currentFromClause) throws SemanticException {
372 if ( !currentFromClause.isSubQuery() ) {
373 for ( int i = 0; i < se.length; i++ ) {
374 SelectExpression expr = se[i];
375 expr.setScalarColumnText( i ); // Create SQL_TOKEN nodes for the columns.
376 }
377 }
378 }
379
380 private void initAliases(SelectExpression[] selectExpressions) {
381 if (constructorNode==null) {
382 aliases = new String[selectExpressions.length];
383 for ( int i=0; i<selectExpressions.length; i++ ) {
384 String alias = selectExpressions[i].getAlias();
385 aliases[i] = alias==null ? Integer.toString(i) : alias;
386 }
387 }
388 else {
389 aliases = constructorNode.getAliases();
390 }
391 }
392
393 private void renderNonScalarSelects(SelectExpression[] selectExpressions, FromClause currentFromClause)
394 throws SemanticException {
395 ASTAppender appender = new ASTAppender( getASTFactory(), this );
396 final int size = selectExpressions.length;
397 int nonscalarSize = 0;
398 for ( int i = 0; i < size; i++ ) {
399 if ( !selectExpressions[i].isScalar() ) nonscalarSize++;
400 }
401
402 int j = 0;
403 for ( int i = 0; i < size; i++ ) {
404 if ( !selectExpressions[i].isScalar() ) {
405 SelectExpression expr = selectExpressions[i];
406 FromElement fromElement = expr.getFromElement();
407 if ( fromElement != null ) {
408 renderNonScalarIdentifiers( fromElement, nonscalarSize, j, expr, appender );
409 j++;
410 }
411 }
412 }
413
414 if ( !currentFromClause.isSubQuery() ) {
415 // Generate the property select tokens.
416 int k = 0;
417 for ( int i = 0; i < size; i++ ) {
418 if ( !selectExpressions[i].isScalar() ) {
419 FromElement fromElement = selectExpressions[i].getFromElement();
420 if ( fromElement != null ) {
421 renderNonScalarProperties( appender, fromElement, nonscalarSize, k );
422 k++;
423 }
424 }
425 }
426 }
427 }
428
429 private void renderNonScalarIdentifiers(FromElement fromElement, int nonscalarSize, int j, SelectExpression expr, ASTAppender appender) {
430 String text = fromElement.renderIdentifierSelect( nonscalarSize, j );
431 if ( !fromElement.getFromClause().isSubQuery() ) {
432 if ( !scalarSelect && !getWalker().isShallowQuery() ) {
433 //TODO: is this a bit ugly?
434 expr.setText( text );
435 }
436 else {
437 appender.append( SqlTokenTypes.SQL_TOKEN, text, false );
438 }
439 }
440 }
441
442 private void renderNonScalarProperties(ASTAppender appender, FromElement fromElement, int nonscalarSize, int k) {
443 String text = fromElement.renderPropertySelect( nonscalarSize, k );
444 appender.append( SqlTokenTypes.SQL_TOKEN, text, false );
445 if ( fromElement.getQueryableCollection() != null && fromElement.isFetch() ) {
446 text = fromElement.renderCollectionSelectFragment( nonscalarSize, k );
447 appender.append( SqlTokenTypes.SQL_TOKEN, text, false );
448 }
449 // Look through the FromElement's children to find any collections of values that should be fetched...
450 ASTIterator iter = new ASTIterator( fromElement );
451 while ( iter.hasNext() ) {
452 FromElement child = ( FromElement ) iter.next();
453 if ( child.isCollectionOfValuesOrComponents() && child.isFetch() ) {
454 // Need a better way to define the suffixes here...
455 text = child.renderValueCollectionSelectFragment( nonscalarSize, nonscalarSize + k );
456 appender.append( SqlTokenTypes.SQL_TOKEN, text, false );
457 }
458 }
459 }
460
461 public List getCollectionFromElements() {
462 return collectionFromElements;
463 }
464 }