Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.blazebit.persistence.impl.PaginatedCriteriaBuilderImpl Maven / Gradle / Ivy
/*
* Copyright 2014 - 2019 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.FullQueryBuilder;
import com.blazebit.persistence.HavingOrBuilder;
import com.blazebit.persistence.KeysetPage;
import com.blazebit.persistence.MultipleSubqueryInitiator;
import com.blazebit.persistence.ObjectBuilder;
import com.blazebit.persistence.PagedList;
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.builder.object.DelegatingKeysetExtractionObjectBuilder;
import com.blazebit.persistence.impl.builder.object.KeysetExtractionObjectBuilder;
import com.blazebit.persistence.impl.function.pageposition.PagePositionFunction;
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.ObjectBuilderTypedQuery;
import com.blazebit.persistence.impl.query.QuerySpecification;
import javax.persistence.Parameter;
import javax.persistence.TypedQuery;
import java.util.AbstractMap;
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.List;
import java.util.Map;
import java.util.Set;
/**
*
* @author Christian Beikov
* @author Moritz Becker
* @since 1.0.0
*/
public class PaginatedCriteriaBuilderImpl extends AbstractFullQueryBuilder, PaginatedCriteriaBuilderImpl, PaginatedCriteriaBuilderImpl, BaseFinalSetOperationBuilderImpl> implements PaginatedCriteriaBuilder {
private static final String ENTITY_PAGE_POSITION_PARAMETER_NAME = "_entityPagePositionParameter";
private static final String PAGE_POSITION_ID_QUERY_ALIAS_PREFIX = "_page_position_";
private static final Set ID_QUERY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.SELECT);
private static final Set ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS = EnumSet.of(ClauseType.SELECT, ClauseType.GROUP_BY);
private static final Set OBJECT_QUERY_CLAUSE_EXCLUSIONS = EnumSet.complementOf(EnumSet.of(ClauseType.ORDER_BY, ClauseType.SELECT));
private static final ResolvedExpression[] EMPTY = new ResolvedExpression[0];
private boolean keysetExtraction;
private boolean withExtractAllKeysets = false;
private boolean withCountQuery = true;
private boolean withForceIdQuery = false;
private int highestOffset = 0;
private final KeysetPage keysetPage;
private final ResolvedExpression[] identifierExpressions;
// Mutable state
private final Object entityId;
private boolean needsNewIdList;
private int[] keysetToSelectIndexMapping;
private String[] identifierToUseSelectAliases;
private KeysetMode keysetMode;
// Cache
private String cachedIdQueryString;
private String cachedExternalIdQueryString;
public PaginatedCriteriaBuilderImpl(AbstractFullQueryBuilder, ?, ?, ?> baseBuilder, boolean keysetExtraction, Object entityId, int pageSize, ResolvedExpression[] identifierExpressions) {
super(baseBuilder);
if (pageSize <= 0) {
throw new IllegalArgumentException("pageSize may not be zero or negative");
}
this.keysetExtraction = keysetExtraction;
this.keysetPage = null;
this.entityId = entityId;
this.maxResults = pageSize;
this.identifierExpressions = identifierExpressions;
updateKeysetMode();
}
public PaginatedCriteriaBuilderImpl(AbstractFullQueryBuilder, ?, ?, ?> baseBuilder, boolean keysetExtraction, KeysetPage keysetPage, int firstRow, int pageSize, ResolvedExpression[] identifierExpressions) {
super(baseBuilder);
if (firstRow < 0) {
throw new IllegalArgumentException("firstRow may not be negative");
}
if (pageSize <= 0) {
throw new IllegalArgumentException("pageSize may not be zero or negative");
}
this.keysetExtraction = keysetExtraction;
this.keysetPage = keysetPage;
this.firstResult = firstRow;
this.entityId = null;
this.maxResults = pageSize;
this.identifierExpressions = identifierExpressions;
updateKeysetMode();
}
@Override
public PaginatedCriteriaBuilder copy(Class resultClass) {
FullQueryBuilder criteriaBuilder = super.copy(resultClass);
PaginatedCriteriaBuilder builder;
if (entityId != null) {
builder = criteriaBuilder.pageAndNavigate(entityId, maxResults);
} else if (keysetPage != null) {
builder = criteriaBuilder.page(keysetPage, firstResult, maxResults);
} else {
builder = criteriaBuilder.page(firstResult, maxResults);
}
builder.withKeysetExtraction(keysetExtraction);
builder.withExtractAllKeysets(withExtractAllKeysets);
builder.withCountQuery(withCountQuery);
builder.withForceIdQuery(withForceIdQuery);
builder.withHighestKeysetOffset(highestOffset);
return builder;
}
@Override
public PaginatedCriteriaBuilder setFirstResult(int firstResult) {
super.setFirstResult(firstResult);
updateKeysetMode();
return this;
}
@Override
public PaginatedCriteriaBuilder setMaxResults(int maxResults) {
super.setMaxResults(maxResults);
updateKeysetMode();
return this;
}
private void updateKeysetMode() {
KeysetMode oldMode = this.keysetMode;
this.keysetMode = KeysetPaginationHelper.getKeysetMode(keysetPage, entityId, firstResult, maxResults);
if (keysetMode == KeysetMode.NONE) {
this.keysetManager.setKeysetLink(null);
} else if (keysetMode == KeysetMode.NEXT) {
this.keysetManager.setKeysetLink(new SimpleKeysetLink(keysetPage.getHighest(), keysetMode));
} else {
this.keysetManager.setKeysetLink(new SimpleKeysetLink(keysetPage.getLowest(), keysetMode));
}
if (keysetMode != oldMode) {
prepareForModification(ClauseType.WHERE);
}
}
@Override
public PaginatedCriteriaBuilder withKeysetExtraction(boolean keysetExtraction) {
this.keysetExtraction = keysetExtraction;
if (!keysetExtraction) {
this.withExtractAllKeysets = false;
}
return this;
}
@Override
public boolean isKeysetExtraction() {
return keysetExtraction;
}
@Override
public PaginatedCriteriaBuilder withExtractAllKeysets(boolean withExtractAllKeysets) {
this.withExtractAllKeysets = withExtractAllKeysets;
if (withExtractAllKeysets) {
this.keysetExtraction = true;
}
return this;
}
@Override
public boolean isWithExtractAllKeysets() {
return withExtractAllKeysets;
}
@Override
public PaginatedCriteriaBuilder withCountQuery(boolean withCountQuery) {
this.withCountQuery = withCountQuery;
return this;
}
@Override
public boolean isWithCountQuery() {
return withCountQuery;
}
@Override
public PaginatedCriteriaBuilder withForceIdQuery(boolean withForceIdQuery) {
this.withForceIdQuery = withForceIdQuery;
return this;
}
@Override
public boolean isWithForceIdQuery() {
return withForceIdQuery;
}
@Override
public PaginatedCriteriaBuilder withHighestKeysetOffset(int offset) {
this.highestOffset = offset;
return this;
}
@Override
public int getHighestKeysetOffset() {
return highestOffset;
}
@Override
protected ResolvedExpression[] getIdentifierExpressions() {
if (identifierExpressions != null) {
return identifierExpressions;
} else if (hasGroupBy) {
return getGroupByIdentifierExpressions();
} else {
return getQueryRootEntityIdentifierExpressions();
}
}
private TypedQuery getCountQuery(String countQueryString, Class resultType, boolean normalQueryMode, Set keyRestrictedLeftJoins) {
if (normalQueryMode && isEmpty(keyRestrictedLeftJoins, COUNT_QUERY_CLAUSE_EXCLUSIONS)) {
TypedQuery countQuery = em.createQuery(countQueryString, resultType);
if (isCacheable()) {
mainQuery.jpaProvider.setCacheable(countQuery);
}
parameterManager.parameterizeQuery(countQuery);
return countQuery;
}
TypedQuery baseQuery = em.createQuery(countQueryString, resultType);
Set parameterListNames = parameterManager.getParameterListNames(baseQuery);
List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, COUNT_QUERY_CLAUSE_EXCLUSIONS);
List entityFunctionNodes = getEntityFunctionNodes(baseQuery);
boolean shouldRenderCteNodes = renderCteNodes(false);
List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST;
QuerySpecification querySpecification = new CustomQuerySpecification(
this, baseQuery, parameterManager.getParameters(), parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes
);
TypedQuery countQuery = new CustomSQLTypedQuery(
querySpecification,
baseQuery,
parameterManager.getTransformers(),
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
parameterManager.parameterizeQuery(countQuery);
return countQuery;
}
@Override
public PaginatedTypedQueryImpl getQuery() {
prepareAndCheck();
// We can only use the query directly if we have no ctes, entity functions or hibernate bugs
Set keyRestrictedLeftJoins = joinManager.getKeyRestrictedLeftJoins();
boolean normalQueryMode = !isMainQuery || (!mainQuery.cteManager.hasCtes() && !joinManager.hasEntityFunctions() && keyRestrictedLeftJoins.isEmpty());
TypedQuery countQuery = null;
String countQueryString = getPageCountQueryStringWithoutCheck();
if (entityId == null) {
// No reference entity id, so just do a simple count query
countQuery = getCountQuery(countQueryString, Long.class, normalQueryMode, keyRestrictedLeftJoins);
} else {
countQuery = getCountQuery(countQueryString, Object[].class, normalQueryMode, keyRestrictedLeftJoins);
}
TypedQuery idQuery = null;
TypedQuery objectQuery;
KeysetExtractionObjectBuilder objectBuilder;
if (hasCollections || withForceIdQuery) {
String idQueryString = getPageIdQueryStringWithoutCheck();
idQuery = getIdQuery(idQueryString, normalQueryMode, keyRestrictedLeftJoins);
objectQuery = getObjectQueryById(normalQueryMode, keyRestrictedLeftJoins);
objectBuilder = null;
} else {
Map.Entry, KeysetExtractionObjectBuilder> entry = getObjectQuery(normalQueryMode, keyRestrictedLeftJoins);
objectQuery = entry.getKey();
objectBuilder = entry.getValue();
}
PaginatedTypedQueryImpl query = new PaginatedTypedQueryImpl<>(
withExtractAllKeysets, withCountQuery,
highestOffset,
countQuery,
idQuery,
objectQuery,
objectBuilder,
parameterManager.getParameters(),
entityId,
firstResult,
maxResults,
getIdentifierExpressionsToUse().length,
needsNewIdList,
keysetToSelectIndexMapping,
keysetMode,
keysetPage
);
return query;
}
@Override
public PagedList getResultList() {
return getQuery().getResultList();
}
@Override
public String getCountQueryString() {
return getPageCountQueryString();
}
@Override
public TypedQuery getCountQuery() {
prepareAndCheck();
// We can only use the query directly if we have no ctes, entity functions or hibernate bugs
Set keyRestrictedLeftJoins = joinManager.getKeyRestrictedLeftJoins();
boolean normalQueryMode = !isMainQuery || (!mainQuery.cteManager.hasCtes() && !joinManager.hasEntityFunctions() && keyRestrictedLeftJoins.isEmpty());
String countQueryString = getPageCountQueryStringWithoutCheck();
return getCountQuery(countQueryString, Long.class, normalQueryMode, keyRestrictedLeftJoins);
}
@Override
public String getPageCountQueryString() {
prepareAndCheck();
return getExternalPageCountQueryString();
}
private String getPageCountQueryStringWithoutCheck() {
if (cachedCountQueryString == null) {
cachedCountQueryString = buildPageCountQueryString(false, false);
}
return cachedCountQueryString;
}
protected String getExternalPageCountQueryString() {
if (cachedExternalCountQueryString == null) {
cachedExternalCountQueryString = buildPageCountQueryString(true, false);
}
return cachedExternalCountQueryString;
}
@Override
public String getPageIdQueryString() {
prepareAndCheck();
return getExternalPageIdQueryString();
}
private String getPageIdQueryStringWithoutCheck() {
if (cachedIdQueryString == null && (hasCollections || withForceIdQuery)) {
cachedIdQueryString = buildPageIdQueryString(false);
}
return cachedIdQueryString;
}
protected String getExternalPageIdQueryString() {
if (cachedExternalIdQueryString == null && (hasCollections || withForceIdQuery)) {
cachedExternalIdQueryString = buildPageIdQueryString(true);
}
return cachedExternalIdQueryString;
}
@Override
public String getQueryString() {
prepareAndCheck();
return getExternalQueryString();
}
@Override
protected String getBaseQueryString() {
if (cachedQueryString == null) {
if ((hasCollections || withForceIdQuery)) {
cachedQueryString = buildBaseQueryString(false);
} else {
cachedQueryString = buildObjectQueryString(false);
}
}
return cachedQueryString;
}
protected String getExternalQueryString() {
if (cachedExternalQueryString == null) {
if ((hasCollections || withForceIdQuery)) {
cachedExternalQueryString = buildBaseQueryString(true);
} else {
cachedExternalQueryString = buildObjectQueryString(true);
}
}
return cachedExternalQueryString;
}
@Override
protected void prepareForModification(ClauseType changedClause) {
super.prepareForModification(changedClause);
cachedIdQueryString = null;
cachedExternalIdQueryString = null;
}
@Override
protected void prepareAndCheck() {
if (!needsCheck) {
return;
}
verifyBuilderEnded();
if (!orderByManager.hasOrderBys()) {
throw new IllegalStateException("Pagination requires at least one order by item!");
}
JoinVisitor joinVisitor = applyImplicitJoins(null);
// We pass true here to always generate implicit group bys for select and order by clauses. We filter these out later if necessary
applyExpressionTransformersAndBuildGroupByClauses(true, joinVisitor);
hasCollections = joinManager.hasCollections();
if (hasGroupBy) {
if (identifierExpressions != null) {
ResolvedExpression[] missingExpressions;
if ((missingExpressions = findMissingExpressions(getIdentifierExpressions(), identifierExpressions)) != null) {
throw new IllegalStateException("Cannot paginate by expressions [" + expressionString(identifierExpressions) + "] because the expression [" + expressionString(missingExpressions) + "] is not part of the group by clause!");
}
}
if (hasCollections) {
// We register a GROUP_BY clause dependency for joins nodes of implicit grouped by expressions
// If none of the collection join nodes appears in the group by, this means they are all aggregated somehow and thus grouped away
boolean groupedAway = true;
for (JoinNode joinNode : joinManager.getCollectionJoins()) {
if (joinNode.getClauseDependencies().contains(ClauseType.GROUP_BY)) {
groupedAway = false;
break;
}
}
if (groupedAway) {
hasCollections = false;
}
}
}
// 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(), joinVisitor);
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 (keysetManager.hasKeyset()) {
keysetManager.initialize(orderByExpressions);
}
// initialize index mappings that we use to avoid putting keyset expressions into select clauses multiple times
if (hasCollections || withForceIdQuery) {
ResolvedExpression[] identifierExpressionsToUse = getIdentifierExpressionsToUse();
Map identifierExpressionStringMap = new HashMap<>(identifierExpressionsToUse.length);
for (int i = 0; i < identifierExpressionsToUse.length; i++) {
identifierExpressionStringMap.put(identifierExpressionsToUse[i].getExpressionString(), i);
}
keysetToSelectIndexMapping = new int[orderByExpressions.size()];
identifierToUseSelectAliases = new String[identifierExpressionsToUse.length];
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) {
keysetToSelectIndexMapping[i] = -1;
} else {
identifierToUseSelectAliases[i] = potentialSelectAlias;
keysetToSelectIndexMapping[i] = index;
}
} else if (keysetExtraction) {
index = identifierExpressionStringMap.get(potentialSelectAlias);
keysetToSelectIndexMapping[i] = index == null ? -1 : index;
}
}
if (!keysetExtraction) {
keysetToSelectIndexMapping = null;
}
} else if (keysetExtraction) {
List selectInfos = selectManager.getSelectInfos();
Map selectExpressionStringMap = new HashMap<>(selectInfos.size() * 2);
for (int i = 0; i < selectInfos.size(); i++) {
SelectInfo selectInfo = selectInfos.get(i);
selectExpressionStringMap.put(selectInfo.getExpression().toString(), i);
if (selectInfo.getAlias() != null) {
selectExpressionStringMap.put(selectInfo.getAlias(), i);
}
}
keysetToSelectIndexMapping = new int[orderByExpressions.size()];
identifierToUseSelectAliases = null;
Integer index;
for (int i = 0; i < orderByExpressions.size(); i++) {
index = selectExpressionStringMap.get(orderByExpressions.get(i).getExpression().toString());
keysetToSelectIndexMapping[i] = index == null ? -1 : index;
}
} else {
keysetToSelectIndexMapping = null;
identifierToUseSelectAliases = null;
}
// When we do keyset extraction of have complex order bys, we have to append additional expression to the end of the select clause which have to be removed later
needsNewIdList = keysetExtraction
|| orderByManager.hasComplexOrderBys();
// No need to do the check again if no mutation occurs
needsCheck = false;
}
private ResolvedExpression[] findMissingExpressions(ResolvedExpression[] targetIdentifierExpressions, ResolvedExpression[] identifierExpressions) {
if (targetIdentifierExpressions == null || targetIdentifierExpressions.length < identifierExpressions.length) {
return identifierExpressions;
}
int identifiersSize = identifierExpressions.length;
int targetIdentifiersSize = targetIdentifierExpressions.length;
List missingExpressions = null;
OUTER: for (int i = 0; i < identifiersSize; i++) {
ResolvedExpression identifierExpression = identifierExpressions[i];
for (int j = 0; j < targetIdentifiersSize; j++) {
if (identifierExpression.equals(targetIdentifierExpressions[j])) {
continue OUTER;
}
}
if (missingExpressions == null) {
missingExpressions = new ArrayList<>();
}
missingExpressions.add(identifierExpression);
}
if (missingExpressions == null) {
return null;
}
return missingExpressions.toArray(new ResolvedExpression[missingExpressions.size()]);
}
@SuppressWarnings("unchecked")
private Map.Entry, KeysetExtractionObjectBuilder> getObjectQuery(boolean normalQueryMode, Set keyRestrictedLeftJoins) {
String queryString = getBaseQueryString();
Class expectedResultType;
// When the keyset is included the query obviously produces an array
if (keysetExtraction) {
expectedResultType = Object[].class;
} else {
expectedResultType = selectManager.getExpectedQueryResultType();
}
TypedQuery query;
if (normalQueryMode && isEmpty(keyRestrictedLeftJoins, hasGroupBy ? NO_CLAUSE_EXCLUSION : OBJECT_QUERY_WITHOUT_GROUP_BY_EXCLUSIONS)) {
query = (TypedQuery) em.createQuery(queryString, expectedResultType);
if (isCacheable()) {
mainQuery.jpaProvider.setCacheable(query);
}
parameterManager.parameterizeQuery(query);
} else {
TypedQuery baseQuery = (TypedQuery) em.createQuery(queryString, expectedResultType);
Set parameterListNames = parameterManager.getParameterListNames(baseQuery);
List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, hasGroupBy ? NO_CLAUSE_EXCLUSION : OBJECT_QUERY_WITHOUT_GROUP_BY_EXCLUSIONS);
List entityFunctionNodes = getEntityFunctionNodes(baseQuery);
boolean shouldRenderCteNodes = renderCteNodes(false);
List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST;
QuerySpecification querySpecification = new CustomQuerySpecification(
this, baseQuery, parameterManager.getParameters(), parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes
);
query = new CustomSQLTypedQuery(
querySpecification,
baseQuery,
parameterManager.getTransformers(),
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
parameterManager.parameterizeQuery(query);
}
KeysetExtractionObjectBuilder objectBuilder = null;
ObjectBuilder transformerObjectBuilder = selectManager.getSelectObjectBuilder();
if (keysetExtraction) {
if (transformerObjectBuilder == null) {
objectBuilder = new KeysetExtractionObjectBuilder(keysetToSelectIndexMapping, keysetMode, selectManager.getExpectedQueryResultType() != Object[].class, withExtractAllKeysets);
} else {
objectBuilder = new DelegatingKeysetExtractionObjectBuilder(transformerObjectBuilder, keysetToSelectIndexMapping, keysetMode, withExtractAllKeysets);
}
transformerObjectBuilder = objectBuilder;
}
if (transformerObjectBuilder != null) {
query = new ObjectBuilderTypedQuery<>(query, transformerObjectBuilder);
}
return new AbstractMap.SimpleEntry, KeysetExtractionObjectBuilder>(query, objectBuilder);
}
private TypedQuery getIdQuery(String idQueryString, boolean normalQueryMode, Set keyRestrictedLeftJoins) {
if (normalQueryMode && isEmpty(keyRestrictedLeftJoins, ID_QUERY_CLAUSE_EXCLUSIONS)) {
TypedQuery idQuery = em.createQuery(idQueryString, Object[].class);
if (isCacheable()) {
mainQuery.jpaProvider.setCacheable(idQuery);
}
parameterManager.parameterizeQuery(idQuery);
return idQuery;
}
TypedQuery baseQuery = em.createQuery(idQueryString, Object[].class);
Set parameterListNames = parameterManager.getParameterListNames(baseQuery);
List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, ID_QUERY_CLAUSE_EXCLUSIONS);
List entityFunctionNodes = getEntityFunctionNodes(baseQuery);
boolean shouldRenderCteNodes = renderCteNodes(false);
List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST;
QuerySpecification querySpecification = new CustomQuerySpecification(
this, baseQuery, parameterManager.getParameters(), parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes
);
TypedQuery idQuery = new CustomSQLTypedQuery(
querySpecification,
baseQuery,
parameterManager.getTransformers(),
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
parameterManager.parameterizeQuery(idQuery);
return idQuery;
}
@SuppressWarnings("unchecked")
private TypedQuery getObjectQueryById(boolean normalQueryMode, Set keyRestrictedLeftJoins) {
ResolvedExpression[] identifierExpressionsToUse = getIdentifierExpressionsToUse();
String skippedParameterPrefix = identifierExpressionsToUse.length == 1 ? ID_PARAM_NAME : ID_PARAM_NAME + "_";
if (normalQueryMode && isEmpty(keyRestrictedLeftJoins, OBJECT_QUERY_CLAUSE_EXCLUSIONS)) {
TypedQuery query = (TypedQuery) em.createQuery(getBaseQueryString(), selectManager.getExpectedQueryResultType());
if (isCacheable()) {
mainQuery.jpaProvider.setCacheable(query);
}
parameterManager.parameterizeQuery(query, skippedParameterPrefix);
return applyObjectBuilder(query);
}
TypedQuery baseQuery = (TypedQuery) em.createQuery(getBaseQueryString(), selectManager.getExpectedQueryResultType());
Set parameterListNames = parameterManager.getParameterListNames(baseQuery, ID_PARAM_NAME);
if (identifierExpressionsToUse.length == 1) {
parameterListNames.add(ID_PARAM_NAME);
}
List keyRestrictedLeftJoinAliases = getKeyRestrictedLeftJoinAliases(baseQuery, keyRestrictedLeftJoins, OBJECT_QUERY_CLAUSE_EXCLUSIONS);
List entityFunctionNodes = getEntityFunctionNodes(baseQuery);
boolean shouldRenderCteNodes = renderCteNodes(false);
List ctes = shouldRenderCteNodes ? getCteNodes(false) : Collections.EMPTY_LIST;
Set> parameters = new HashSet<>(parameterManager.getParameters());
if (identifierExpressionsToUse.length == 1) {
parameters.add(baseQuery.getParameter(ID_PARAM_NAME));
}
QuerySpecification querySpecification = new CustomQuerySpecification(
this, baseQuery, parameters, parameterListNames, null, null, keyRestrictedLeftJoinAliases, entityFunctionNodes, mainQuery.cteManager.isRecursive(), ctes, shouldRenderCteNodes
);
TypedQuery query = new CustomSQLTypedQuery(
querySpecification,
baseQuery,
parameterManager.getTransformers(),
parameterManager.getValuesParameters(),
parameterManager.getValuesBinders()
);
parameterManager.parameterizeQuery(query, skippedParameterPrefix);
return applyObjectBuilder(query);
}
@Override
protected void appendPageCountQueryStringExtensions(StringBuilder sbSelectFrom) {
if (entityId != null) {
parameterManager.addParameterMapping(ENTITY_PAGE_POSITION_PARAMETER_NAME, entityId, ClauseType.SELECT, this);
sbSelectFrom.append(", ");
sbSelectFrom.append(mainQuery.jpaProvider.getCustomFunctionInvocation(PagePositionFunction.FUNCTION_NAME, 2));
sbSelectFrom.append('(');
appendSimplePageIdQueryString(sbSelectFrom);
sbSelectFrom.append("),");
sbSelectFrom.append(':').append(ENTITY_PAGE_POSITION_PARAMETER_NAME);
sbSelectFrom.append(")");
}
}
private String appendSimplePageIdQueryString(StringBuilder sbSelectFrom) {
queryGenerator.setAliasPrefix(PAGE_POSITION_ID_QUERY_ALIAS_PREFIX);
sbSelectFrom.append("SELECT ");
appendIdentifierExpressions(sbSelectFrom);
// TODO: actually we should add the select clauses needed for order bys
// TODO: if we do so, the page position function has to omit select items other than the first
List whereClauseConjuncts = new ArrayList<>();
List optionalWhereClauseConjuncts = new ArrayList<>();
// The id query does not have any fetch owners
// Note that we always exclude the nodes with group by dependency. We consider just the ones from the identifiers
Set idNodesToFetch = Collections.emptySet();
Set identifierExpressionsToUseNonRootJoinNodes = getIdentifierExpressionsToUseNonRootJoinNodes();
joinManager.buildClause(sbSelectFrom, ID_QUERY_CLAUSE_EXCLUSIONS, PAGE_POSITION_ID_QUERY_ALIAS_PREFIX, false, false, true, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, idNodesToFetch, identifierExpressionsToUseNonRootJoinNodes);
whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null);
boolean inverseOrder = false;
groupByManager.buildGroupBy(sbSelectFrom, ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, getIdentifierExpressionsToUse());
havingManager.buildClause(sbSelectFrom);
// Resolve select aliases because we might omit the select items
orderByManager.buildOrderBy(sbSelectFrom, inverseOrder, true, false);
queryGenerator.setAliasPrefix(null);
return sbSelectFrom.toString();
}
private String buildPageIdQueryString(boolean externalRepresentation) {
StringBuilder sbSelectFrom = new StringBuilder();
if (externalRepresentation && isMainQuery) {
mainQuery.cteManager.buildClause(sbSelectFrom);
}
buildPageIdQueryString(sbSelectFrom, externalRepresentation);
return sbSelectFrom.toString();
}
private String buildPageIdQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) {
sbSelectFrom.append("SELECT ");
queryGenerator.setQueryBuffer(sbSelectFrom);
queryGenerator.setClauseType(ClauseType.SELECT);
ResolvedExpression[] identifierExpressionsToUse = getIdentifierExpressionsToUse();
for (int i = 0; i < identifierExpressionsToUse.length; i++) {
identifierExpressionsToUse[i].getExpression().accept(queryGenerator);
if (identifierToUseSelectAliases[i] != null) {
sbSelectFrom.append(" AS ");
sbSelectFrom.append(identifierToUseSelectAliases[i]);
}
sbSelectFrom.append(", ");
}
sbSelectFrom.setLength(sbSelectFrom.length() - 2);
if (needsNewIdList) {
orderByManager.buildSelectClauses(sbSelectFrom, keysetExtraction, keysetToSelectIndexMapping);
}
List whereClauseConjuncts = new ArrayList<>();
List optionalWhereClauseConjuncts = new ArrayList<>();
// The id query does not have any fetch owners
// Note that we always exclude the nodes with group by dependency. We consider just the ones from the identifiers
Set idNodesToFetch = Collections.emptySet();
Set identifierExpressionsToUseNonRootJoinNodes = getIdentifierExpressionsToUseNonRootJoinNodes();
joinManager.buildClause(sbSelectFrom, ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, null, false, externalRepresentation, true, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, idNodesToFetch, identifierExpressionsToUseNonRootJoinNodes);
if (keysetMode == KeysetMode.NONE) {
whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null);
} else {
sbSelectFrom.append(" WHERE ");
int positionalOffset = parameterManager.getPositionalOffset();
if (mainQuery.getQueryConfiguration().isOptimizedKeysetPredicateRenderingEnabled()) {
keysetManager.buildOptimizedKeysetPredicate(sbSelectFrom, positionalOffset);
} else {
keysetManager.buildKeysetPredicate(sbSelectFrom, positionalOffset);
}
if (whereManager.hasPredicates() || !whereClauseConjuncts.isEmpty()) {
sbSelectFrom.append(" AND ");
whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null);
}
}
boolean inverseOrder = keysetMode == KeysetMode.PREVIOUS;
// TODO: Think about optimizing this here
// We could avoid rendering the group by clause if the collection joins aren't referenced
// We could also build a minimal group by clause when hasGroupBy == false, otherwise we require the clause
// The same optimization can be done in the simplePageIdQueryString
groupByManager.buildGroupBy(sbSelectFrom, ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, getIdentifierExpressionsToUse());
havingManager.buildClause(sbSelectFrom);
// Resolve select aliases to their actual expressions only if the select items aren't included
orderByManager.buildOrderBy(sbSelectFrom, inverseOrder, !needsNewIdList, needsNewIdList);
// execute illegal collection access check
orderByManager.acceptVisitor(new IllegalSubqueryDetector(aliasManager));
return sbSelectFrom.toString();
}
@Override
protected String buildBaseQueryString(boolean externalRepresentation) {
StringBuilder sbSelectFrom = new StringBuilder();
if (externalRepresentation && isMainQuery) {
mainQuery.cteManager.buildClause(sbSelectFrom);
}
buildBaseQueryString(sbSelectFrom, externalRepresentation);
return sbSelectFrom.toString();
}
@Override
protected void buildBaseQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) {
selectManager.buildSelect(sbSelectFrom, false, externalRepresentation);
/**
* we have already selected the IDs so now we only need so select the
* fields and apply the ordering all other clauses are not required any
* more and therefore we can also omit any joins which the SELECT or the
* ORDER_BY clause do not depend on
*/
List whereClauseConjuncts = new ArrayList<>();
// We always have a where clause, so no need for an separate collection
List optionalWhereClauseConjuncts = whereClauseConjuncts;
joinManager.buildClause(sbSelectFrom, OBJECT_QUERY_CLAUSE_EXCLUSIONS, null, false, externalRepresentation, false, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, nodesToFetch, Collections.EMPTY_SET);
sbSelectFrom.append(" WHERE ");
ResolvedExpression[] identifierExpressions = getIdentifierExpressions();
ResolvedExpression[] resultUniqueExpressions = getUniqueIdentifierExpressions();
if (resultUniqueExpressions != null) {
identifierExpressions = resultUniqueExpressions;
}
queryGenerator.setQueryBuffer(sbSelectFrom);
if (identifierExpressions.length == 1) {
identifierExpressions[0].getExpression().accept(queryGenerator);
sbSelectFrom.append(" IN :").append(ID_PARAM_NAME);
} else {
sbSelectFrom.append('(');
for (int i = 0; i < maxResults; i++) {
for (int j = 0; j < identifierExpressions.length; j++) {
identifierExpressions[j].getExpression().accept(queryGenerator);
sbSelectFrom.append(" = :").append(ID_PARAM_NAME);
sbSelectFrom.append('_').append(j).append('_').append(i);
sbSelectFrom.append(" AND ");
}
sbSelectFrom.setLength(sbSelectFrom.length() - " AND ".length());
sbSelectFrom.append(" OR ");
}
sbSelectFrom.setLength(sbSelectFrom.length() - " OR ".length());
sbSelectFrom.append(')');
}
for (String conjunct : whereClauseConjuncts) {
sbSelectFrom.append(" AND ");
sbSelectFrom.append(conjunct);
}
if (hasGroupBy) {
groupByManager.buildGroupBy(sbSelectFrom, OBJECT_QUERY_CLAUSE_EXCLUSIONS);
havingManager.buildClause(sbSelectFrom);
}
orderByManager.buildOrderBy(sbSelectFrom, false, false, false);
}
private String buildObjectQueryString(boolean externalRepresentation) {
StringBuilder sbSelectFrom = new StringBuilder();
if (externalRepresentation && isMainQuery) {
mainQuery.cteManager.buildClause(sbSelectFrom);
}
buildObjectQueryString(sbSelectFrom, externalRepresentation);
return sbSelectFrom.toString();
}
private String buildObjectQueryString(StringBuilder sbSelectFrom, boolean externalRepresentation) {
selectManager.buildSelect(sbSelectFrom, false, externalRepresentation);
if (keysetExtraction) {
orderByManager.buildSelectClauses(sbSelectFrom, true, keysetToSelectIndexMapping);
}
List whereClauseConjuncts = new ArrayList<>();
List optionalWhereClauseConjuncts = new ArrayList<>();
joinManager.buildClause(sbSelectFrom, hasGroupBy ? NO_CLAUSE_EXCLUSION : OBJECT_QUERY_WITHOUT_GROUP_BY_EXCLUSIONS, null, false, externalRepresentation, false, optionalWhereClauseConjuncts, whereClauseConjuncts, null, explicitVersionEntities, nodesToFetch, Collections.EMPTY_SET);
if (keysetMode == KeysetMode.NONE) {
whereManager.buildClause(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null);
} else {
sbSelectFrom.append(" WHERE ");
int positionalOffset = parameterManager.getPositionalOffset();
if (mainQuery.getQueryConfiguration().isOptimizedKeysetPredicateRenderingEnabled()) {
keysetManager.buildOptimizedKeysetPredicate(sbSelectFrom, positionalOffset);
} else {
keysetManager.buildKeysetPredicate(sbSelectFrom, positionalOffset);
}
if (whereManager.hasPredicates() || !whereClauseConjuncts.isEmpty()) {
sbSelectFrom.append(" AND ");
whereManager.buildClausePredicate(sbSelectFrom, whereClauseConjuncts, optionalWhereClauseConjuncts, null);
}
}
boolean inverseOrder = keysetMode == KeysetMode.PREVIOUS;
appendGroupByClause(sbSelectFrom);
orderByManager.buildOrderBy(sbSelectFrom, inverseOrder, false, false);
// execute illegal collection access check
orderByManager.acceptVisitor(new IllegalSubqueryDetector(aliasManager));
return sbSelectFrom.toString();
}
@Override
public PaginatedCriteriaBuilder distinct() {
throw new IllegalStateException("Calling distinct() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public RestrictionBuilder> having(String expression) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public CaseWhenStarterBuilder>> havingCase() {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public SimpleCaseWhenStarterBuilder>> havingSimpleCase(String expression) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public HavingOrBuilder> havingOr() {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public SubqueryInitiator> havingExists() {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public SubqueryInitiator> havingNotExists() {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public SubqueryBuilder> havingExists(FullQueryBuilder criteriaBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public SubqueryBuilder> havingNotExists(FullQueryBuilder criteriaBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public SubqueryInitiator>> havingSubquery() {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public SubqueryInitiator>> havingSubquery(String subqueryAlias, String expression) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public MultipleSubqueryInitiator>> havingSubqueries(String expression) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public SubqueryBuilder>> havingSubquery(FullQueryBuilder criteriaBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public SubqueryBuilder>> havingSubquery(String subqueryAlias, String expression, FullQueryBuilder criteriaBuilder) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public PaginatedCriteriaBuilder setHavingExpression(String expression) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
public MultipleSubqueryInitiator> setHavingExpressionSubqueries(String expression) {
throw new IllegalStateException("Calling having() on a PaginatedCriteriaBuilder is not allowed.");
}
@Override
@SuppressWarnings("unchecked")
public SelectObjectBuilder> selectNew(Class clazz) {
return (SelectObjectBuilder>) super.selectNew(clazz);
}
@Override
@SuppressWarnings("unchecked")
public PaginatedCriteriaBuilder selectNew(ObjectBuilder builder) {
return (PaginatedCriteriaBuilder) super.selectNew(builder);
}
}