org.hibernate.hql.internal.ast.tree.SelectClause.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.hql.internal.ast.tree.SelectClause.java

Source

/*
 * 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
 */
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;
    }
}