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

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

/*
 * Copyright 2014 - 2020 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 com.blazebit.persistence.CaseWhenStarterBuilder;
import com.blazebit.persistence.CriteriaBuilder;
import com.blazebit.persistence.FullQueryBuilder;
import com.blazebit.persistence.HavingOrBuilder;
import com.blazebit.persistence.JoinType;
import com.blazebit.persistence.KeysetPage;
import com.blazebit.persistence.MultipleSubqueryInitiator;
import com.blazebit.persistence.ObjectBuilder;
import com.blazebit.persistence.PaginatedCriteriaBuilder;
import com.blazebit.persistence.RestrictionBuilder;
import com.blazebit.persistence.SelectObjectBuilder;
import com.blazebit.persistence.SimpleCaseWhenStarterBuilder;
import com.blazebit.persistence.SubqueryBuilder;
import com.blazebit.persistence.SubqueryInitiator;
import com.blazebit.persistence.impl.function.alias.AliasFunction;
import com.blazebit.persistence.impl.function.count.AbstractCountFunction;
import com.blazebit.persistence.impl.function.countwrapper.CountWrapperFunction;
import com.blazebit.persistence.impl.function.entity.EntityFunction;
import com.blazebit.persistence.impl.function.limit.LimitFunction;
import com.blazebit.persistence.impl.function.nullsubquery.NullSubqueryFunction;
import com.blazebit.persistence.impl.keyset.KeysetMode;
import com.blazebit.persistence.impl.keyset.KeysetPaginationHelper;
import com.blazebit.persistence.impl.keyset.SimpleKeysetLink;
import com.blazebit.persistence.impl.query.CTENode;
import com.blazebit.persistence.impl.query.CustomQuerySpecification;
import com.blazebit.persistence.impl.query.CustomSQLTypedQuery;
import com.blazebit.persistence.impl.query.EntityFunctionNode;
import com.blazebit.persistence.impl.query.QuerySpecification;
import com.blazebit.persistence.parser.expression.Expression;
import com.blazebit.persistence.parser.expression.ExpressionCopyContext;
import com.blazebit.persistence.parser.expression.PathExpression;
import com.blazebit.persistence.parser.util.JpaMetamodelUtils;
import com.blazebit.persistence.parser.util.TypeUtils;
import com.blazebit.persistence.spi.AttributeAccessor;
import com.blazebit.persistence.spi.JpaMetamodelAccessor;

import javax.persistence.Parameter;
import javax.persistence.TypedQuery;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.SingularAttribute;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import static com.blazebit.persistence.parser.util.JpaMetamodelUtils.ATTRIBUTE_NAME_COMPARATOR;

/**
 *
 * @param  The query result type
 * @param  The concrete builder type
 * @author Christian Beikov
 * @author Moritz Becker
 * @since 1.0.0
 */
public abstract class AbstractFullQueryBuilder, Z, W, FinalSetReturn extends BaseFinalSetOperationBuilderImpl> extends AbstractQueryBuilder implements FullQueryBuilder {

