You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/10/22 23:07:17 UTC
(grails-core) branch feat/criteria-handler-context-8.1.x updated: Hibernate 7: let a handler order by the expression a sqlProjection records under its alias
This is an automated email from the ASF dual-hosted git repository.
borinquenkid pushed a commit to branch feat/criteria-handler-context-8.1.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git
The following commit(s) were added to refs/heads/feat/criteria-handler-context-8.1.x by this push:
new 1e47f7ed5d Hibernate 7: let a handler order by the expression a sqlProjection records under its alias
1e47f7ed5d is described below
commit 1e47f7ed5d5986dbd8694e9530e108a4610878e1
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Thu Oct 22 15:11:29 2026 -0500
Hibernate 7: let a handler order by the expression a sqlProjection records under its alias
A custom order that names a sqlProjection alias could not reach that expression: the translator records it with registerSelectionAlias, and getFullyQualifiedPath and getFullyQualifiedExpression resolve property paths only. QueryPathContext now exposes getSelectionAlias(String), which JpaQueryContext already had, so an order handler orders by a projected value the way the built-in alias order does; predicate property resolution is unchanged. HibernateCriteriaBuilderSqlProjectionSpec or [...]
---
.../orm/hibernate/query/JpaQueryContext.java | 1 +
.../orm/hibernate/query/QueryPathContext.java | 7 +++++
...ibernateCriteriaBuilderSqlProjectionSpec.groovy | 35 ++++++++++++++++++++++
.../docs/src/docs/asciidoc/querying/criteria.adoc | 3 +-
4 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaQueryContext.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaQueryContext.java
index bf180c3c55..16f01501ef 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaQueryContext.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaQueryContext.java
@@ -149,6 +149,7 @@ public class JpaQueryContext implements Cloneable, QueryPathContext {
* @param alias the alias of the selection
* @return the selection, or {@code null} if no selection of this query has the alias
*/
+ @Override
public Expression<?> getSelectionAlias(String alias) {
return selectionAliases.get(alias);
}
diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/QueryPathContext.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/QueryPathContext.java
index 5758615bac..7a7a3df751 100644
--- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/QueryPathContext.java
+++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/QueryPathContext.java
@@ -46,4 +46,11 @@ public interface QueryPathContext {
* @return the expression for it: a path, or the expression an alias or projection stands for
*/
Expression<?> getFullyQualifiedExpression(String path);
+
+ /**
+ * @param alias the column alias of a {@code sqlProjection} of the query
+ * @return the projected expression recorded under that alias, so that a handler can order by it, or {@code null}
+ * when no projection of the query carries the alias
+ */
+ Expression<?> getSelectionAlias(String alias);
}
diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderSqlProjectionSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderSqlProjectionSpec.groovy
index fb0d025f13..aca33a9420 100644
--- a/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderSqlProjectionSpec.groovy
+++ b/grails-data-hibernate7/core/src/test/groovy/grails/orm/HibernateCriteriaBuilderSqlProjectionSpec.groovy
@@ -32,6 +32,8 @@ import spock.lang.Specification
import grails.gorm.annotation.Entity
import grails.gorm.transactions.Rollback
+import org.grails.orm.hibernate.query.PredicateGenerator
+import org.grails.datastore.mapping.query.Query
import org.grails.datastore.mapping.core.DatastoreUtils
import org.grails.orm.hibernate.HibernateDatastore
import org.grails.orm.hibernate.cfg.Settings
@@ -125,6 +127,31 @@ class HibernateCriteriaBuilderSqlProjectionSpec extends Specification {
rows*.toList().sort { it[0] } == [[2, 3L, 24L], [4, 1L, 9L]]
}
+ @Rollback
+ void 'an order handler orders by the expression a SQL projection records under its alias'() {
+ given: "a handler that orders by the selection an alias stands for, descending"
+ saveBoxes()
+ PredicateGenerator.registerOrderHandler(SqlProjectionAliasOrder) { query, root, cb, order, context ->
+ cb.desc(context.getSelectionAlias(order.property))
+ }
+
+ when:
+ List rows = SqlProjectionBox.createCriteria().list {
+ projections {
+ sqlProjection 'width * 10 as scaled', 'scaled', INTEGER
+ }
+ order(new SqlProjectionAliasOrder('scaled'))
+ }
+
+ then: "the rows come in descending order of the projection, and the SQL orders by the select item, not by a column"
+ rows == rows.sort(false).reverse()
+ rows.size() == SqlProjectionBox.count()
+ sqlCapture.statements.any { it.contains('select width * 10 from') && it.contains('order by 1 desc') }
+
+ cleanup:
+ PredicateGenerator.clearCustomOrderHandlers()
+ }
+
@Rollback
void 'the results are ordered by the alias of a SQL projection'() {
given:
@@ -512,3 +539,11 @@ class SqlProjectionBox {
Date packedAt
SqlProjectionShelf shelf
}
+
+/** An order that a registered handler converts, naming the column alias of a SQL projection. */
+class SqlProjectionAliasOrder extends Query.Order {
+
+ SqlProjectionAliasOrder(String alias) {
+ super(alias)
+ }
+}
diff --git a/grails-data-hibernate7/docs/src/docs/asciidoc/querying/criteria.adoc b/grails-data-hibernate7/docs/src/docs/asciidoc/querying/criteria.adoc
index 6c6d315da1..0b3000fba2 100644
--- a/grails-data-hibernate7/docs/src/docs/asciidoc/querying/criteria.adoc
+++ b/grails-data-hibernate7/docs/src/docs/asciidoc/querying/criteria.adoc
@@ -424,7 +424,8 @@ The handlers convert them. A criterion handler is given the query, its root, the
criterion and the query's `QueryPathContext`. Through the context, `getFullyQualifiedPath` resolves a property path
the way the built-in criteria do: `alias.property`, for an alias created with `createAlias`, or `association.property`
becomes a path on the join GORM created for that association, so a custom criterion and a built-in one on the
-same association restrict the same row rather than each joining the association again. An order handler is given
+same association restrict the same row rather than each joining the association again, and `getSelectionAlias`
+returns the expression a `sqlProjection` records under its column alias, so an order can sort by a projected value. An order handler is given
the query, its root, the criteria builder, the order and the context, and returns the JPA order, or `null` to leave
the order out. A query whose result is a count leaves every order out by itself, so no handler runs for it: