/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.hql.internal.ast.tree;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.hibernate.QueryException;
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
import org.hibernate.hql.internal.antlr.SqlTokenTypes;
import org.hibernate.hql.internal.ast.util.ASTAppender;
import org.hibernate.hql.internal.ast.util.ASTIterator;
import org.hibernate.hql.internal.ast.util.ASTPrinter;
import org.hibernate.hql.internal.ast.util.TokenPrinters;
import org.hibernate.type.Type;

import antlr.SemanticException;
import antlr.collections.AST;

Represents the list of expressions in a SELECT clause.
Author:josh
/** * Represents the list of expressions in a SELECT clause. * * @author josh */
public class SelectClause extends SelectExpressionList { private boolean prepared; private boolean scalarSelect; private List fromElementsForLoad = new ArrayList(); private List alreadyRenderedIdentifiers = new ArrayList(); //private Type[] sqlResultTypes; private Type[] queryReturnTypes; private String[][] columnNames; private List collectionFromElements; private String[] aliases; private int[] columnNamesStartPositions; // Currently we can only have one... private AggregatedSelectExpression aggregatedSelectExpression;
Does this SelectClause represent a scalar query
Returns:True if this is a scalara select clause; false otherwise.
/** * Does this SelectClause represent a scalar query * * @return True if this is a scalara select clause; false otherwise. */
public boolean isScalarSelect() { return scalarSelect; } public boolean isDistinct() { return getFirstChild() != null && getFirstChild().getType() == SqlTokenTypes.DISTINCT; }
FromElements which need to be accounted for in the load phase (either for return or for fetch).
Returns:List of appropriate FromElements.
/** * FromElements which need to be accounted for in the load phase (either for return or for fetch). * * @return List of appropriate FromElements. */
public List getFromElementsForLoad() { return fromElementsForLoad; } /* * The types represented in the SQL result set. * * @return The types represented in the SQL result set. */ /*public Type[] getSqlResultTypes() { return sqlResultTypes; }*/
The types actually being returned from this query at the "object level".
Returns:The query return types.
/** * The types actually being returned from this query at the "object level". * * @return The query return types. */
public Type[] getQueryReturnTypes() { return queryReturnTypes; }
The HQL aliases, or generated aliases
Returns:the aliases
/** * The HQL aliases, or generated aliases * * @return the aliases */
public String[] getQueryReturnAliases() { return aliases; }
The column alias names being used in the generated SQL.
Returns:The SQL column aliases.
/** * The column alias names being used in the generated SQL. * * @return The SQL column aliases. */
public String[][] getColumnNames() { return columnNames; } public AggregatedSelectExpression getAggregatedSelectExpression() { return aggregatedSelectExpression; }
Prepares an explicitly defined select clause.
Params:
  • fromClause – The from clause linked to this select clause.
Throws:
/** * Prepares an explicitly defined select clause. * * @param fromClause The from clause linked to this select clause. * * @throws SemanticException indicates a semntic issue with the explicit select clause. */
public void initializeExplicitSelectClause(FromClause fromClause) throws SemanticException { if ( prepared ) { throw new IllegalStateException( "SelectClause was already prepared!" ); } //explicit = true; // This is an explict Select. //ArrayList sqlResultTypeList = new ArrayList(); ArrayList queryReturnTypeList = new ArrayList(); // First, collect all of the select expressions. // NOTE: This must be done *before* invoking setScalarColumnText() because setScalarColumnText() // changes the AST!!! SelectExpression[] selectExpressions = collectSelectExpressions(); // we only support parameters in select in the case of INSERT...SELECT statements if ( getParameterPositions().size() > 0 && getWalker().getStatementType() != HqlSqlTokenTypes.INSERT ) { throw new QueryException( "Parameters are only supported in SELECT clauses when used as part of a INSERT INTO DML statement" ); } for ( SelectExpression selectExpression : selectExpressions ) { if ( AggregatedSelectExpression.class.isInstance( selectExpression ) ) { aggregatedSelectExpression = (AggregatedSelectExpression) selectExpression; queryReturnTypeList.addAll( aggregatedSelectExpression.getAggregatedSelectionTypeList() ); scalarSelect = true; } else { // we have no choice but to do this check here // this is not very elegant but the "right way" would most likely involve a bigger rewrite so as to // treat ParameterNodes in select clauses as SelectExpressions boolean inSubquery = selectExpression instanceof QueryNode && ( (QueryNode) selectExpression ).getFromClause().getParentFromClause() != null; if ( getWalker().getStatementType() == HqlSqlTokenTypes.INSERT && inSubquery ) { // we do not support parameters for subqueries in INSERT...SELECT if ( ( (QueryNode) selectExpression ).getSelectClause().getParameterPositions().size() > 0 ) { throw new QueryException( "Use of parameters in subqueries of INSERT INTO DML statements is not supported." ); } } Type type = selectExpression.getDataType(); if ( type == null ) { throw new QueryException( "No data type for node: " + selectExpression.getClass().getName() + " " + TokenPrinters.SQL_TOKEN_PRINTER.showAsString( (AST) selectExpression, "" ) ); } //sqlResultTypeList.add( type ); // If the data type is not an association type, it could not have been in the FROM clause. if ( selectExpression.isScalar() ) { scalarSelect = true; } if ( isReturnableEntity( selectExpression ) ) { fromElementsForLoad.add( selectExpression.getFromElement() ); } // Always add the type to the return type list. queryReturnTypeList.add( type ); } } //init the aliases, after initing the constructornode initAliases( selectExpressions ); if ( !getWalker().isShallowQuery() ) { // add the fetched entities List fromElements = fromClause.getProjectionList(); // Get ready to start adding nodes. ASTAppender appender = new ASTAppender( getASTFactory(), this ); int size = fromElements.size(); Iterator iterator = fromElements.iterator(); for ( int k = 0; iterator.hasNext(); k++ ) { FromElement fromElement = (FromElement) iterator.next(); if ( fromElement.isFetch() ) { FromElement origin = null; if ( fromElement.getRealOrigin() == null ) { // work around that crazy issue where the tree contains // "empty" FromElements (no text); afaict, this is caused // by FromElementFactory.createCollectionJoin() if ( fromElement.getOrigin() == null ) { throw new QueryException( "Unable to determine origin of join fetch [" + fromElement.getDisplayText() + "]" ); } else { origin = fromElement.getOrigin(); } } else { origin = fromElement.getRealOrigin(); } if ( !fromElementsForLoad.contains( origin ) // work around that fetch joins of element collections where their parent instead of the root is selected && ( !fromElement.isCollectionJoin() || !fromElementsForLoad.contains( fromElement.getFetchOrigin() ) ) ) { throw new QueryException( "query specified join fetching, but the owner " + "of the fetched association was not present in the select list " + "[" + fromElement.getDisplayText() + "]" ); } Type type = fromElement.getSelectType(); addCollectionFromElement( fromElement ); if ( type != null ) { boolean collectionOfElements = fromElement.isCollectionOfValuesOrComponents(); if ( !collectionOfElements ) { // Add the type to the list of returned sqlResultTypes. fromElement.setIncludeSubclasses( true ); fromElementsForLoad.add( fromElement ); //sqlResultTypeList.add( type ); // Generate the select expression. String text = fromElement.renderIdentifierSelect( size, k ); alreadyRenderedIdentifiers.add( text ); SelectExpressionImpl generatedExpr = (SelectExpressionImpl) appender.append( SqlTokenTypes.SELECT_EXPR, text, false ); if ( generatedExpr != null ) { generatedExpr.setFromElement( fromElement ); } } } } } // generate id select fragment and then property select fragment for // each expression, just like generateSelectFragments(). renderNonScalarSelects( collectSelectExpressions(), fromClause ); } if ( scalarSelect || getWalker().isShallowQuery() ) { // If there are any scalars (non-entities) selected, render the select column aliases. renderScalarSelects( selectExpressions, fromClause ); } finishInitialization( /*sqlResultTypeList,*/ queryReturnTypeList ); } private void finishInitialization(ArrayList queryReturnTypeList) { queryReturnTypes = (Type[]) queryReturnTypeList.toArray( new Type[queryReturnTypeList.size()] ); initializeColumnNames(); prepared = true; } private void initializeColumnNames() { // Generate a 2d array of column names, the first dimension is parallel with the // return types array. The second dimension is the list of column names for each // type. // todo: we should really just collect these from the various SelectExpressions, rather than regenerating here columnNames = getSessionFactoryHelper().generateColumnNames( queryReturnTypes ); columnNamesStartPositions = new int[columnNames.length]; int startPosition = 1; for ( int i = 0; i < columnNames.length; i++ ) { columnNamesStartPositions[i] = startPosition; startPosition += columnNames[i].length; } } public int getColumnNamesStartPosition(int i) { return columnNamesStartPositions[i]; }
Prepares a derived (i.e., not explicitly defined in the query) select clause.
Params:
  • fromClause – The from clause to which this select clause is linked.
/** * Prepares a derived (i.e., not explicitly defined in the query) select clause. * * @param fromClause The from clause to which this select clause is linked. */
public void initializeDerivedSelectClause(FromClause fromClause) throws SemanticException { if ( prepared ) { throw new IllegalStateException( "SelectClause was already prepared!" ); } //Used to be tested by the TCK but the test is no longer here // if ( getSessionFactoryHelper().isStrictJPAQLComplianceEnabled() && !getWalker().isSubQuery() ) { // // NOTE : the isSubQuery() bit is a temporary hack... // throw new QuerySyntaxException( "JPA-QL compliance requires select clause" ); // } List fromElements = fromClause.getProjectionList(); ASTAppender appender = new ASTAppender( getASTFactory(), this ); // Get ready to start adding nodes. int size = fromElements.size(); ArrayList queryReturnTypeList = new ArrayList( size ); Iterator iterator = fromElements.iterator(); for ( int k = 0; iterator.hasNext(); k++ ) { FromElement fromElement = (FromElement) iterator.next(); Type type = fromElement.getSelectType(); addCollectionFromElement( fromElement ); if ( type != null ) { boolean collectionOfElements = fromElement.isCollectionOfValuesOrComponents(); if ( !collectionOfElements ) { if ( !fromElement.isFetch() ) { // Add the type to the list of returned sqlResultTypes. queryReturnTypeList.add( type ); } fromElementsForLoad.add( fromElement ); // Generate the select expression. String text = fromElement.renderIdentifierSelect( size, k ); SelectExpressionImpl generatedExpr = (SelectExpressionImpl) appender.append( SqlTokenTypes.SELECT_EXPR, text, false ); if ( generatedExpr != null ) { generatedExpr.setFromElement( fromElement ); } } } } // Get all the select expressions (that we just generated) and render the select. SelectExpression[] selectExpressions = collectSelectExpressions(); if ( getWalker().isShallowQuery() ) { renderScalarSelects( selectExpressions, fromClause ); } else { renderNonScalarSelects( selectExpressions, fromClause ); } finishInitialization( queryReturnTypeList ); } public static boolean VERSION2_SQL; private void addCollectionFromElement(FromElement fromElement) { if ( fromElement.isFetch() ) { if ( fromElement.getQueryableCollection() != null ) { String suffix; if ( collectionFromElements == null ) { collectionFromElements = new ArrayList(); suffix = VERSION2_SQL ? "__" : "0__"; } else { suffix = Integer.toString( collectionFromElements.size() ) + "__"; } collectionFromElements.add( fromElement ); fromElement.setCollectionSuffix( suffix ); } } } @Override protected AST getFirstSelectExpression() { AST n = getFirstChild(); // Skip 'DISTINCT' and 'ALL', so we return the first expression node. while ( n != null && ( n.getType() == SqlTokenTypes.DISTINCT || n.getType() == SqlTokenTypes.ALL ) ) { n = n.getNextSibling(); } return n; } @SuppressWarnings("SimplifiableIfStatement") private boolean isReturnableEntity(SelectExpression selectExpression) throws SemanticException { FromElement fromElement = selectExpression.getFromElement(); boolean isFetchOrValueCollection = fromElement != null && ( fromElement.isFetch() || fromElement.isCollectionOfValuesOrComponents() ); if ( isFetchOrValueCollection ) { return false; } else { return selectExpression.isReturnableEntity(); } } private void renderScalarSelects(SelectExpression[] se, FromClause currentFromClause) throws SemanticException { if ( !currentFromClause.isSubQuery() ) { for ( int i = 0; i < se.length; i++ ) { SelectExpression expr = se[i]; expr.setScalarColumn( i ); // Create SQL_TOKEN nodes for the columns. } } } private void initAliases(SelectExpression[] selectExpressions) { if ( aggregatedSelectExpression == null ) { aliases = new String[selectExpressions.length]; for ( int i = 0; i < selectExpressions.length; i++ ) { aliases[i] = selectExpressions[i].getAlias(); } } else { aliases = aggregatedSelectExpression.getAggregatedAliases(); } } private void renderNonScalarSelects(SelectExpression[] selectExpressions, FromClause currentFromClause) throws SemanticException { ASTAppender appender = new ASTAppender( getASTFactory(), this ); final int size = selectExpressions.length; int nonscalarSize = 0; for ( int i = 0; i < size; i++ ) { if ( !selectExpressions[i].isScalar() ) { nonscalarSize++; } } int j = 0; for ( int i = 0; i < size; i++ ) { if ( !selectExpressions[i].isScalar() ) { SelectExpression expr = selectExpressions[i]; FromElement fromElement = expr.getFromElement(); if ( fromElement != null ) { renderNonScalarIdentifiers( fromElement, nonscalarSize, j, expr, appender ); j++; } } } if ( !currentFromClause.isSubQuery() ) { // Generate the property select tokens. int k = 0; for ( int i = 0; i < size; i++ ) { if ( !selectExpressions[i].isScalar() ) { FromElement fromElement = selectExpressions[i].getFromElement(); if ( fromElement != null ) { renderNonScalarProperties( appender, selectExpressions[i], fromElement, nonscalarSize, k ); k++; } } } } } private void renderNonScalarIdentifiers( FromElement fromElement, int nonscalarSize, int j, SelectExpression expr, ASTAppender appender) { if ( !fromElement.getFromClause().isSubQuery() ) { if ( !scalarSelect && !getWalker().isShallowQuery() ) { // // todo : ugh this is all fugly code // if ( expr instanceof MapKeyNode ) { // // don't over-write node text // } // else if ( expr instanceof MapEntryNode ) { // // don't over-write node text // } // else { // String text = fromElement.renderIdentifierSelect( nonscalarSize, j ); // expr.setText( text ); // } String text = fromElement.renderIdentifierSelect( nonscalarSize, j ); expr.setText( text ); } else { String text = fromElement.renderIdentifierSelect( nonscalarSize, j ); if (! alreadyRenderedIdentifiers.contains(text)) { appender.append( SqlTokenTypes.SQL_TOKEN, text, false ); alreadyRenderedIdentifiers.add(text); } } } } private void renderNonScalarProperties( ASTAppender appender, SelectExpression selectExpression, FromElement fromElement, int nonscalarSize, int k) { final String text; if ( selectExpression instanceof MapKeyNode ) { final MapKeyNode mapKeyNode = (MapKeyNode) selectExpression; if ( mapKeyNode.getMapKeyEntityFromElement() != null ) { text = mapKeyNode.getMapKeyEntityFromElement().renderMapKeyPropertySelectFragment( nonscalarSize, k ); } else { text = fromElement.renderPropertySelect( nonscalarSize, k ); } } else if ( selectExpression instanceof MapEntryNode ) { text = fromElement.renderMapEntryPropertySelectFragment( nonscalarSize, k ); } else { text = fromElement.renderPropertySelect( nonscalarSize, k ); } appender.append( SqlTokenTypes.SQL_TOKEN, text, false ); if ( fromElement.getQueryableCollection() != null && fromElement.isFetch() ) { String subText1 = fromElement.renderCollectionSelectFragment( nonscalarSize, k ); appender.append( SqlTokenTypes.SQL_TOKEN, subText1, false ); } // Look through the FromElement's children to find any collections of values that should be fetched... ASTIterator itr = new ASTIterator( fromElement ); while ( itr.hasNext() ) { FromElement child = (FromElement) itr.next(); if ( child.isCollectionOfValuesOrComponents() && child.isFetch() ) { // Need a better way to define the suffixes here... final String subText2 = child.renderValueCollectionSelectFragment( nonscalarSize, nonscalarSize + k ); appender.append( SqlTokenTypes.SQL_TOKEN, subText2, false ); } } } public List getCollectionFromElements() { return collectionFromElements; } }