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

com.blazebit.persistence.impl.SelectManager Maven / Gradle / Ivy

There is a newer version: 1.2.0-Alpha1
Show newest version
/*
 * Copyright 2014 Blazebit.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.blazebit.persistence.impl;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.Tuple;
import javax.persistence.metamodel.Metamodel;

import com.blazebit.persistence.CaseWhenStarterBuilder;
import com.blazebit.persistence.FullQueryBuilder;
import com.blazebit.persistence.ObjectBuilder;
import com.blazebit.persistence.SelectObjectBuilder;
import com.blazebit.persistence.SimpleCaseWhenStarterBuilder;
import com.blazebit.persistence.SubqueryInitiator;
import com.blazebit.persistence.impl.builder.expression.CaseWhenBuilderImpl;
import com.blazebit.persistence.impl.builder.expression.ExpressionBuilder;
import com.blazebit.persistence.impl.builder.expression.ExpressionBuilderEndedListenerImpl;
import com.blazebit.persistence.impl.builder.expression.SimpleCaseWhenBuilderImpl;
import com.blazebit.persistence.impl.builder.expression.SuperExpressionSubqueryBuilderListener;
import com.blazebit.persistence.impl.builder.object.ClassObjectBuilder;
import com.blazebit.persistence.impl.builder.object.ConstructorObjectBuilder;
import com.blazebit.persistence.impl.builder.object.SelectObjectBuilderImpl;
import com.blazebit.persistence.impl.builder.object.TupleObjectBuilder;
import com.blazebit.persistence.impl.expression.Expression;
import com.blazebit.persistence.impl.expression.Expression.ResultVisitor;
import com.blazebit.persistence.impl.expression.Expression.Visitor;
import com.blazebit.persistence.impl.expression.ExpressionFactory;
import com.blazebit.persistence.impl.expression.PathElementExpression;
import com.blazebit.persistence.impl.expression.PathExpression;
import com.blazebit.persistence.impl.expression.PropertyExpression;
import com.blazebit.persistence.impl.expression.SimplePathReference;
import com.blazebit.persistence.impl.expression.SubqueryExpression;
import com.blazebit.persistence.impl.jpaprovider.JpaProvider;

/**
 *
 * @author Christian Beikov
 * @author Moritz Becker
 * @since 1.0
 */
public class SelectManager extends AbstractManager {

    private final List selectInfos = new ArrayList();
    private boolean distinct = false;
    private SelectObjectBuilderImpl selectObjectBuilder;
    private ObjectBuilder objectBuilder;
    private SubqueryBuilderListenerImpl subqueryBuilderListener;
    // needed for tuple/alias matching
    private final Map selectAliasToPositionMap = new HashMap();
    private final SelectObjectBuilderEndedListenerImpl selectObjectBuilderEndedListener = new SelectObjectBuilderEndedListenerImpl();
    private CaseExpressionBuilderListener caseExpressionBuilderListener;
    private final JoinManager joinManager;
    private final AliasManager aliasManager;
    private final SubqueryInitiatorFactory subqueryInitFactory;
    private final ExpressionFactory expressionFactory;
    private final JpaProvider jpaProvider;
    private final GroupByUsableDetectionVisitor groupByUsableDetectionVisitor = new GroupByUsableDetectionVisitor(false);
    
    @SuppressWarnings("unchecked")
    public SelectManager(ResolvingQueryGenerator queryGenerator, ParameterManager parameterManager, JoinManager joinManager, AliasManager aliasManager, SubqueryInitiatorFactory subqueryInitFactory, ExpressionFactory expressionFactory, JpaProvider jpaProvider, Class resultClazz) {
        super(queryGenerator, parameterManager);
        this.joinManager = joinManager;
        this.aliasManager = aliasManager;
        this.subqueryInitFactory = subqueryInitFactory;
        this.expressionFactory = expressionFactory;
        this.jpaProvider = jpaProvider;
        if (resultClazz.equals(Tuple.class)) {
            objectBuilder = (ObjectBuilder) new TupleObjectBuilder(selectInfos, selectAliasToPositionMap);
        }
    }

    void verifyBuilderEnded() {
        if (subqueryBuilderListener != null) {
            subqueryBuilderListener.verifySubqueryBuilderEnded();
        }
        if (caseExpressionBuilderListener != null) {
            caseExpressionBuilderListener.verifyBuilderEnded();
        }
        selectObjectBuilderEndedListener.verifyBuilderEnded();
    }

    ObjectBuilder getSelectObjectBuilder() {
        return objectBuilder;
    }

