All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.hql.internal.ast.tree.SelectClause Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * 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 .
 */
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
 */
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
	 *
	 * @return True if this is a scalar 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).
	 *
	 * @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".
	 *
	 * @return The query return types.
	 */
	public Type[] getQueryReturnTypes() {
		return queryReturnTypes;
	}

	/**
	 * The HQL aliases, or generated aliases
	 *
	 * @return the aliases
	 */
	public String[] getQueryReturnAliases() {
		return 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.
	 *
	 * @param fromClause The from clause linked to this select clause.
	 *
	 * @throws SemanticException indicates a semantic 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 explicit 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 initiating 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() ) {
					final FromElement origin;
					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 ) && !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.
	 *
	 * @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;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy