Skip to content

Commit

Permalink
[Blazebit#249] Allow embedding of count query into id/object query
Browse files Browse the repository at this point in the history
  • Loading branch information
beikov committed Jan 5, 2020
1 parent 682a367 commit f2d7f5f
Show file tree
Hide file tree
Showing 21 changed files with 372 additions and 197 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Not yet released
* Allow extraction of `CriteriaBuilder` for id-query from `FullQueryBuilder`
* Allow definition of CTE based on `CriteriaBuilder`
* Allow to control embedding of id query into pagination object query and enable by default if DBMS allows it
* Allow to control embedding of count query into pagination object/id query and enable by default if possible

### Bug fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,23 @@ public interface PaginatedCriteriaBuilder<T> extends FullQueryBuilder<T, Paginat
*/
public boolean isWithInlineIdQuery();

/**
* Forces the inlining of an count query into the object or id query.
*
* @param withInlineCountQuery true to inline the count query, false otherwise
* @return The query builder for chaining calls
* @since 1.4.1
*/
public PaginatedCriteriaBuilder<T> withInlineCountQuery(boolean withInlineCountQuery);

/**
* Returns whether count query should be inlined.
*
* @return true when count query should be inlined, false otherwise
* @since 1.4.1
*/
public boolean isWithInlineCountQuery();

/**
* Creates and returns a new {@link CriteriaBuilder} that can be used to query the id values for the current page.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.blazebit.persistence.SimpleCaseWhenStarterBuilder;
import com.blazebit.persistence.SubqueryBuilder;
import com.blazebit.persistence.SubqueryInitiator;
import com.blazebit.persistence.impl.builder.object.CountExtractionObjectBuilder;
import com.blazebit.persistence.impl.builder.object.DelegatingKeysetExtractionObjectBuilder;
import com.blazebit.persistence.impl.builder.object.KeysetExtractionObjectBuilder;
import com.blazebit.persistence.impl.function.alias.AliasFunction;
Expand Down Expand Up @@ -77,6 +78,7 @@ public class PaginatedCriteriaBuilderImpl<T> extends AbstractFullQueryBuilder<T,
private boolean withCountQuery = true;
private boolean withForceIdQuery = false;
private Boolean withInlineIdQuery;
private boolean withInlineCountQuery;
private int highestOffset = 0;
private final KeysetPage keysetPage;
private final ResolvedExpression[] identifierExpressions;
Expand All @@ -103,6 +105,7 @@ public PaginatedCriteriaBuilderImpl(AbstractFullQueryBuilder<T, ? extends FullQu
this.entityId = entityId;
this.maxResults = pageSize;
this.identifierExpressions = identifierExpressions;
this.withInlineCountQuery = entityId == null;
updateKeysetMode();
}

Expand All @@ -120,6 +123,7 @@ public PaginatedCriteriaBuilderImpl(AbstractFullQueryBuilder<T, ? extends FullQu
this.entityId = null;
this.maxResults = pageSize;
this.identifierExpressions = identifierExpressions;
this.withInlineCountQuery = true;
updateKeysetMode();
}

Expand Down Expand Up @@ -270,6 +274,23 @@ public boolean isWithInlineIdQuery() {
return withInlineIdQuery;
}

@Override
public boolean isWithInlineCountQuery() {
return withInlineCountQuery;
}

@Override
public PaginatedCriteriaBuilder<T> withInlineCountQuery(boolean withInlineCountQuery) {
if (withInlineCountQuery && entityId != null) {
throw new IllegalStateException("Can't inline the count query when paginating to a page by entity id!");
}
if (this.withInlineCountQuery != withInlineCountQuery) {
prepareForModification(ClauseType.SELECT);
}
this.withInlineCountQuery = withInlineCountQuery;
return this;
}

@Override
protected ResolvedExpression[] getIdentifierExpressions() {
if (identifierExpressions != null) {
Expand Down Expand Up @@ -324,7 +345,7 @@ public PaginatedTypedQueryImpl<T> getQuery() {
// We can only use the query directly if we have no ctes, entity functions or hibernate bugs
Set<JoinNode> keyRestrictedLeftJoins = joinManager.getKeyRestrictedLeftJoins();
boolean normalQueryMode = !isMainQuery || (!mainQuery.cteManager.hasCtes() && !joinManager.hasEntityFunctions() && keyRestrictedLeftJoins.isEmpty());
TypedQuery<?> countQuery = null;
TypedQuery<?> countQuery;
String countQueryString = getPageCountQueryStringWithoutCheck();

if (entityId == null) {
Expand All @@ -336,16 +357,17 @@ public PaginatedTypedQueryImpl<T> getQuery() {

TypedQuery<?> idQuery = null;
TypedQuery<T> objectQuery;
KeysetExtractionObjectBuilder<T> objectBuilder;
ObjectBuilder<T> objectBuilder;
boolean inlinedIdQuery;
boolean inlinedCountQuery = withCountQuery && withInlineCountQuery;
if (!isWithInlineIdQuery() && (hasCollections || withForceIdQuery)) {
String idQueryString = getPageIdQueryStringWithoutCheck();
idQuery = getIdQuery(idQueryString, normalQueryMode, keyRestrictedLeftJoins);
objectQuery = getObjectQueryById(normalQueryMode, keyRestrictedLeftJoins);
objectBuilder = null;
inlinedIdQuery = false;
} else {
Map.Entry<TypedQuery<T>, KeysetExtractionObjectBuilder<T>> entry = getObjectQuery(normalQueryMode, keyRestrictedLeftJoins);
Map.Entry<TypedQuery<T>, ObjectBuilder<T>> entry = getObjectQuery(normalQueryMode, keyRestrictedLeftJoins);
objectQuery = entry.getKey();
objectBuilder = entry.getValue();
inlinedIdQuery = isWithInlineIdQuery() && (hasCollections || withForceIdQuery);
Expand All @@ -368,7 +390,8 @@ public PaginatedTypedQueryImpl<T> getQuery() {
keysetMode,
keysetPage,
forceFirstResult,
inlinedIdQuery
inlinedIdQuery,
inlinedCountQuery
);
return query;
}
Expand Down Expand Up @@ -532,7 +555,7 @@ protected void prepareAndCheck() {
// initialize index mappings that we use to avoid putting keyset expressions into select clauses multiple times
if (!isWithInlineIdQuery() && (hasCollections || withForceIdQuery)) {
initializeOrderByAliasesWithIdentifierToUse(orderByExpressions);
} else if (keysetExtraction) {
} else if (keysetExtraction || withInlineCountQuery) {
if (isWithInlineIdQuery()) {
initializeOrderByAliasesWithIdentifierToUse(orderByExpressions);
// If we have no select item, this means we implicitly select the root and thus need to offset the index mapping as we will append that
Expand Down Expand Up @@ -610,12 +633,12 @@ private void initializeOrderByAliasesWithIdentifierToUse(List<OrderByExpression>
identifierToUseSelectAliases[i] = potentialSelectAlias;
keysetToSelectIndexMapping[i] = index;
}
} else if (keysetExtraction) {
} else if (keysetExtraction || withInlineCountQuery) {
index = identifierExpressionStringMap.get(potentialSelectAlias);
keysetToSelectIndexMapping[i] = index == null ? -1 : index;
}
}
if (!keysetExtraction) {
if (!keysetExtraction && !withInlineCountQuery) {
keysetToSelectIndexMapping = null;
}
}
Expand Down Expand Up @@ -647,12 +670,12 @@ private ResolvedExpression[] findMissingExpressions(ResolvedExpression[] targetI
}

@SuppressWarnings("unchecked")
private Map.Entry<TypedQuery<T>, KeysetExtractionObjectBuilder<T>> getObjectQuery(boolean normalQueryMode, Set<JoinNode> keyRestrictedLeftJoins) {
private Map.Entry<TypedQuery<T>, ObjectBuilder<T>> getObjectQuery(boolean normalQueryMode, Set<JoinNode> keyRestrictedLeftJoins) {
String queryString = getBaseQueryString();
Class<?> expectedResultType;

// When the keyset is included the query obviously produces an array
if (keysetExtraction) {
if (keysetExtraction || withCountQuery && withInlineCountQuery) {
expectedResultType = Object[].class;
} else {
expectedResultType = selectManager.getExpectedQueryResultType();
Expand Down Expand Up @@ -696,24 +719,27 @@ private Map.Entry<TypedQuery<T>, KeysetExtractionObjectBuilder<T>> getObjectQuer
parameterManager.parameterizeQuery(query);
}

KeysetExtractionObjectBuilder<T> objectBuilder = null;
ObjectBuilder<T> objectBuilder = null;
ObjectBuilder<T> transformerObjectBuilder = selectManager.getSelectObjectBuilder();
boolean inlinedCountQuery = withCountQuery && withInlineCountQuery;

if (keysetExtraction) {
if (transformerObjectBuilder == null) {
objectBuilder = new KeysetExtractionObjectBuilder<T>(keysetToSelectIndexMapping, keysetMode, selectManager.getExpectedQueryResultType() != Object[].class, withExtractAllKeysets);
objectBuilder = new KeysetExtractionObjectBuilder<T>(keysetToSelectIndexMapping, keysetMode, selectManager.getExpectedQueryResultType() != Object[].class, withExtractAllKeysets, inlinedCountQuery);
} else {
objectBuilder = new DelegatingKeysetExtractionObjectBuilder<T>(transformerObjectBuilder, keysetToSelectIndexMapping, keysetMode, withExtractAllKeysets);
objectBuilder = new DelegatingKeysetExtractionObjectBuilder<T>(transformerObjectBuilder, keysetToSelectIndexMapping, keysetMode, withExtractAllKeysets, inlinedCountQuery);
}

transformerObjectBuilder = objectBuilder;
} else if (inlinedCountQuery && transformerObjectBuilder != null) {
transformerObjectBuilder = objectBuilder = new CountExtractionObjectBuilder<>(transformerObjectBuilder);
}

if (transformerObjectBuilder != null) {
query = new ObjectBuilderTypedQuery<>(query, transformerObjectBuilder);
}

return new AbstractMap.SimpleEntry<TypedQuery<T>, KeysetExtractionObjectBuilder<T>>(query, objectBuilder);
return new AbstractMap.SimpleEntry<TypedQuery<T>, ObjectBuilder<T>>(query, objectBuilder);
}

private TypedQuery<Object[]> getIdQuery(String idQueryString, boolean normalQueryMode, Set<JoinNode> keyRestrictedLeftJoins) {
Expand Down Expand Up @@ -878,10 +904,22 @@ private String buildPageIdQueryString(StringBuilder sbSelectFrom, boolean aliasF
sbSelectFrom.append(", ");
}
}

sbSelectFrom.setLength(sbSelectFrom.length() - 2);

if (needsNewIdList) {
orderByManager.buildSelectClauses(sbSelectFrom, keysetExtraction, aliasFunction && !externalRepresentation, keysetToSelectIndexMapping);
if (isWithInlineIdQuery()) {
// We need to pass a null keysetToSelectIndexMapping in this case to force rendering the order by alias expressions to the id query
orderByManager.buildSelectClauses(sbSelectFrom, false, aliasFunction && !externalRepresentation, null);
} else {
orderByManager.buildSelectClauses(sbSelectFrom, keysetExtraction, aliasFunction && !externalRepresentation, keysetToSelectIndexMapping);
}
}

if (!aliasFunction && withCountQuery && withInlineCountQuery) {
sbSelectFrom.append(", (");
buildPageCountQueryString(sbSelectFrom, externalRepresentation, false);
sbSelectFrom.append(')');
}

List<String> whereClauseConjuncts = new ArrayList<>();
Expand Down Expand Up @@ -1015,6 +1053,12 @@ private String buildObjectQueryString(StringBuilder sbSelectFrom, boolean extern
}
}

if (withCountQuery && withInlineCountQuery) {
sbSelectFrom.append(", (");
buildPageCountQueryString(sbSelectFrom, externalRepresentation, false);
sbSelectFrom.append(')');
}

List<String> whereClauseConjuncts = new ArrayList<>();
List<String> optionalWhereClauseConjuncts = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package com.blazebit.persistence.impl;

import com.blazebit.persistence.KeysetPage;
import com.blazebit.persistence.ObjectBuilder;
import com.blazebit.persistence.PagedArrayList;
import com.blazebit.persistence.PagedList;
import com.blazebit.persistence.PaginatedTypedQuery;
import com.blazebit.persistence.impl.builder.object.CountExtractionObjectBuilder;
import com.blazebit.persistence.impl.builder.object.KeysetExtractionObjectBuilder;
import com.blazebit.persistence.impl.keyset.KeysetMode;
import com.blazebit.persistence.DefaultKeysetPage;
Expand Down Expand Up @@ -59,7 +61,7 @@ public class PaginatedTypedQueryImpl<X> implements PaginatedTypedQuery<X> {
private final TypedQuery<?> countQuery;
private final TypedQuery<?> idQuery;
private final TypedQuery<X> objectQuery;
private final KeysetExtractionObjectBuilder<X> objectBuilder;
private final ObjectBuilder<X> objectBuilder;
private final Map<String, Parameter<?>> parameters;
private final Map<String, ParameterLocation> parameterToQuery;
private final Object entityId;
Expand All @@ -74,9 +76,10 @@ public class PaginatedTypedQueryImpl<X> implements PaginatedTypedQuery<X> {
private final KeysetPage keysetPage;
private final boolean forceFirstResult;
private final boolean inlinedIdQuery;
private final boolean inlinedCountQuery;

public PaginatedTypedQueryImpl(boolean withExtractAllKeysets, boolean withCount, int highestOffset, TypedQuery<?> countQuery, TypedQuery<?> idQuery, TypedQuery<X> objectQuery, KeysetExtractionObjectBuilder<X> objectBuilder, Set<Parameter<?>> parameters,
Object entityId, int firstResult, int pageSize, int identifierCount, boolean needsNewIdList, int[] keysetToSelectIndexMapping, KeysetMode keysetMode, KeysetPage keysetPage, boolean forceFirstResult, boolean inlinedIdQuery) {
public PaginatedTypedQueryImpl(boolean withExtractAllKeysets, boolean withCount, int highestOffset, TypedQuery<?> countQuery, TypedQuery<?> idQuery, TypedQuery<X> objectQuery, ObjectBuilder<X> objectBuilder, Set<Parameter<?>> parameters,
Object entityId, int firstResult, int pageSize, int identifierCount, boolean needsNewIdList, int[] keysetToSelectIndexMapping, KeysetMode keysetMode, KeysetPage keysetPage, boolean forceFirstResult, boolean inlinedIdQuery, boolean inlinedCountQuery) {
this.withExtractAllKeysets = withExtractAllKeysets;
this.withCount = withCount;
this.highestOffset = highestOffset;
Expand All @@ -95,6 +98,7 @@ public PaginatedTypedQueryImpl(boolean withExtractAllKeysets, boolean withCount,
this.keysetPage = keysetPage;
this.forceFirstResult = forceFirstResult;
this.inlinedIdQuery = inlinedIdQuery;
this.inlinedCountQuery = inlinedCountQuery;

Map<String, Parameter<?>> params = new HashMap<>(parameters.size());
for (Parameter<?> parameter : parameters) {
Expand Down Expand Up @@ -168,7 +172,7 @@ public PagedList<X> getResultList() {
int queryFirstResult = firstResult;
int firstRow = firstResult;
long totalSize = -1L;
if (withCount) {
if (withCount && !inlinedCountQuery) {
if (entityId == null) {
totalSize = ((Number) countQuery.getSingleResult()).longValue();
} else {
Expand Down Expand Up @@ -213,7 +217,7 @@ private PagedList<X> getResultList(int queryFirstResult, int firstRow, long tota
newKeysetPage = keysetPage;
}

return new PagedArrayList<X>(newKeysetPage, totalSize, queryFirstResult, pageSize);
return new PagedArrayList<X>(newKeysetPage, withCount && totalSize == -1 ? getTotalCount() : totalSize, queryFirstResult, pageSize);
}

Serializable[] lowest = null;
Expand Down Expand Up @@ -268,6 +272,11 @@ private PagedList<X> getResultList(int queryFirstResult, int firstRow, long tota
}
}

// extract count
if (inlinedCountQuery) {
Object[] first = (Object[]) ids.get(0);
totalSize = (long) first[first.length - 1];
}
List<Object> newIds = new ArrayList<Object>(ids.size());
if (identifierCount > 1) {
for (int i = 0; i < ids.size(); i++) {
Expand Down Expand Up @@ -352,10 +361,33 @@ private PagedList<X> getResultList(int queryFirstResult, int firstRow, long tota
KeysetPage newKeyset = null;

if (keysetToSelectIndexMapping != null) {
Serializable[] lowest = objectBuilder.getLowest();
Serializable[] highest = objectBuilder.getHighest();
Serializable[][] keysets = objectBuilder.getKeysets();
newKeyset = new DefaultKeysetPage(firstRow, pageSize, lowest, highest, keysets);
if (objectBuilder == null) {
// extract count
if (inlinedCountQuery) {
Object[] first = (Object[]) result.get(0);
totalSize = (long) first[first.length - 1];
// If this would have been a non-object array type without the count query, we must unwrap the result
if (first.length == 2) {
List<X> newResult = new ArrayList<>(result.size());
for (int i = 0; i < result.size(); i++) {
newResult.add((X) ((Object[]) result.get(i))[0]);
}
result = newResult;
}
}
} else if (objectBuilder instanceof KeysetExtractionObjectBuilder<?>) {
KeysetExtractionObjectBuilder<?> keysetExtractionObjectBuilder = (KeysetExtractionObjectBuilder<?>) objectBuilder;
Serializable[] lowest = keysetExtractionObjectBuilder.getLowest();
Serializable[] highest = keysetExtractionObjectBuilder.getHighest();
Serializable[][] keysets = keysetExtractionObjectBuilder.getKeysets();
// extract count
if (inlinedCountQuery) {
totalSize = keysetExtractionObjectBuilder.getCount();
}
newKeyset = new DefaultKeysetPage(firstRow, pageSize, lowest, highest, keysets);
} else if (objectBuilder instanceof CountExtractionObjectBuilder<?>) {
totalSize = ((CountExtractionObjectBuilder<X>) objectBuilder).getCount();
}
}

PagedList<X> pagedResultList = new PagedArrayList<X>(result, newKeyset, totalSize, queryFirstResult, pageSize);
Expand Down
Loading

0 comments on commit f2d7f5f

Please sign in to comment.