    public List getSelectInfos() {
        return selectInfos;
    }
    
    /**
     * 
     * @return an array with length 2. 
     * Element 0 is true if the select clause contains a any expression that would be required in group by when aggregates are used.
     * Element 1 is true if the select clause contains a any complex expression that would be required in group by when aggregates are used.
     */
    public boolean[] containsGroupBySelect(boolean treatSizeAsAggregate) {
    	GroupByExpressionGatheringVisitor gatheringVisitor = new GroupByExpressionGatheringVisitor(treatSizeAsAggregate);
    	boolean containsGroupBySelect = false;
    	for (SelectInfo selectInfo : selectInfos) {
    		if (!(selectInfo.getExpression() instanceof PathExpression)) {
    			selectInfo.getExpression().accept(gatheringVisitor);
    		} else {
    			containsGroupBySelect = true;
    		}
    	}
    	boolean containsComplexGroupBySelect = !gatheringVisitor.getExpressions().isEmpty();
    	return new boolean[] {containsGroupBySelect || containsComplexGroupBySelect, containsComplexGroupBySelect};
    }
    
    public boolean containsSizeSelect() {
        List infos = selectInfos;
        int size = selectInfos.size();
        for (int i = 0; i < size; i++) {
            final SelectInfo selectInfo = infos.get(i);
            if (ExpressionUtils.containsSizeExpression(selectInfo.getExpression())) {
                return true;
            }
        }
        return false;
    }

    void acceptVisitor(Visitor v) {
        List infos = selectInfos;
        int size = selectInfos.size();
        for (int i = 0; i < size; i++) {
            final SelectInfo selectInfo = infos.get(i);
            selectInfo.getExpression().accept(v);
        }
    }

     X acceptVisitor(ResultVisitor v, X stopValue) {
        List infos = selectInfos;
        int size = selectInfos.size();
        for (int i = 0; i < size; i++) {
            final SelectInfo selectInfo = infos.get(i);
            if (stopValue.equals(selectInfo.getExpression().accept(v))) {
                return stopValue;
            }
        }

        return null;
    }

    /**
     * Builds the clauses needed for the group by clause for a query that uses aggregate functions to work.
     * 
     * @param m
     * @return
     */
    void buildGroupByClauses(final Metamodel m, Set clauses) {
        boolean conditionalContext = queryGenerator.setConditionalContext(false);
        StringBuilder sb = new StringBuilder();

        Set componentPaths = new LinkedHashSet();
        EntitySelectResolveVisitor resolveVisitor = new EntitySelectResolveVisitor(m, componentPaths);

        // When no select infos are available, it can only be a root entity select
        if (selectInfos.isEmpty()) {
            List roots = joinManager.getRoots();
            
            if (roots.size() > 1) {
            	throw new IllegalArgumentException("Empty select not allowed when having multiple roots!");
            }
            
        	JoinNode rootNode = roots.get(0);
        	String rootAlias = rootNode.getAliasInfo().getAlias();
        	
            List path = Arrays.asList((PathElementExpression) new PropertyExpression(rootAlias));
            resolveVisitor.visit(new PathExpression(path, new SimplePathReference(rootNode, null), false, false));

            for (PathExpression pathExpr : componentPaths) {
                sb.setLength(0);
                queryGenerator.setQueryBuffer(sb);
                pathExpr.accept(queryGenerator);
                clauses.add(sb.toString());
            }
        } else {
            List infos = selectInfos;
            int size = selectInfos.size();
            for (int i = 0; i < size; i++) {
                final SelectInfo selectInfo = infos.get(i);
                componentPaths.clear();
                selectInfo.getExpression().accept(resolveVisitor);

                // The select info can only either an entity select or any other expression
                // but entity selects can't be nested in other expressions, therefore we can differentiate here
                if (componentPaths.size() > 0) {
                    for (PathExpression pathExpr : componentPaths) {
                        sb.setLength(0);
                        queryGenerator.setQueryBuffer(sb);
                        pathExpr.accept(queryGenerator);
                        clauses.add(sb.toString());
                    }
                } else {
                    // This visitor checks if an expression is usable in a group by
                    if (!selectInfo.getExpression().accept(groupByUsableDetectionVisitor)) {
                        sb.setLength(0);
                        queryGenerator.setQueryBuffer(sb);
                        selectInfo.getExpression().accept(queryGenerator);
                        clauses.add(sb.toString());
                    }
                }
            }
        }

        queryGenerator.setConditionalContext(conditionalContext);
    }

