org.hibernate.hql.internal.ast.tree.SelectClause Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* 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 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).
*
* @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 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.
*
* @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;
}
}