Skip to content

Commit 6b8b81c

Browse files
maarten-ritensevaltimo-platform[bot]
authored andcommitted
Fix for total count of filtered task list result (#1512)
* added Query utils to detect if query to be executed concerns a count query, if so skip providing groupBy * switched to single result for count query * added copyright header * removed distinct from list query as this is not necessary * added additional todo for performance improvement * distinct is not necessary when also having a group by --------- Co-authored-by: valtimo-platform[bot] <80107705+valtimo-platform[bot]@users.noreply.github.com>
1 parent 50002ca commit 6b8b81c

File tree

6 files changed

+64
-35
lines changed

6 files changed

+64
-35
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2015-2024 Ritense BV, the Netherlands.
3+
*
4+
* Licensed under EUPL, Version 1.2 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" basis,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.ritense.authorization.utils
18+
19+
import jakarta.persistence.criteria.AbstractQuery
20+
import org.hibernate.query.sqm.function.SelfRenderingSqmAggregateFunction
21+
22+
object QueryUtils {
23+
24+
fun isCountQuery(query: AbstractQuery<*>) =
25+
query.selection is SelfRenderingSqmAggregateFunction
26+
&&
27+
(query.selection as SelfRenderingSqmAggregateFunction<out Any>).functionName == "count"
28+
}

core/src/main/java/com/ritense/valtimo/service/CamundaTaskService.java

+10-9
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,6 @@ public Page<TaskExtended> findTasksFiltered(
343343
var processDefinitionKeyPath = taskRoot.get(PROCESS_DEFINITION).get(KEY);
344344

345345
query.multiselect(taskRoot, executionIdPath, businessKeyPath, processDefinitionIdPath, processDefinitionKeyPath);
346-
query.distinct(true);
347346
query.where(specification.toPredicate(taskRoot, query, cb));
348347
query.groupBy(taskRoot, executionIdPath, businessKeyPath, processDefinitionIdPath, processDefinitionKeyPath);
349348
query.orderBy(getOrderBy(cb, taskRoot, pageable.getSort()));
@@ -388,14 +387,16 @@ public Page<TaskExtended> findTasksFiltered(
388387
})
389388
.toList();
390389

391-
var cbCount = entityManager.getCriteriaBuilder();
392-
var queryCount = cbCount.createQuery();
393-
var taskCountRoot = queryCount.from(CamundaTask.class);
394-
queryCount.select(cbCount.countDistinct(taskCountRoot));
395-
queryCount.where(specification.toPredicate(taskCountRoot, queryCount, cbCount));
396-
var results = entityManager.createQuery(queryCount).getResultList();
397-
long total = results.isEmpty() ? 0 : (long) results.get(0);
398-
return new PageImpl<>(tasks, pageable, total);
390+
return new PageImpl<>(tasks, pageable, countTasksFiltered(specification));
391+
}
392+
393+
private long countTasksFiltered(Specification<CamundaTask> specification) {
394+
var cb = entityManager.getCriteriaBuilder();
395+
var countQuery = cb.createQuery(Long.class);
396+
var taskCountRoot = countQuery.from(CamundaTask.class);
397+
countQuery.select(cb.countDistinct(taskCountRoot));
398+
countQuery.where(specification.toPredicate(taskCountRoot, countQuery, cb));
399+
return entityManager.createQuery(countQuery).getSingleResult();
399400
}
400401

401402
@Transactional(readOnly = true)

document/src/main/java/com/ritense/document/service/impl/JsonSchemaDocumentSearchService.java

+3-13
Original file line numberDiff line numberDiff line change
@@ -190,25 +190,15 @@ private Page<JsonSchemaDocument> search(QueryWhereBuilder queryWhereBuilder, Pag
190190
return new PageImpl<>(documents, pageable, count(queryWhereBuilder));
191191
}
192192