    void buildSelect(StringBuilder sb) {
        sb.append("SELECT ");

        if (distinct) {
            sb.append("DISTINCT ");
        }

        List infos = selectInfos;
        int size = selectInfos.size();
        if (size == 0) {
            List roots = joinManager.getRoots();
            
            if (roots.size() > 1) {
            	throw new IllegalArgumentException("Empty select not allowed when having multiple roots!");
            }
            
            sb.append(roots.get(0).getAliasInfo().getAlias());
        } else {
            // we must not replace select alias since we would loose the original expressions
            queryGenerator.setQueryBuffer(sb);
            boolean conditionalContext = queryGenerator.setConditionalContext(false);
            
            for (int i = 0; i < size; i++) {
                if (i != 0) {
                    sb.append(", ");
                }
                
                applySelect(queryGenerator, sb, infos.get(i));
            }
            
            queryGenerator.setConditionalContext(conditionalContext);
        }
    }

    void applyTransformer(ExpressionTransformer transformer) {
        List infos = selectInfos;
        int size = selectInfos.size();
        // carry out transformations
        for (int i = 0; i < size; i++) {
            final SelectInfo selectInfo = infos.get(i);
            Expression transformed = transformer.transform(selectInfo.getExpression(), ClauseType.SELECT, true);
            selectInfo.setExpression(transformed);
        }
    }

    void applySelectInfoTransformer(SelectInfoTransformer selectInfoTransformer) {
        List infos = selectInfos;
        int size = selectInfos.size();
        for (int i = 0; i < size; i++) {
            final SelectInfo selectInfo = infos.get(i);
            selectInfoTransformer.transform(selectInfo);
        }
    }

    @SuppressWarnings("unchecked")
     SubqueryInitiator selectSubquery(X builder, final String selectAlias) {
        verifyBuilderEnded();

        subqueryBuilderListener = new SelectSubqueryBuilderListener(selectAlias);
        SubqueryInitiator initiator = subqueryInitFactory.createSubqueryInitiator(builder, (SubqueryBuilderListener) subqueryBuilderListener);
        subqueryBuilderListener.onInitiatorStarted(initiator);
        return initiator;
    }

    @SuppressWarnings("unchecked")
     SubqueryInitiator selectSubquery(X builder, String subqueryAlias, Expression expression, String selectAlias) {
        verifyBuilderEnded();

        subqueryBuilderListener = new SuperExpressionSelectSubqueryBuilderListener(subqueryAlias, expression, selectAlias);
        SubqueryInitiator initiator = subqueryInitFactory.createSubqueryInitiator(builder, (SubqueryBuilderListener) subqueryBuilderListener);
        subqueryBuilderListener.onInitiatorStarted(initiator);
        return initiator;
    }

     CaseWhenStarterBuilder selectCase(X builder, final String selectAlias) {
        verifyBuilderEnded();
        caseExpressionBuilderListener = new CaseExpressionBuilderListener(selectAlias);
        return caseExpressionBuilderListener.startBuilder(new CaseWhenBuilderImpl(builder, caseExpressionBuilderListener, subqueryInitFactory, expressionFactory, parameterManager));
    }

     SimpleCaseWhenStarterBuilder selectSimpleCase(X builder, final String selectAlias, Expression caseOperandExpression) {
        verifyBuilderEnded();
        caseExpressionBuilderListener = new CaseExpressionBuilderListener(selectAlias);
        return caseExpressionBuilderListener.startBuilder(new SimpleCaseWhenBuilderImpl(builder, caseExpressionBuilderListener, expressionFactory, caseOperandExpression));
    }

    Class getExpectedQueryResultType() {
        // Tuple case
        if (selectInfos.size() > 1) {
            return Object[].class;
        }

        return jpaProvider.getDefaultQueryResultType();
    }

