Skip to content

Commit

Permalink
[Blazebit#370, Blazebit#1357] Fix entity view pagination issue with j…
Browse files Browse the repository at this point in the history
…oined collection and subselect fetching and implement support for subselect fetching when main query builder has LIMIT/OFFSET and/or ORDER BY clause
  • Loading branch information
beikov committed Nov 4, 2024
1 parent 89745ca commit aba7dd6
Show file tree
Hide file tree
Showing 20 changed files with 350 additions and 153 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Not yet released
* Fix correlation predicate root type in validation
* Allow usage of TRUE and FALSE as path continuation tokens
* Fix coalescing add/remove operations to avoid constraint violation if new object should be persisted
* Fix entity view pagination issue with joined collection and subselect fetching
* Implement support for subselect fetching when main query builder has LIMIT/OFFSET and/or ORDER BY clause

### Backwards-incompatible changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ public interface FullQueryBuilder<T, X extends FullQueryBuilder<T, X>> extends Q
*/
public <Y> FullQueryBuilder<Y, ?> copy(Class<Y> resultClass);

/**
* Copies this query builder into a new {@link CriteriaBuilder}, using it's projection as an overridable default.
*
* @param <Y> The type of the result class
* @param resultClass The result class of the query
* @param copyOrderBy Whether the order by clause should be copied
* @return A new CriteriaBuilder
* @since 1.6.3
*/
public <Y> CriteriaBuilder<Y> copyCriteriaBuilder(Class<Y> resultClass, boolean copyOrderBy);

/**
* Returns a query that counts the results that would be produced if the current query was run.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ protected AbstractCommonQueryBuilder(AbstractCommonQueryBuilder<QueryResultType,
new SizeTransformerGroup(sizeTransformationVisitor, this, orderByManager, selectManager, joinManager, groupByManager, parameterManager));
this.resultType = builder.resultType;

applyFrom(builder, isMainQuery, true, true, Collections.<ClauseType>emptySet(), Collections.<JoinNode>emptySet(), joinManagerMapping, copyContext);
applyFrom(builder, isMainQuery, true, true, true, Collections.<ClauseType>emptySet(), Collections.<JoinNode>emptySet(), joinManagerMapping, copyContext);
}

protected AbstractCommonQueryBuilder(MainQuery mainQuery, QueryContext queryContext, boolean isMainQuery, DbmsStatementType statementType, Class<QueryResultType> resultClazz, String alias, AliasManager aliasManager, JoinManager parentJoinManager, ExpressionFactory expressionFactory, FinalSetReturn finalSetOperationBuilder, boolean implicitFromClause) {
Expand Down Expand Up @@ -374,7 +374,7 @@ public AbstractCommonQueryBuilder(MainQuery mainQuery, QueryContext queryContext

abstract AbstractCommonQueryBuilder<QueryResultType, BuilderType, SetReturn, SubquerySetReturn, FinalSetReturn> copy(QueryContext queryContext, Map<JoinManager, JoinManager> joinManagerMapping, ExpressionCopyContext copyContext);

ExpressionCopyContext applyFrom(AbstractCommonQueryBuilder<?, ?, ?, ?, ?> builder, boolean copyMainQuery, boolean copySelect, boolean fixedSelect, Set<ClauseType> clauseExclusions, Set<JoinNode> alwaysIncludedNodes, Map<JoinManager, JoinManager> joinManagerMapping, ExpressionCopyContext copyContext) {
ExpressionCopyContext applyFrom(AbstractCommonQueryBuilder<?, ?, ?, ?, ?> builder, boolean copyMainQuery, boolean copySelect, boolean fixedSelect, boolean copyOrderBy, Set<ClauseType> clauseExclusions, Set<JoinNode> alwaysIncludedNodes, Map<JoinManager, JoinManager> joinManagerMapping, ExpressionCopyContext copyContext) {
if (copyMainQuery) {
copyContext = new ExpressionCopyContextMap(parameterManager.copyFrom(builder.parameterManager), false);
mainQuery.cteManager.applyFrom(builder.mainQuery.cteManager, joinManagerMapping, copyContext);
Expand All @@ -387,7 +387,9 @@ ExpressionCopyContext applyFrom(AbstractCommonQueryBuilder<?, ?, ?, ?, ?> builde
whereManager.applyFrom(builder.whereManager, copyContext);
havingManager.applyFrom(builder.havingManager, copyContext);
groupByManager.applyFrom(builder.groupByManager, clauseExclusions, copyContext);
orderByManager.applyFrom(builder.orderByManager, copyContext);
if (copyOrderBy) {
orderByManager.applyFrom(builder.orderByManager, copyContext);
}

setFirstResult(builder.firstResult);
setMaxResults(builder.maxResults);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.blazebit.persistence.SubqueryBuilder;
import com.blazebit.persistence.SubqueryInitiator;
import com.blazebit.persistence.impl.function.alias.AliasFunction;
import com.blazebit.persistence.impl.function.coltrunc.ColumnTruncFunction;
import com.blazebit.persistence.impl.function.count.AbstractCountFunction;
import com.blazebit.persistence.impl.function.countwrapper.CountWrapperFunction;
import com.blazebit.persistence.impl.function.entity.EntityFunction;
Expand All @@ -46,7 +47,9 @@
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.FunctionExpression;
import com.blazebit.persistence.parser.expression.PathExpression;
import com.blazebit.persistence.parser.expression.StringLiteral;
import com.blazebit.persistence.parser.util.JpaMetamodelUtils;
import com.blazebit.persistence.parser.util.TypeUtils;
import com.blazebit.persistence.spi.AttributeAccessor;
Expand Down Expand Up @@ -141,13 +144,21 @@ AbstractCommonQueryBuilder<T, X, Z, W, FinalSetReturn> copy(QueryContext queryCo

@Override
public <Y> FullQueryBuilder<Y, ?> copy(Class<Y> resultClass) {
return copyCriteriaBuilder(resultClass, true);
}

@Override
public <Y> CriteriaBuilderImpl<Y> copyCriteriaBuilder(Class<Y> resultClass, boolean copyOrderBy) {
if (createdPaginatedBuilder) {
throw new IllegalStateException("Calling copy() on a CriteriaBuilder that was transformed to a PaginatedCriteriaBuilder is not allowed.");
}
prepareAndCheck();
MainQuery mainQuery = cbf.createMainQuery(getEntityManager());
mainQuery.copyConfiguration(this.mainQuery.getQueryConfiguration());
CriteriaBuilderImpl<Y> newBuilder = new CriteriaBuilderImpl<Y>(mainQuery, true, resultClass, null);
newBuilder.fromClassExplicitlySet = true;

newBuilder.applyFrom(this, true, true, false, Collections.<ClauseType>emptySet(), Collections.<JoinNode>emptySet(), new IdentityHashMap<JoinManager, JoinManager>(), ExpressionCopyContext.EMPTY);
newBuilder.applyFrom(this, true, true, false, copyOrderBy, Collections.<ClauseType>emptySet(), Collections.<JoinNode>emptySet(), new IdentityHashMap<JoinManager, JoinManager>(), ExpressionCopyContext.EMPTY);

return newBuilder;
}
Expand Down Expand Up @@ -187,8 +198,12 @@ protected CriteriaBuilder<Object[]> createPageIdQuery(KeysetPage keysetPage, int
mainQuery.copyConfiguration(this.mainQuery.getQueryConfiguration());
CriteriaBuilderImpl<Object[]> newBuilder = new CriteriaBuilderImpl<>(mainQuery, true, Object[].class, null);
newBuilder.fromClassExplicitlySet = true;
applyPageIdQueryInto(newBuilder, keysetPage, firstResult, maxResults, identifierExpressionsToUse, false);
return newBuilder;
}

newBuilder.applyFrom(this, true, false, false, ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, getIdentifierExpressionsToUseNonRootJoinNodes(identifierExpressionsToUse), new IdentityHashMap<JoinManager, JoinManager>(), ExpressionCopyContext.EMPTY);
protected void applyPageIdQueryInto(AbstractCommonQueryBuilder<?, ?, ?, ?, ?> newBuilder, KeysetPage keysetPage, int firstResult, int maxResults, ResolvedExpression[] identifierExpressionsToUse, boolean withAlias) {
ExpressionCopyContext expressionCopyContext = newBuilder.applyFrom(this, true, false, false, false, ID_QUERY_GROUP_BY_CLAUSE_EXCLUSIONS, getIdentifierExpressionsToUseNonRootJoinNodes(identifierExpressionsToUse), new IdentityHashMap<JoinManager, JoinManager>(), ExpressionCopyContext.EMPTY);
newBuilder.setFirstResult(firstResult);
newBuilder.setMaxResults(maxResults);

Expand All @@ -210,30 +225,25 @@ protected CriteriaBuilder<Object[]> createPageIdQuery(KeysetPage keysetPage, int
newBuilder.keysetManager.initialize(orderByExpressions);
}

String[] identifierToUseSelectAliases = new String[identifierExpressionsToUse.length];
// Applying order by items needs special care for page id queries because we have to re-alias the items to avoid collisions
Map<String, Integer> identifierExpressionStringMap = new HashMap<>(identifierExpressionsToUse.length);

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

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;
}
if (withAlias) {
for (int i = 0; i < identifierExpressionsToUse.length; i++) {
List<Expression> args = new ArrayList<>(2);
args.add(identifierExpressionsToUse[i].getExpression().copy(expressionCopyContext));
args.add(new StringLiteral(ColumnTruncFunction.SYNTHETIC_COLUMN_PREFIX + i));
newBuilder.selectManager.select(new FunctionExpression(AliasFunction.FUNCTION_NAME, args), identifierToUseSelectAliases[i]);
}
} else {
for (int i = 0; i < identifierExpressionsToUse.length; i++) {
newBuilder.selectManager.select(identifierExpressionsToUse[i].getExpression().copy(expressionCopyContext), identifierToUseSelectAliases[i]);
}
}
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ <Y> FullSelectCTECriteriaBuilder<Y> with(EntityType<?> cteEntity, Y result, Abst
CTEKey cteKey = getCteKey(cteEntity, null, inlineOwner);
assertCteNameAvailable(cteKey);
FullSelectCTECriteriaBuilderImpl<Y> cteBuilder = new FullSelectCTECriteriaBuilderImpl<Y>(mainQuery, queryContext, cteKey, inline, (Class<Object>) cteClass, result, this, parentAliasManager, parentJoinManager);
cteBuilder.applyFrom(builder, true, false, false, Collections.<ClauseType>emptySet(), Collections.<JoinNode>emptySet(), new IdentityHashMap<JoinManager, JoinManager>(), ExpressionCopyContext.EMPTY);
cteBuilder.applyFrom(builder, true, false, false, true, Collections.<ClauseType>emptySet(), Collections.<JoinNode>emptySet(), new IdentityHashMap<JoinManager, JoinManager>(), ExpressionCopyContext.EMPTY);
this.onBuilderStarted(cteBuilder);
return cteBuilder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,20 @@
package com.blazebit.persistence.impl;

import com.blazebit.persistence.impl.function.alias.AliasFunction;
import com.blazebit.persistence.impl.function.coltrunc.ColumnTruncFunction;
import com.blazebit.persistence.impl.transform.ExpressionModifierVisitor;
import com.blazebit.persistence.parser.EntityMetamodel;
import com.blazebit.persistence.parser.SimpleQueryGenerator;
import com.blazebit.persistence.parser.expression.Expression;
import com.blazebit.persistence.parser.expression.ExpressionCopyContext;
import com.blazebit.persistence.parser.expression.ExpressionFactory;
import com.blazebit.persistence.parser.expression.FunctionExpression;
import com.blazebit.persistence.parser.expression.GeneralCaseExpression;
import com.blazebit.persistence.parser.expression.NumericLiteral;
import com.blazebit.persistence.parser.expression.NumericType;
import com.blazebit.persistence.parser.expression.PathExpression;
import com.blazebit.persistence.parser.expression.PropertyExpression;
import com.blazebit.persistence.parser.expression.StringLiteral;
import com.blazebit.persistence.parser.expression.WhenClauseExpression;
import com.blazebit.persistence.parser.expression.modifier.ExpressionModifier;
import com.blazebit.persistence.parser.predicate.CompoundPredicate;
Expand All @@ -39,6 +42,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
Expand Down Expand Up @@ -79,6 +83,43 @@ void applyFrom(OrderByManager orderByManager, ExpressionCopyContext copyContext)
}
}

String[] applyFrom(OrderByManager orderByManager, Map<String, Integer> identifierExpressionStringMap) {
String[] identifierToUseSelectAliases = new String[identifierExpressionStringMap.size()];
for (int i = 0; i < orderByManager.orderByInfos.size(); i++) {
OrderByInfo info = orderByManager.orderByInfos.get(i);
String potentialSelectAlias = info.getExpressionString();
AliasInfo aliasInfo = orderByManager.aliasManager.getAliasInfo(potentialSelectAlias);
Expression expression;
if (aliasInfo instanceof SelectInfo) {
SelectInfo selectInfo = (SelectInfo) aliasInfo;
Integer selectItemIndex = identifierExpressionStringMap.get(selectInfo.getExpression().toString());
if (selectItemIndex != null) {
// We need to use the same alias as in the SQL because Hibernate for some reason does not resolve aliases in the order by clause of subqueries
String alias = ColumnTruncFunction.SYNTHETIC_COLUMN_PREFIX + selectItemIndex;
identifierToUseSelectAliases[selectItemIndex] = alias;
expression = new PathExpression(new PropertyExpression(alias));
} else {
// We have an order by item with an alias that is not part of the identifier expression map
Expression copiedSelectExpression = selectInfo.getExpression().copy(ExpressionCopyContext.EMPTY);
if (selectInfo.getExpression() instanceof PathExpression) {
expression = copiedSelectExpression;
} else {
String alias = aliasManager.generateRootAlias("_generated_alias");
selectManager.select(copiedSelectExpression, alias);
List<Expression> args = new ArrayList<>(2);
args.add(new PathExpression(new PropertyExpression(alias)));
args.add(new StringLiteral(alias));
expression = new FunctionExpression(AliasFunction.FUNCTION_NAME, args);
}
}
} else {
expression = info.getExpression().copy(ExpressionCopyContext.EMPTY);
}
orderBy(subqueryInitFactory.reattachSubqueries(expression, ClauseType.ORDER_BY), info.ascending, info.nullFirst);
}
return identifierToUseSelectAliases;
}

@Override
public ClauseType getClauseType() {
return ClauseType.ORDER_BY;
Expand Down
Loading

0 comments on commit aba7dd6

Please sign in to comment.