    protected static final Set NO_CLAUSE_EXCLUSION = EnumSet.noneOf(ClauseType.class);
    protected static final Set OBJECT_QUERY_WITHOUT_GROUP_BY_EXCLUSIONS = EnumSet.of(ClauseType.GROUP_BY);
    protected static final Set COUNT_QUERY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT);
    protected static final Set COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT, ClauseType.GROUP_BY);
    protected static final Set ID_QUERY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.SELECT);
    protected static final Set ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.SELECT, ClauseType.GROUP_BY);

    protected long cachedMaximumCount;
    protected String cachedCountQueryString;
    protected String cachedExternalCountQueryString;
    protected Set cachedIdentifierExpressionsToUseNonRootJoinNodes;

    /**
     * This flag indicates whether the current builder has been used to create a
     * PaginatedCriteriaBuilder. In this case we must not allow any calls to
     * group by and distinct since the corresponding managers are shared with
     * the PaginatedCriteriaBuilder and any changes would affect the
     * PaginatedCriteriaBuilder as well.
     */
    private boolean createdPaginatedBuilder = false;
    private boolean explicitPaginatedIdentifier = false;

    private ResolvedExpression[] entityIdentifierExpressions;
    private ResolvedExpression[] uniqueIdentifierExpressions;
    private JoinNodeGathererVisitor joinNodeGathererVisitor;

    /**
     * Create flat copy of builder
     *
     * @param builder
     */
    protected AbstractFullQueryBuilder(AbstractFullQueryBuilder, ?, ?, ?> builder) {
        super(builder);
        this.entityIdentifierExpressions = builder.entityIdentifierExpressions;
    }

    public AbstractFullQueryBuilder(MainQuery mainQuery, boolean isMainQuery, Class clazz, String alias, FinalSetReturn finalSetOperationBuilder) {
        super(mainQuery, isMainQuery, clazz, alias, finalSetOperationBuilder);
    }

    @Override
    protected void prepareForModification(ClauseType changedClause) {
        super.prepareForModification(changedClause);
        cachedMaximumCount = Long.MAX_VALUE;
        cachedCountQueryString = null;
        cachedExternalCountQueryString = null;
        cachedIdentifierExpressionsToUseNonRootJoinNodes = null;
        uniqueIdentifierExpressions = null;
    }

    @Override
    AbstractCommonQueryBuilder copy(QueryContext queryContext, Map joinManagerMapping, ExpressionCopyContext copyContext) {
        throw new UnsupportedOperationException("This should only be used on CTEs!");
    }

    @Override
    public  FullQueryBuilder copy(Class resultClass) {
        prepareAndCheck();
        MainQuery mainQuery = cbf.createMainQuery(getEntityManager());
        mainQuery.copyConfiguration(this.mainQuery.getQueryConfiguration());
        CriteriaBuilderImpl newBuilder = new CriteriaBuilderImpl(mainQuery, true, resultClass, null);
        newBuilder.fromClassExplicitlySet = true;

        newBuilder.applyFrom(this, true, true, false, Collections.emptySet(), Collections.emptySet(), new IdentityHashMap(), ExpressionCopyContext.EMPTY);

        return newBuilder;
    }

    @Override
    public CriteriaBuilder createPageIdQuery(int firstResult, int maxResults, String identifierExpression) {
        return createPageIdQuery(null, firstResult, maxResults, getIdentifierExpressionsToUse(identifierExpression, null));
    }

    @Override
    public CriteriaBuilder createPageIdQuery(KeysetPage keysetPage, int firstResult, int maxResults, String identifierExpression) {
        return createPageIdQuery(keysetPage, firstResult, maxResults, getIdentifierExpressionsToUse(identifierExpression, null));
    }

    @Override
    public CriteriaBuilder createPageIdQuery(int firstResult, int maxResults, String identifierExpression, String... identifierExpressions) {
        return createPageIdQuery(null, firstResult, maxResults, getIdentifierExpressionsToUse(identifierExpression, identifierExpressions));
    }

    @Override
    public CriteriaBuilder createPageIdQuery(KeysetPage keysetPage, int firstResult, int maxResults, String identifierExpression, String... identifierExpressions) {
        return createPageIdQuery(keysetPage, firstResult, maxResults, getIdentifierExpressionsToUse(identifierExpression, identifierExpressions));
    }

    private ResolvedExpression[] getIdentifierExpressionsToUse(String identifierExpression, String[] identifierExpressions) {
        ResolvedExpression[] resolvedExpressions = getIdentifierExpressions(identifierExpression, identifierExpressions);
        ResolvedExpression[] uniqueResolvedExpressions = functionalDependencyAnalyzerVisitor.getFunctionalDependencyRootExpressions(whereManager.rootPredicate.getPredicate(), resolvedExpressions, joinManager.getRoots().get(0));
        if (uniqueResolvedExpressions != null) {
            return uniqueResolvedExpressions;
        }
        return resolvedExpressions;
    }

    protected CriteriaBuilder createPageIdQuery(KeysetPage keysetPage, int firstResult, int maxResults, ResolvedExpression[] identifierExpressionsToUse) {
        prepareAndCheck();
        MainQuery mainQuery = cbf.createMainQuery(getEntityManager());
        mainQuery.copyConfiguration(this.mainQuery.getQueryConfiguration());
        CriteriaBuilderImpl newBuilder = new CriteriaBuilderImpl<>(mainQuery, true, Object[].class, null);
        newBuilder.fromClassExplicitlySet = true;

        newBuilder.applyFrom(this, true, false, false, ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, getIdentifierExpressionsToUseNonRootJoinNodes(identifierExpressionsToUse), new IdentityHashMap(), ExpressionCopyContext.EMPTY);
        newBuilder.setFirstResult(firstResult);
        newBuilder.setMaxResults(maxResults);

        // Paginated criteria builders always need the last order by expression to be unique
        List orderByExpressions = orderByManager.getOrderByExpressions(false, whereManager.rootPredicate.getPredicate(), hasGroupBy ? Arrays.asList(getIdentifierExpressions()) : Collections.emptyList(), null);
        if (!orderByExpressions.get(orderByExpressions.size() - 1).isResultUnique()) {
            throw new IllegalStateException("The order by items of the query builder are not guaranteed to produce unique tuples! Consider also ordering by the entity identifier!");
        }

        if (keysetPage != null) {
            KeysetMode keysetMode = KeysetPaginationHelper.getKeysetMode(keysetPage, null, firstResult, maxResults);
            if (keysetMode == KeysetMode.NONE) {
                newBuilder.keysetManager.setKeysetLink(null);
            } else if (keysetMode == KeysetMode.NEXT) {
                newBuilder.keysetManager.setKeysetLink(new SimpleKeysetLink(keysetPage.getHighest(), keysetMode));
            } else {
                newBuilder.keysetManager.setKeysetLink(new SimpleKeysetLink(keysetPage.getLowest(), keysetMode));
            }
            newBuilder.keysetManager.initialize(orderByExpressions);
        }

        String[] identifierToUseSelectAliases = new String[identifierExpressionsToUse.length];
        Map identifierExpressionStringMap = new HashMap<>(identifierExpressionsToUse.length);

        for (int i = 0; i < identifierExpressionsToUse.length; i++) {
            identifierExpressionStringMap.put(identifierExpressionsToUse[i].getExpressionString(), i);
        }

        Integer index;
        for (int i = 0; i < orderByExpressions.size(); i++) {
            String potentialSelectAlias = orderByExpressions.get(i).getExpression().toString();
            AliasInfo aliasInfo = aliasManager.getAliasInfo(potentialSelectAlias);
            if (aliasInfo instanceof SelectInfo) {
                index = identifierExpressionStringMap.get(((SelectInfo) aliasInfo).getExpression().toString());
                if (index != null) {
                    identifierToUseSelectAliases[i] = potentialSelectAlias;
                }
            }
        }
        for (int i = 0; i < identifierExpressionsToUse.length; i++) {
            newBuilder.selectManager.select(identifierExpressionsToUse[i].getExpression().copy(ExpressionCopyContext.EMPTY), identifierToUseSelectAliases[i]);
        }
        newBuilder.selectManager.setDefaultSelect();

        return newBuilder;
    }

    private String getCountQueryStringWithoutCheck(long maximumCount) {
        if (cachedMaximumCount != maximumCount) {
            cachedMaximumCount = maximumCount;
            cachedCountQueryString = null;
            cachedExternalCountQueryString = null;
        }
        if (cachedCountQueryString == null) {
            cachedCountQueryString = buildPageCountQueryString(false, true, cachedMaximumCount);
        }

        return cachedCountQueryString;
    }

    private String getExternalCountQueryString(long maximumCount) {
        if (cachedMaximumCount != maximumCount) {
            cachedMaximumCount = maximumCount;
            cachedCountQueryString = null;
            cachedExternalCountQueryString = null;
        }
        if (cachedExternalCountQueryString == null) {
            cachedExternalCountQueryString = buildPageCountQueryString(true, true, cachedMaximumCount);
        }

        return cachedExternalCountQueryString;
    }

    protected String buildPageCountQueryString(boolean externalRepresentation, boolean countAll, long maximumCount) {
        StringBuilder sbSelectFrom = new StringBuilder();
        if (externalRepresentation && isMainQuery) {
            mainQuery.cteManager.buildClause(sbSelectFrom);
        }
        buildPageCountQueryString(sbSelectFrom, externalRepresentation, countAll && !hasGroupBy, maximumCount);
        return sbSelectFrom.toString();
    }

    protected final String getDualNodeAlias() {
        return "dual_";
    }

    protected final JoinNode createDualNode() {
        return JoinNode.createSimpleValuesRootNode(mainQuery, Long.class, 1, new JoinAliasInfo(getDualNodeAlias(), getDualNodeAlias(), false, true, null));
    }

    protected final void buildPageCountQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation, boolean countAll, long maximumCount) {
        sbSelectFrom.append("SELECT ");
        int countStartIdx = sbSelectFrom.length();
        int countEndIdx;
        boolean isResultUnique;

        if (maximumCount != Long.MAX_VALUE) {
            // bounded counting
            Set alwaysIncludedNodes = getIdentifierExpressionsToUseNonRootJoinNodes();
            Set clauseExclusions = countAll ? NO_CLAUSE_EXCLUSION : COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS;
            List entityFunctions = joinManager.getEntityFunctions(clauseExclusions, true, alwaysIncludedNodes);
            List entityFunctionNodes = getEntityFunctionNodes(null, entityFunctions);

            JoinNode valuesNode = createDualNode();
            if (externalRepresentation) {
                sbSelectFrom.append("COUNT(*)");
                appendPageCountQueryStringExtensions(sbSelectFrom);
                sbSelectFrom.append(" FROM ");
            } else {
                sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation(CountWrapperFunction.FUNCTION_NAME, 1));
                sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation(LimitFunction.FUNCTION_NAME, 1));

                for (int i = 0; i < entityFunctionNodes.size(); i++) {
                    sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation(EntityFunction.FUNCTION_NAME, 1));
                }
            }

            // Start of the content of the count query wrapper
            sbSelectFrom.append("(SELECT ");
            countStartIdx = sbSelectFrom.length();
            String aliasFunctionInvocation = mainQuery.jpaProvider.getCustomFunctionInvocation(AliasFunction.FUNCTION_NAME, 1);
            if (countAll) {
                sbSelectFrom.append(aliasFunctionInvocation);
                sbSelectFrom.append("1, 'c')");
                countEndIdx = sbSelectFrom.length() - 1;
                isResultUnique = true;
            } else {
                sbSelectFrom.append("DISTINCT ");
                isResultUnique = appendIdentifierExpressions(sbSelectFrom, true);
                countEndIdx = sbSelectFrom.length() - 1;
            }

            List whereClauseConjuncts = new ArrayList<>();
            List optionalWhereClauseConjuncts = new ArrayList<>();
            // The count query does not have any fetch owners
            Set countNodesToFetch = Collections.emptySet();

            if (countAll) {
                joinManager.buildClause(sbSelectFrom, clauseExclusions, null, false, externalRepresentation, false, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, countNodesToFetch, Collections.emptySet(), null);
                whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts);
            } else {
                // Collect usage of collection join nodes to optimize away the count distinct
                // Note that we always exclude the nodes with group by dependency. We consider just the ones from the identifiers
                Set identifierExpressionsToUseNonRootJoinNodes = getIdentifierExpressionsToUseNonRootJoinNodes();
                Set collectionJoinNodes = joinManager.buildClause(sbSelectFrom, clauseExclusions, null, true, externalRepresentation, true, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, countNodesToFetch, identifierExpressionsToUseNonRootJoinNodes, null);
                boolean hasCollectionJoinUsages = collectionJoinNodes.size() > 0;

                whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts);

                // Instead of a count distinct, we render a 1 if we have no collection joins and the identifier expression is result unique
                // It is result unique when it contains the query root primary key or a unique key that of a uniqueness preserving association of that
                if (!hasCollectionJoinUsages && isResultUnique) {
                    for (int i = countStartIdx, j = 0; i < countEndIdx; i++, j++) {
                        sbSelectFrom.setCharAt(i, ' ');
                    }
                    String suffix = "1, 'c'";
                    sbSelectFrom.replace(countEndIdx - (aliasFunctionInvocation.length() + suffix.length()), countEndIdx - suffix.length(), aliasFunctionInvocation);
                    sbSelectFrom.replace(countEndIdx - suffix.length(), countEndIdx, suffix);
                }
            }

            if (externalRepresentation) {
                sbSelectFrom.append(" LIMIT ").append(maximumCount).append(')');
            } else {
                if (!mainQuery.dbmsDialect.supportsLimitWithoutOrderBy()) {
                    sbSelectFrom.append(" ORDER BY ");
                    sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation(NullSubqueryFunction.FUNCTION_NAME, 0));
                    sbSelectFrom.append(')');
                }
                // Close subquery
                sbSelectFrom.append(')');

                finishEntityFunctionNodes(sbSelectFrom, entityFunctionNodes);

                // Limit
                sbSelectFrom.append(", ").append(maximumCount).append(")");
                // Count wrapper
                sbSelectFrom.append(")");

                appendPageCountQueryStringExtensions(sbSelectFrom);
                sbSelectFrom.append(" FROM ").append(valuesNode.getValueType().getName()).append(" ").append(valuesNode.getAlias());
                // Values predicate
                sbSelectFrom.append(" WHERE ");
                joinManager.renderPlaceholderRequiringPredicate(sbSelectFrom, valuesNode, valuesNode.getAlias(), externalRepresentation, true);
            }
        } else {
            if (countAll) {
                if (mainQuery.jpaProvider.supportsCountStar()) {
                    sbSelectFrom.append("COUNT(*)");
                } else if (mainQuery.jpaProvider.supportsCustomFunctions()) {
                    sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation("count_star", 0)).append(')');
                } else {
                    sbSelectFrom.append("COUNT(");
                    appendIdentifierExpressions(sbSelectFrom, false);
                    sbSelectFrom.append(")");
                }
                countEndIdx = sbSelectFrom.length() - 1;
                isResultUnique = true;
            } else if (mainQuery.jpaProvider.supportsCustomFunctions()) {
                sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation(AbstractCountFunction.FUNCTION_NAME, 1));
                sbSelectFrom.append("'DISTINCT',");

                isResultUnique = appendIdentifierExpressions(sbSelectFrom, false);

                sbSelectFrom.append(")");
                countEndIdx = sbSelectFrom.length() - 1;

                appendPageCountQueryStringExtensions(sbSelectFrom);
            } else {
                sbSelectFrom.append("COUNT(");
                sbSelectFrom.append("DISTINCT ");
                isResultUnique = appendIdentifierExpressions(sbSelectFrom, false);

                sbSelectFrom.append(")");
                countEndIdx = sbSelectFrom.length() - 1;

                appendPageCountQueryStringExtensions(sbSelectFrom);
            }

            List whereClauseConjuncts = new ArrayList<>();
            List optionalWhereClauseConjuncts = new ArrayList<>();
            // The count query does not have any fetch owners
            Set countNodesToFetch = Collections.emptySet();

            if (countAll) {
                joinManager.buildClause(sbSelectFrom, NO_CLAUSE_EXCLUSION, null, false, externalRepresentation, false, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, countNodesToFetch, Collections.emptySet(), null);
                whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts);
            } else {
                // Collect usage of collection join nodes to optimize away the count distinct
                // Note that we always exclude the nodes with group by dependency. We consider just the ones from the identifiers
                Set identifierExpressionsToUseNonRootJoinNodes = getIdentifierExpressionsToUseNonRootJoinNodes();
                Set collectionJoinNodes = joinManager.buildClause(sbSelectFrom, COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, null, true, externalRepresentation, true, false, optionalWhereClauseConjuncts, whereClauseConjuncts, explicitVersionEntities, countNodesToFetch, identifierExpressionsToUseNonRootJoinNodes, null);
                boolean hasCollectionJoinUsages = collectionJoinNodes.size() > 0;

                whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts);

                // Instead of a count distinct, we render a count(*) if we have no collection joins and the identifier expression is result unique
                // It is result unique when it contains the query root primary key or a unique key that of a uniqueness preserving association of that
                if (!hasCollectionJoinUsages && isResultUnique) {
                    if (mainQuery.jpaProvider.supportsCustomFunctions()) {
                        String countStar;
                        if (mainQuery.jpaProvider.supportsCountStar()) {
                            countStar = "COUNT(*";
                        } else {
                            countStar = mainQuery.jpaProvider.getCustomFunctionInvocation("count_star", 0);
                        }
                        for (int i = countStartIdx, j = 0; i < countEndIdx; i++, j++) {
                            if (j < countStar.length()) {
                                sbSelectFrom.setCharAt(i, countStar.charAt(j));
                            } else {
                                sbSelectFrom.setCharAt(i, ' ');
                            }
                        }
                    } else {
                        // Strip off the distinct part
                        int i = countStartIdx + "COUNT(".length();
                        countEndIdx = i + "DISTINCT ".length();
                        for (; i < countEndIdx; i++) {
                            sbSelectFrom.setCharAt(i, ' ');
                        }

                    }
                }
            }
        }
    }

    protected void finishEntityFunctionNodes(StringBuilder sbSelectFrom, List entityFunctionNodes) {
        for (EntityFunctionNode node : entityFunctionNodes) {
            String subquery = node.getSubquery();
            String aliases = node.getAliases();
            String syntheticPredicate = node.getSyntheticPredicate();

            // TODO: this is a hibernate specific integration detail
            // Replace the subview subselect that is generated for this subselect
            sbSelectFrom.append(",'");
            sbSelectFrom.append(node.getEntityName());
            sbSelectFrom.append("',");
            TypeUtils.STRING_CONVERTER.appendTo(subquery, sbSelectFrom);
            sbSelectFrom.append(",'");
            if (aliases != null) {
                sbSelectFrom.append(aliases);
            }
            sbSelectFrom.append("','");
            if (syntheticPredicate != null) {
                sbSelectFrom.append(syntheticPredicate);
            }
            sbSelectFrom.append("')");
        }
    }

    protected void appendPageCountQueryStringExtensions(StringBuilder sbSelectFrom) {
    }

    protected boolean appendIdentifierExpressions(StringBuilder sbSelectFrom, boolean alias) {
        boolean isResultUnique;
        ResolvedExpression[] identifierExpressions = getIdentifierExpressions();
        ResolvedExpression[] resultUniqueExpressions = getUniqueIdentifierExpressions();

        if (resultUniqueExpressions == null) {
            isResultUnique = false;
        } else {
            // We only render the identifiers that are necessary to make it unique
            identifierExpressions = resultUniqueExpressions;
            isResultUnique = resultUniqueExpressions == entityIdentifierExpressions || functionalDependencyAnalyzerVisitor.isResultUnique();
        }

        queryGenerator.setQueryBuffer(sbSelectFrom);
        for (int i = 0; i < identifierExpressions.length; i++) {
            if (alias) {
                sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation(AliasFunction.FUNCTION_NAME, 1));
            }
            identifierExpressions[i].getExpression().accept(queryGenerator);
            if (alias) {
                sbSelectFrom.append(", 'c").append(i).append("')");
            }
            sbSelectFrom.append(", ");
        }
        sbSelectFrom.setLength(sbSelectFrom.length() - 2);
        return isResultUnique;
    }

    protected ResolvedExpression[] getUniqueIdentifierExpressions() {
        if (uniqueIdentifierExpressions == null) {
            ResolvedExpression[] identifierExpressions = getIdentifierExpressions();
            // Fast path, if we see that the identifier expressions are the entity identifier expressions, we don't need to check uniqueness
            if (identifierExpressions == entityIdentifierExpressions) {
                uniqueIdentifierExpressions = identifierExpressions;
            } else {
                uniqueIdentifierExpressions = functionalDependencyAnalyzerVisitor.getFunctionalDependencyRootExpressions(whereManager.rootPredicate.getPredicate(), identifierExpressions, joinManager.getRoots().get(0));
            }
        }

        return uniqueIdentifierExpressions;
    }

    protected ResolvedExpression[] getIdentifierExpressionsToUse() {
        ResolvedExpression[] identifierExpressions = getIdentifierExpressions();
        ResolvedExpression[] resultUniqueExpressions = getUniqueIdentifierExpressions();

        if (resultUniqueExpressions == null) {
            return identifierExpressions;
        }
        return resultUniqueExpressions;
    }

    protected Set getIdentifierExpressionsToUseNonRootJoinNodes() {
        if (cachedIdentifierExpressionsToUseNonRootJoinNodes == null) {
            cachedIdentifierExpressionsToUseNonRootJoinNodes = getIdentifierExpressionsToUseNonRootJoinNodes(getIdentifierExpressionsToUse());
        }

        return cachedIdentifierExpressionsToUseNonRootJoinNodes;
    }

    protected Set getIdentifierExpressionsToUseNonRootJoinNodes(ResolvedExpression[] identifierExpressionsToUse) {
        if (joinNodeGathererVisitor == null) {
            joinNodeGathererVisitor = new JoinNodeGathererVisitor();
        }
        Set joinNodes = joinNodeGathererVisitor.collectNonRootJoinNodes(identifierExpressionsToUse);
        // Remove join nodes that use non-optional one-to-one associations
        Iterator iterator = joinNodes.iterator();
        OUTER: while (iterator.hasNext()) {
            JoinNode joinNode = iterator.next();
            JoinTreeNode parentTreeNode;
            while ((parentTreeNode = joinNode.getParentTreeNode()) != null) {
                if (parentTreeNode.isOptional() || parentTreeNode.getAttribute().getPersistentAttributeType() != Attribute.PersistentAttributeType.ONE_TO_ONE) {
                    continue OUTER;
                }
                joinNode = joinNode.getParent();
            }
            iterator.remove();
        }

        return joinNodes;
    }

    protected ResolvedExpression[] getIdentifierExpressions() {
        if (hasGroupBy) {
            return getGroupByIdentifierExpressions();
        }

        return getQueryRootEntityIdentifierExpressions();
    }

    @Override
    public String getCountQueryString() {
        if (!havingManager.isEmpty()) {
            throw new IllegalStateException("Cannot count a HAVING query yet!");
        }
        prepareAndCheck();
        return getExternalCountQueryString(Long.MAX_VALUE);
    }

    @Override
    public String getCountQueryString(long maximumCount) {
        if (!havingManager.isEmpty()) {
            throw new IllegalStateException("Cannot count a HAVING query yet!");
        }
        prepareAndCheck();
        return getExternalCountQueryString(maximumCount);
    }

    @Override
    public TypedQuery getCountQuery() {
        if (!havingManager.isEmpty()) {
            throw new IllegalStateException("Cannot count a HAVING query yet!");
        }
        prepareAndCheck();
        return getCountQuery(getCountQueryStringWithoutCheck(Long.MAX_VALUE));
    }

    @Override
    public TypedQuery getCountQuery(long maximumCount) {
        if (!havingManager.isEmpty()) {
            throw new IllegalStateException("Cannot count a HAVING query yet!");
        }
        prepareAndCheck();
        return getCountQuery(getCountQueryStringWithoutCheck(maximumCount));
    }

    protected TypedQuery getCountQuery(String countQueryString) {
        // We can only use the query directly if we have no ctes, entity functions or hibernate bugs
        Set keyRestrictedLeftJoins = getKeyRestrictedLeftJoins();
        Set alwaysIncludedNodes = getIdentifierExpressionsToUseNonRootJoinNodes();
        List entityFunctions = null;
        boolean normalQueryMode = !isMainQuery || (!mainQuery.cteManager.hasCtes() && (entityFunctions = joinManager.getEntityFunctions(COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, true, alwaysIncludedNodes)).isEmpty() && keyRestrictedLeftJoins.isEmpty());

        Set> parameters = parameterManager.getParameters();
        Map valuesParameters = parameterManager.getValuesParameters();
        Map valuesBinders = parameterManager.getValuesBinders();
        JoinNode dualNode = null;
        if (cachedMaximumCount == Long.MAX_VALUE) {
            if (normalQueryMode && isEmpty(keyRestrictedLeftJoins, COUNT_QUERY_CLAUSE_EXCLUSIONS)) {
                TypedQuery countQuery = em.createQuery(countQueryString, Long.class);
                if (isCacheable()) {
                    mainQuery.jpaProvider.setCacheable(countQuery);
                }
                parameterManager.parameterizeQuery(countQuery);
                return countQuery;
            }
        } else {
            dualNode = createDualNode();
            entityFunctions = new ArrayList<>();
            entityFunctions.add(dualNode);
            String valueParameterName = dualNode.getAlias() + "_value_0";
            String[][] parameterNames = new String[1][1];
            parameterNames[0][0] = valueParameterName;
            ParameterManager.ValuesParameterWrapper valuesParameterWrapper = new ParameterManager.ValuesParameterWrapper(dualNode.getJavaType(), parameterNames, new AttributeAccessor[1]);
            parameters.add(new ParameterManager.ParameterImpl(dualNode.getAlias(), false, null, null, valuesParameterWrapper));
            valuesParameters = new HashMap<>(valuesParameters);
            valuesParameters.put(valueParameterName, dualNode.getAlias());
            valuesBinders.put(dualNode.getAlias(), valuesParameterWrapper.getBinder());
        }
        if (entityFunctions == null) {
            entityFunctions = joinManager.getEntityFunctions(COUNT_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, true, alwaysIncludedNodes);
        }

        TypedQuery baseQuery = em.createQuery(countQueryString, Long.class);
        Set parameterListNames = parameterManager.getParameterListNames(baseQuery);
        List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, COUNT_QUERY_CLAUSE_EXCLUSIONS);
        List entityFunctionNodes;
        if (dualNode == null) {
            entityFunctionNodes = getEntityFunctionNodes(baseQuery, entityFunctions);
        } else {
            entityFunctionNodes = getEntityFunctionNodes(baseQuery, entityFunctions, Collections.emptyList(), false);
        }
        boolean shouldRenderCteNodes = renderCteNodes(false);
        List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST;
        QuerySpecification querySpecification = new CustomQuerySpecification(
                this, baseQuery, parameters, parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes
        );

        TypedQuery countQuery = new CustomSQLTypedQuery<>(
                querySpecification,
                baseQuery,
                parameterManager.getTransformers(),
                valuesParameters,
                valuesBinders
        );

        if (dualNode == null) {
            parameterManager.parameterizeQuery(countQuery);
        } else {
            parameterManager.parameterizeQuery(countQuery, dualNode.getAlias());
            countQuery.setParameter(dualNode.getAlias(), Collections.singleton(0L));
        }
        return countQuery;
    }

    @Override
    public PaginatedCriteriaBuilder page(int firstRow, int pageSize) {
        return pageBy(firstRow, pageSize, (ResolvedExpression[]) null);
    }

    @Override
    public PaginatedCriteriaBuilder page(Object entityId, int pageSize) {
        return pageByAndNavigate(entityId, pageSize, getQueryRootEntityIdentifierExpressions());
    }
    @Override
    public PaginatedCriteriaBuilder pageAndNavigate(Object entityId, int pageSize) {
        return pageByAndNavigate(entityId, pageSize, getQueryRootEntityIdentifierExpressions());
    }

    @Override
    public PaginatedCriteriaBuilder page(KeysetPage keysetPage, int firstRow, int pageSize) {
        return pageBy(keysetPage, firstRow, pageSize, (ResolvedExpression[]) null);
    }

    @Override
    public PaginatedCriteriaBuilder pageBy(int firstRow, int pageSize, String identifierExpression) {
        return pageBy(firstRow, pageSize, getIdentifierExpressions(identifierExpression, null));
    }

    @Override
    public PaginatedCriteriaBuilder pageByAndNavigate(Object entityId, int pageSize, String identifierExpression) {
        return pageByAndNavigate(entityId, pageSize, getIdentifierExpressions(identifierExpression, null));
    }

    @Override
    public PaginatedCriteriaBuilder pageBy(KeysetPage keysetPage, int firstRow, int pageSize, String identifierExpression) {
        return pageBy(keysetPage, firstRow, pageSize, getIdentifierExpressions(identifierExpression, null));
    }

    @Override
    public PaginatedCriteriaBuilder pageBy(int firstRow, int pageSize, String identifierExpression, String... identifierExpressions) {
        return pageBy(firstRow, pageSize, getIdentifierExpressions(identifierExpression, identifierExpressions));
    }

    @Override
    public PaginatedCriteriaBuilder pageByAndNavigate(Object entityId, int pageSize, String identifierExpression, String... identifierExpressions) {
        return pageByAndNavigate(entityId, pageSize, getIdentifierExpressions(identifierExpression, identifierExpressions));
    }

    @Override
    public PaginatedCriteriaBuilder pageBy(KeysetPage keysetPage, int firstRow, int pageSize, String identifierExpression, String... identifierExpressions) {
        return pageBy(keysetPage, firstRow, pageSize, getIdentifierExpressions(identifierExpression, identifierExpressions));
    }

    protected ResolvedExpression[] getQueryRootEntityIdentifierExpressions() {
        if (entityIdentifierExpressions == null) {
            JoinNode rootNode = joinManager.getRootNodeOrFail("Paginated criteria builders do not support multiple from clause elements!");
            Set> idAttributes = JpaMetamodelUtils.getIdAttributes(mainQuery.metamodel.entity(rootNode.getJavaType()));
            @SuppressWarnings("unchecked")
            List identifierExpressions = new ArrayList<>(idAttributes.size());
            StringBuilder sb = new StringBuilder();
            addAttributes("", idAttributes, identifierExpressions, sb, rootNode);
            entityIdentifierExpressions = identifierExpressions.toArray(new ResolvedExpression[identifierExpressions.size()]);
        }
        return entityIdentifierExpressions;
    }

    private void addAttributes(String prefix, Set> attributes, List resolvedExpressions, StringBuilder sb, JoinNode rootNode) {
        for (SingularAttribute attribute : attributes) {
            String attributeName;
            if (prefix.isEmpty()) {
                attributeName = attribute.getName();
            } else {
                attributeName = prefix + attribute.getName();
            }

            if (attribute.getType() instanceof EmbeddableType) {
                Set> subAttributes = new TreeSet<>(ATTRIBUTE_NAME_COMPARATOR);
                subAttributes.addAll(((EmbeddableType) attribute.getType()).getSingularAttributes());
                addAttributes(attributeName + ".", subAttributes, resolvedExpressions, sb, rootNode);
            } else {
                sb.setLength(0);
                rootNode.appendDeReference(sb, attributeName, false);
                PathExpression expression = (PathExpression) rootNode.createExpression(attributeName);
                JpaMetamodelAccessor jpaMetamodelAccessor = mainQuery.jpaProvider.getJpaMetamodelAccessor();
                expression.setPathReference(new SimplePathReference(rootNode, attributeName, getMetamodel().type(jpaMetamodelAccessor.getAttributePath(getMetamodel(), rootNode.getManagedType(), attributeName).getAttributeClass())));
                resolvedExpressions.add(new ResolvedExpression(sb.toString(), expression));
            }
        }
    }

    private ResolvedExpression[] getIdentifierExpressions(String identifierExpression, String[] identifierExpressions) {
        if (identifierExpression == null) {
            throw new IllegalArgumentException("Invalid null identifier expression passed to page()!");
        }

        // Note: Identifier expressions are inner joined!
        List resolvedExpressions = new ArrayList<>(identifierExpressions == null ? 1 : identifierExpression.length() + 1);
        Expression expression = expressionFactory.createSimpleExpression(identifierExpression, false);
        joinManager.implicitJoin(expression, true, true, false, null, null, JoinType.INNER, null, new HashSet(), false, false, true, false, false, false);
        StringBuilder sb = new StringBuilder();

        implicitJoinWhereClause();
        functionalDependencyAnalyzerVisitor.clear(whereManager.rootPredicate.getPredicate(), joinManager.getRoots().get(0), true);
        functionalDependencyAnalyzerVisitor.analyzeFormsUniqueTuple(expression);
        queryGenerator.setQueryBuffer(sb);
        if (functionalDependencyAnalyzerVisitor.getSplittedOffExpressions().isEmpty()) {
            sb.setLength(0);
            expression.accept(queryGenerator);
            resolvedExpressions.add(new ResolvedExpression(sb.toString(), expression));
        } else {
            for (Expression splittedOffExpression : functionalDependencyAnalyzerVisitor.getSplittedOffExpressions()) {
                sb.setLength(0);
                splittedOffExpression.accept(queryGenerator);
                resolvedExpressions.add(new ResolvedExpression(sb.toString(), splittedOffExpression));
            }
        }

        if (identifierExpressions != null) {
            for (String expressionString : identifierExpressions) {
                expression = expressionFactory.createSimpleExpression(expressionString, false);
                joinManager.implicitJoin(expression, true, true, false, null, null, JoinType.INNER, null, new HashSet(), false, false, true, false, false, false);
                functionalDependencyAnalyzerVisitor.analyzeFormsUniqueTuple(expression);
                if (functionalDependencyAnalyzerVisitor.getSplittedOffExpressions().isEmpty()) {
                    sb.setLength(0);
                    expression.accept(queryGenerator);
                    ResolvedExpression resolvedExpression = new ResolvedExpression(sb.toString(), expression);
                    if (resolvedExpressions.contains(resolvedExpression)) {
                        throw new IllegalArgumentException("Duplicate identifier expression '" + expressionString + "' in " + Arrays.toString(identifierExpressions) + "!");
                    }
                    resolvedExpressions.add(resolvedExpression);
                } else {
                    for (Expression splittedOffExpression : functionalDependencyAnalyzerVisitor.getSplittedOffExpressions()) {
                        sb.setLength(0);
                        splittedOffExpression.accept(queryGenerator);
                        ResolvedExpression resolvedExpression = new ResolvedExpression(sb.toString(), splittedOffExpression);
                        if (resolvedExpressions.contains(resolvedExpression)) {
                            throw new IllegalArgumentException("Duplicate identifier expression '" + expressionString + "' in " + Arrays.toString(identifierExpressions) + "!");
                        }
                        resolvedExpressions.add(resolvedExpression);
                    }
                }
            }
        }

        @SuppressWarnings("unchecked")
        ResolvedExpression[] entries = resolvedExpressions.toArray(new ResolvedExpression[resolvedExpressions.size()]);
        if (!functionalDependencyAnalyzerVisitor.isResultUnique()) {
            throw new IllegalArgumentException("The identifier expressions [" + expressionString(entries) + "] do not form a unique tuple which is required for pagination!");
        }

        return entries;
    }

    private PaginatedCriteriaBuilder pageBy(int firstRow, int pageSize, ResolvedExpression[] identifierExpressions) {
        prepareForModification(ClauseType.GROUP_BY);
        if (selectManager.isDistinct()) {
            throw new IllegalStateException("Cannot paginate a DISTINCT query");
        }
        if (!havingManager.isEmpty()) {
            throw new IllegalStateException("Cannot paginate a HAVING query");
        }
        createdPaginatedBuilder = true;
        explicitPaginatedIdentifier = identifierExpressions != null;
        return new PaginatedCriteriaBuilderImpl(this, false, null, firstRow, pageSize, identifierExpressions);
    }

    private PaginatedCriteriaBuilder pageByAndNavigate(Object entityId, int pageSize, ResolvedExpression[] identifierExpressions) {
        prepareForModification(ClauseType.GROUP_BY);
        if (selectManager.isDistinct()) {
            throw new IllegalStateException("Cannot paginate a DISTINCT query");
        }
        if (!havingManager.isEmpty()) {
            throw new IllegalStateException("Cannot paginate a HAVING query");
        }
        checkEntityId(entityId, identifierExpressions);
        createdPaginatedBuilder = true;
        explicitPaginatedIdentifier = identifierExpressions != null;
        return new PaginatedCriteriaBuilderImpl(this, false, entityId, pageSize, identifierExpressions);
    }

    private PaginatedCriteriaBuilder pageBy(KeysetPage keysetPage, int firstRow, int pageSize, ResolvedExpression[] identifierExpressions) {
        prepareForModification(ClauseType.GROUP_BY);
        if (selectManager.isDistinct()) {
            throw new IllegalStateException("Cannot paginate a DISTINCT query");
        }
        if (!havingManager.isEmpty()) {
            throw new IllegalStateException("Cannot paginate a HAVING query");
        }
        createdPaginatedBuilder = true;
        explicitPaginatedIdentifier = identifierExpressions != null;
        return new PaginatedCriteriaBuilderImpl(this, true, keysetPage, firstRow, pageSize, identifierExpressions);
    }

    protected static String expressionString(ResolvedExpression[] identifierExpressions) {
        StringBuilder sb = new StringBuilder();
        for (ResolvedExpression identifierExpression : identifierExpressions) {
            sb.append(identifierExpression.getExpressionString()).append(", ");
        }
        sb.setLength(sb.length() - 2);
        return sb.toString();
    }

    private void checkEntityId(Object entityId, ResolvedExpression[] identifierExpressions) {
        if (entityId == null) {
            throw new IllegalArgumentException("Invalid null entity id given");
        }
        if (identifierExpressions.length == 0) {
            throw new IllegalArgumentException("Empty identifier expressions given");
        }
        if (identifierExpressions.length > 1) {
            if (!entityId.getClass().isArray()) {
                throw new IllegalArgumentException("The type of the given entity id '" + entityId.getClass().getName()
                        + "' is not an array of the identifier components " + Arrays.toString(identifierExpressions) + " !");
            }

            Object[] entityIdComponents = (Object[]) entityId;
            if (entityIdComponents.length != identifierExpressions.length) {
                throw new IllegalArgumentException("The number of entity id components is '" + entityIdComponents.length
                        + "' which does not match the number of identifier component expressions " + identifierExpressions.length + " !");
            }

            for (int i = 0; i < identifierExpressions.length; i++) {
                checkEntityIdComponent(entityIdComponents[i], identifierExpressions[i].getExpression(), identifierExpressions[i].getExpressionString());
            }
        } else {
            checkEntityIdComponent(entityId, identifierExpressions[0].getExpression(), "identifier");
        }
    }

    private void checkEntityIdComponent(Object component, Expression expression, String componentName) {
        AttributeHolder attribute = JpaUtils.getAttributeForJoining(getMetamodel(), expression);
        Class type = attribute.getAttributeType().getJavaType();

        if (type == null || !type.isInstance(component)) {
            throw new IllegalArgumentException("The type of the given " + componentName + " '" + component.getClass().getName()
                    + "' is not an instance of the expected type '" + JpaMetamodelUtils.getTypeName(attribute.getAttributeType()) + "'");
        }
    }

    @Override
    public  SelectObjectBuilder> selectNew(Class clazz) {
        prepareForModification(ClauseType.SELECT);
        if (clazz == null) {
            throw new NullPointerException("clazz");
        }

        verifyBuilderEnded();
        return selectManager.selectNew(this, clazz);
    }

    @Override
    public  SelectObjectBuilder> selectNew(Constructor constructor) {
        prepareForModification(ClauseType.SELECT);
        if (constructor == null) {
            throw new NullPointerException("constructor");
        }

        verifyBuilderEnded();
        return selectManager.selectNew(this, constructor);
    }

    @Override
    @SuppressWarnings("unchecked")
    public  FullQueryBuilder selectNew(ObjectBuilder objectBuilder) {
        prepareForModification(ClauseType.SELECT);
        if (objectBuilder == null) {
            throw new NullPointerException("objectBuilder");
        }

        verifyBuilderEnded();
        selectManager.selectNew((X) this, objectBuilder);
        return (FullQueryBuilder) this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public X fetch(String path) {
        prepareForModification(ClauseType.JOIN);
        verifyBuilderEnded();
        joinManager.implicitJoin(expressionFactory.createPathExpression(path), true, true, true, null, null, null, null, new HashSet(), false, false, true, false, true, false);
        return (X) this;
    }

    @Override
    @SuppressWarnings("unchecked")
    public X fetch(String... paths) {
        prepareForModification(ClauseType.JOIN);
        verifyBuilderEnded();

        HashSet currentlyResolvingAliases = new HashSet<>();
        for (String path : paths) {
            joinManager.implicitJoin(expressionFactory.createPathExpression(path), true, true, true, null, null, null, null, currentlyResolvingAliases, false, false, true, false, true, false);
        }

        return (X) this;
    }

    @Override
    public X innerJoinFetch(String path, String alias) {
        return join(path, alias, JoinType.INNER, true);
    }

    @Override
    public X innerJoinFetchDefault(String path, String alias) {
        return joinDefault(path, alias, JoinType.INNER, true);
    }

    @Override
    public X leftJoinFetch(String path, String alias) {
        return join(path, alias, JoinType.LEFT, true);
    }

    @Override
    public X leftJoinFetchDefault(String path, String alias) {
        return joinDefault(path, alias, JoinType.LEFT, true);
    }

    @Override
    public X rightJoinFetch(String path, String alias) {
        return join(path, alias, JoinType.RIGHT, true);
    }

    @Override
    public X rightJoinFetchDefault(String path, String alias) {
        return joinDefault(path, alias, JoinType.RIGHT, true);
    }

    @Override
    public X join(String path, String alias, JoinType type, boolean fetch) {
        return join(path, alias, type, fetch, false);
    }

    @Override
    public X joinDefault(String path, String alias, JoinType type, boolean fetch) {
        return join(path, alias, type, fetch, true);
    }

    @SuppressWarnings("unchecked")
    private X join(String path, String alias, JoinType type, boolean fetch, boolean defaultJoin) {
        prepareForModification(ClauseType.JOIN);
        if (path == null) {
            throw new NullPointerException("path");
        }
        if (alias == null) {
            throw new NullPointerException("alias");
        }
        if (type == null) {
            throw new NullPointerException("type");
        }
        if (alias.isEmpty()) {
            throw new IllegalArgumentException("Empty alias");
        }

        verifyBuilderEnded();
        joinManager.join(path, alias, type, fetch, defaultJoin, null);
        return (X) this;
    }

    @Override
    public X distinct() {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling distinct() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.distinct();
    }

    @Override
    public RestrictionBuilder having(String expression) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.having(expression);
    }

    @Override
    public CaseWhenStarterBuilder> havingCase() {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingCase();
    }

    @Override
    public SimpleCaseWhenStarterBuilder> havingSimpleCase(String expression) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingSimpleCase(expression);
    }

    @Override
    public HavingOrBuilder havingOr() {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingOr();
    }

    @Override
    public SubqueryInitiator havingExists() {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingExists();
    }

    @Override
    public SubqueryInitiator havingNotExists() {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingNotExists();
    }

    @Override
    public SubqueryBuilder havingExists(FullQueryBuilder criteriaBuilder) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingExists(criteriaBuilder);
    }

    @Override
    public SubqueryBuilder havingNotExists(FullQueryBuilder criteriaBuilder) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingNotExists(criteriaBuilder);
    }

    @Override
    public SubqueryInitiator> havingSubquery() {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingSubquery();
    }

    @Override
    public SubqueryInitiator> havingSubquery(String subqueryAlias, String expression) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingSubquery(subqueryAlias, expression);
    }

    @Override
    public MultipleSubqueryInitiator> havingSubqueries(String expression) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingSubqueries(expression);
    }

    @Override
    public SubqueryBuilder> havingSubquery(FullQueryBuilder criteriaBuilder) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingSubquery(criteriaBuilder);
    }

    @Override
    public SubqueryBuilder> havingSubquery(String subqueryAlias, String expression, FullQueryBuilder criteriaBuilder) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.havingSubquery(subqueryAlias, expression, criteriaBuilder);
    }

    @Override
    public X setHavingExpression(String expression) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.setHavingExpression(expression);
    }

    @Override
    public MultipleSubqueryInitiator setHavingExpressionSubqueries(String expression) {
        if (createdPaginatedBuilder) {
            throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
        }
        return super.setHavingExpressionSubqueries(expression);
    }

    @Override
    public X groupBy(String... paths) {
        if (explicitPaginatedIdentifier) {
            throw new IllegalStateException("Cannot add a GROUP BY clause when paginating by the expressions [" + expressionString(getIdentifierExpressions()) + "]");
        }
        return super.groupBy(paths);
    }

    @Override
    public X groupBy(String expression) {
        if (explicitPaginatedIdentifier) {
            throw new IllegalStateException("Cannot add a GROUP BY clause when paginating by the expressions [" + expressionString(getIdentifierExpressions()) + "]");
        }
        return super.groupBy(expression);
    }
}