    void select(Expression expr, String selectAlias) {
        SelectInfo selectInfo = new SelectInfo(expr, selectAlias, aliasManager);
        if (selectAlias != null) {
            aliasManager.registerAliasInfo(selectInfo);
            selectAliasToPositionMap.put(selectAlias, selectAliasToPositionMap.size());
        }
        selectInfos.add(selectInfo);

        registerParameterExpressions(expr);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    > SelectObjectBuilder> selectNew(X builder, Class clazz) {
        if (selectObjectBuilder != null) {
            throw new IllegalStateException("Only one selectNew is allowed");
        }
        if (!selectInfos.isEmpty()) {
            throw new IllegalStateException("No mixture of select and selectNew is allowed");
        }

        selectObjectBuilder = selectObjectBuilderEndedListener.startBuilder(new SelectObjectBuilderImpl(builder, selectObjectBuilderEndedListener, subqueryInitFactory, expressionFactory));
        objectBuilder = new ClassObjectBuilder(clazz);
        return (SelectObjectBuilder) selectObjectBuilder;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    > SelectObjectBuilder> selectNew(X builder, Constructor constructor) {
        if (selectObjectBuilder != null) {
            throw new IllegalStateException("Only one selectNew is allowed");
        }
        if (!selectInfos.isEmpty()) {
            throw new IllegalStateException("No mixture of select and selectNew is allowed");
        }

        selectObjectBuilder = selectObjectBuilderEndedListener.startBuilder(new SelectObjectBuilderImpl(builder, selectObjectBuilderEndedListener, subqueryInitFactory, expressionFactory));
        objectBuilder = new ConstructorObjectBuilder(constructor);
        return (SelectObjectBuilder) selectObjectBuilder;
    }

    @SuppressWarnings("unchecked")
    > void selectNew(X builder, ObjectBuilder objectBuilder) {
        if (selectObjectBuilder != null) {
            throw new IllegalStateException("Only one selectNew is allowed");
        }
        if (!selectInfos.isEmpty()) {
            throw new IllegalStateException("No mixture of select and selectNew is allowed");
        }

        objectBuilder.applySelects(builder);
        this.objectBuilder = (ObjectBuilder) objectBuilder;
    }

    void distinct() {
        this.distinct = true;
    }

    boolean isDistinct() {
        return this.distinct;
    }

    private void applySelect(ResolvingQueryGenerator queryGenerator, StringBuilder sb, SelectInfo select) {
        select.getExpression().accept(queryGenerator);
        if (select.alias != null) {
            sb.append(" AS ").append(select.alias);
        }
    }

    // TODO: needs equals-hashCode implementation
    private class SelectSubqueryBuilderListener extends SubqueryBuilderListenerImpl {

        private final String selectAlias;

        public SelectSubqueryBuilderListener(String selectAlias) {
            this.selectAlias = selectAlias;
        }

        @Override
        public void onBuilderEnded(SubqueryInternalBuilder builder) {
            super.onBuilderEnded(builder);
            select(new SubqueryExpression(builder), selectAlias);
        }
    }

    private class SuperExpressionSelectSubqueryBuilderListener extends SuperExpressionSubqueryBuilderListener {

        private final String selectAlias;

        public SuperExpressionSelectSubqueryBuilderListener(String subqueryAlias, Expression superExpression, String selectAlias) {
            super(subqueryAlias, superExpression);
            this.selectAlias = selectAlias;
        }

        @Override
        public void onBuilderEnded(SubqueryInternalBuilder builder) {
            super.onBuilderEnded(builder);
            select(superExpression, selectAlias);
        }
    }

    private class CaseExpressionBuilderListener extends ExpressionBuilderEndedListenerImpl {

        private final String selectAlias;

        public CaseExpressionBuilderListener(String selectAlias) {
            this.selectAlias = selectAlias;
        }

        @Override
        public void onBuilderEnded(ExpressionBuilder builder) {
            super.onBuilderEnded(builder); // To change body of generated methods, choose Tools | Templates.
            select(builder.getExpression(), selectAlias);
        }

    }

    private class SelectObjectBuilderEndedListenerImpl implements SelectObjectBuilderEndedListener {

        private SelectObjectBuilder currentBuilder;

        protected void verifyBuilderEnded() {
            if (currentBuilder != null) {
                throw new IllegalStateException("A builder was not ended properly.");
            }
        }

        protected > X startBuilder(X builder) {
            if (currentBuilder != null) {
                throw new IllegalStateException("There was an attempt to start a builder but a previous builder was not ended.");
            }

            currentBuilder = builder;
            return builder;
        }

        @Override
        public void onBuilderEnded(Collection> expressions) {
            if (currentBuilder == null) {
                throw new IllegalStateException("There was an attempt to end a builder that was not started or already closed.");
            }
            currentBuilder = null;
            for (Map.Entry e : expressions) {
            	select(e.getKey(), e.getValue());
                // SelectInfo selectInfo = new SelectInfo(e.getKey(), e.getValue(), aliasManager);
                // if (e.getValue() != null) {
                // aliasManager.registerAliasInfo(selectInfo);
                // selectAliasToPositionMap.put(e.getValue(), selectAliasToPositionMap.size());
                // }
                // registerParameterExpressions(e.getKey());
                // SelectManager.this.selectInfos.add(selectInfo);
            }
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy