Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ private Class<?> resolveProjectionType(Query.PropertyProjection projection) {

@SuppressWarnings("unchecked")
private void assignOrderBy(CriteriaQuery<?> cq, JpaQueryContext context) {
if (isCountOnly()) {
// a count returns one row whatever the order, and an ORDER BY on a column that is not aggregated fails
// on H2 and most other databases, so a count query leaves the criteria's orders out
return;
}
List<Query.Order> orders = detachedCriteria.getOrders();
if (!orders.isEmpty()) {
var jpaOrders = orders.stream()
Expand All @@ -296,6 +301,17 @@ private void assignOrderBy(CriteriaQuery<?> cq, JpaQueryContext context) {
}
}

/**
* Whether the query selects nothing but counts, as {@code count()} and {@code countDistinct()} do: such a query
* has one row, so the criteria's orders do not apply to it. A query that groups and counts keeps its orders.
*/
private boolean isCountOnly() {
List<Query.Projection> projectionList = projections.getProjectionList();
return !projectionList.isEmpty() && projectionList.stream()
.allMatch(projection -> projection instanceof Query.CountProjection ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The predicate covers count and countDistinct, so a query whose only projections are other aggregates still carries the orders and fails the same way:

c.get { projections { max('amount') }; order('amount') }

Column "CTE1_0.AMOUNT" must be in the GROUP BY list
select max(cte1_0.amount) from criteria_test_entity cte1_0 order by cte1_0.amount

That one is user-written rather than added by the framework, so it is less pressing than count(), but the rule could be "no group by and every projection is an aggregate" (count, countDistinct, max, min, sum, avg) at no extra cost. A follow-up PR is fine.

projection instanceof Query.CountDistinctProjection);
}

private void discoverAliases(List<Query.Criterion> criteria, JpaQueryContext context) {
if (criteria == null) return;
for (Query.Criterion criterion : criteria) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,28 @@ class HibernateCriteriaBuilderDirectSpec extends HibernateGormDatastoreSpec {
cleanup:
PredicateGenerator.clearCustomCriterionHandlers()
}

void "a count query leaves the criteria's orders out"() {
expect: "a count with an order counts every row instead of failing on the order by"
new DetachedCriteria(CriteriaTestEntity).build { order('amount', 'desc') }.count() == 4L
new DetachedCriteria(CriteriaTestEntity).build {
gt('amount', 15)
order('name')
}.count() == 3L

and: "a row count projection with an order returns the count"
c.list {
projections { rowCount() }
order('amount')
} == [4L]

and: "the builder's count with an order counts every row"
new HibernateCriteriaBuilder(CriteriaTestEntity, manager.hibernateDatastore.sessionFactory, manager.hibernateDatastore)
.count { order('amount', 'desc') } == 4L

and: "the order still applies to the list"
new DetachedCriteria(CriteriaTestEntity).build { order('amount', 'desc') }.list()*.name == ['D', 'C', 'B', 'A']
}
}

@Entity
Expand Down
Loading