193-
private Long count(
194-
QueryWhereBuilder queryWhereBuilder
195-
) {
193+
private Long count(QueryWhereBuilder queryWhereBuilder) {
196194
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
197195
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
198196
Root<JsonSchemaDocument> countRoot = countQuery.from(JsonSchemaDocument.class);
199-
countQuery.select(cb.count(countRoot));
197+
countQuery.select(cb.countDistinct(countRoot));
200198
queryWhereBuilder.apply(cb, countQuery, countRoot);
201199

202200
// TODO: Should be turned into a subquery, and then do a count over the results from the subquery.
203-
List<Long> countResultList = entityManager.createQuery(countQuery).getResultList();
204-
205-
long count = 0L;
206-
207-
if (!countResultList.isEmpty()) {
208-
count = countResultList.size();
209-
}
210-
211-
return count;
201+
return entityManager.createQuery(countQuery).getSingleResult();
212202
}
213203

214204
private void buildQueryWhere(SearchRequest searchRequest, CriteriaBuilder cb, CriteriaQuery<?> query, Root<JsonSchemaDocument> documentRoot) {

document/src/main/kotlin/com/ritense/document/service/JsonSchemaDocumentSpecification.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ import com.ritense.authorization.AuthorizationContext.Companion.runWithoutAuthor
1919
import com.ritense.authorization.permission.Permission
2020
import com.ritense.authorization.request.AuthorizationRequest
2121
import com.ritense.authorization.specification.AuthorizationSpecification
22+
import com.ritense.authorization.utils.QueryUtils
2223
import com.ritense.document.domain.impl.JsonSchemaDocument
2324
import com.ritense.document.service.impl.JsonSchemaDocumentService
2425
import com.ritense.valtimo.contract.database.QueryDialectHelper
2526
import jakarta.persistence.criteria.AbstractQuery
2627
import jakarta.persistence.criteria.CriteriaBuilder
2728
import jakarta.persistence.criteria.Predicate
2829
import jakarta.persistence.criteria.Root
30+
import org.hibernate.query.sqm.function.SelfRenderingSqmAggregateFunction
2931

3032
class JsonSchemaDocumentSpecification(
3133
authRequest: AuthorizationRequest<JsonSchemaDocument>,
@@ -41,7 +43,7 @@ class JsonSchemaDocumentSpecification(
4143
): Predicate {
4244
// Filter the permissions for the relevant ones and use those to find the filters that are required
4345
// Turn those filters into predicates
44-
if (query.groupList.isEmpty()) {
46+
if (!QueryUtils.isCountQuery(query) && query.groupList.isEmpty()) {
4547
val groupList = ArrayList(query.groupList)
4648
groupList.add(root.get<Any>("id").get<Any>("id"))
4749
query.groupBy(groupList)

process-document/src/main/kotlin/com/ritense/processdocument/camunda/authorization/CamundaTaskDocumentMapper.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.ritense.processdocument.camunda.authorization
1818

1919
import com.ritense.authorization.AuthorizationEntityMapper
2020
import com.ritense.authorization.AuthorizationEntityMapperResult
21+
import com.ritense.authorization.utils.QueryUtils
2122
import com.ritense.document.domain.impl.JsonSchemaDocument
2223
import com.ritense.document.domain.impl.JsonSchemaDocumentId
2324
import com.ritense.document.repository.impl.JsonSchemaDocumentRepository
@@ -32,6 +33,7 @@ import com.ritense.valtimo.contract.database.QueryDialectHelper
3233
import jakarta.persistence.criteria.AbstractQuery
3334
import jakarta.persistence.criteria.CriteriaBuilder
3435
import jakarta.persistence.criteria.Root
36+
import org.hibernate.query.sqm.function.SelfRenderingSqmAggregateFunction
3537

3638
class CamundaTaskDocumentMapper(
3739
private val processDocumentInstanceRepository: ProcessDocumentInstanceRepository,
@@ -53,7 +55,9 @@ class CamundaTaskDocumentMapper(
5355
): AuthorizationEntityMapperResult<JsonSchemaDocument> {
5456
val documentRoot = query.from(JsonSchemaDocument::class.java)
5557
val processBusinessKey = root.get<CamundaExecution>(PROCESS_INSTANCE).get<String>(BUSINESS_KEY)
56-
query.groupBy(query.groupList + root.get<String>(ID))
58+
if (!QueryUtils.isCountQuery(query)) {
59+
query.groupBy(query.groupList + root.get<String>(ID))
60+
}
5761
val documentId = queryDialectHelper.uuidToString(
5862
criteriaBuilder,
5963
documentRoot.get<JsonSchemaDocumentId>(ID).get(ID)

process-document/src/main/kotlin/com/ritense/processdocument/service/CaseTaskListSearchService.kt

+15-11
Original file line numberDiff line numberDiff line change
@@ -183,24 +183,27 @@ class CaseTaskListSearchService(
183183
val groupList = query.groupList.toMutableList()
184184
groupList.addAll(selectCols)
185185
query.groupBy(groupList)
186-
query.where(constructWhere(cb, query, taskRoot, documentRoot, caseDefinitionName, advancedSearchRequest))
187-
query.orderBy(constructOrderBy(query, cb, taskRoot, documentRoot, pageable.sort))
188186

189-
val countQuery = cb.createQuery(Long::class.java)
190-
val countTaskRoot = countQuery.from(CamundaTask::class.java)
191-
val countDocumentRoot = countQuery.from(JsonSchemaDocument::class.java)
192-
countQuery.select(cb.count(countDocumentRoot))
193-
entityManager.createQuery(countQuery)
194-
countQuery.where(constructWhere(cb, countQuery, countTaskRoot, countDocumentRoot, caseDefinitionName, advancedSearchRequest))
187+
// TODO: look into ability to re-use where predicate in list and count query. improves performance
188+
query.where(constructWhere(cb, query, taskRoot, documentRoot, caseDefinitionName, advancedSearchRequest))
195189

196-
// Can't use singleResult here due to the group by issue mentioned above.
197-
val count = entityManager.createQuery(countQuery).resultList.sum()
190+
query.orderBy(constructOrderBy(query, cb, taskRoot, documentRoot, pageable.sort))
198191

199192
val pagedQuery = entityManager.createQuery(query)
200193
.setFirstResult(pageable.offset.toInt())
201194
.setMaxResults(pageable.pageSize)
202195

203-
return PageImpl(pagedQuery.resultList, pageable, count)
196+
return PageImpl(pagedQuery.resultList, pageable, count(caseDefinitionName, advancedSearchRequest))
197+
}
198+
199+
private fun count(caseDefinitionName: String, advancedSearchRequest: AdvancedSearchRequest): Long {
200+
val cb: CriteriaBuilder = entityManager.criteriaBuilder
201+
val query = cb.createQuery(Long::class.java)
202+
val taskRoot = query.from(CamundaTask::class.java)
203+
val documentRoot = query.from(JsonSchemaDocument::class.java)
204+
query.select(cb.countDistinct(taskRoot))
205+
query.where(constructWhere(cb, query, taskRoot, documentRoot, caseDefinitionName, advancedSearchRequest))
206+
return entityManager.createQuery(query).singleResult
204207
}
205208

206209
private fun constructWhere(
@@ -216,6 +219,7 @@ class CaseTaskListSearchService(
216219

217220
val assignmentFilterPredicate: Predicate = constructAssignmentFilter(advancedSearchRequest.assigneeFilter, cb, taskRoot)
218221

222+
// TODO: look into options to improve performance as with complex rules this takes quite some time to finish
219223
val searchRequestPredicate: Array<Predicate> = constructSearchCriteriaFilter(advancedSearchRequest, cb, query, taskRoot, documentRoot)
220224

221225
val where = cb.and(

0 commit comments

Comments
 (0)