borinquenkid commented on code in PR #16696:
URL: https://github.com/apache/grails-core/pull/16696#discussion_r4350978568
##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaCriteriaQueryCreator.groovy:
##########
@@ -287,6 +287,16 @@ class JpaCriteriaQueryCreator<T> {
if (!orders.isEmpty()) {
List<jakarta.persistence.criteria.Order> jpaOrders = [];
for (Query.Order order : orders) {
+ PredicateGenerator.OrderHandler customHandler =
PredicateGenerator.orderHandler(order.class);
+ if (customHandler != null) {
+ // the handler of a Query.Order subclass builds its JPA
order, or leaves the order out
+ jakarta.persistence.criteria.Order customOrder =
+ customHandler.handle(cq, context.root,
criteriaBuilder, order, context);
Review Comment:
Split off as #16731 against 8.0.x: a query whose only projections are counts
leaves the criteria's orders out, so `count()` with an `order` works for
property orders too. Once it lands, this PR drops the null-on-count advice from
the docs and the `OrderHandler` javadoc and adds a `count()` case with an order
handler.
##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.groovy:
##########
@@ -111,20 +178,113 @@ class PredicateGenerator {
}
+ /**
+ * Extension point for ordering a criteria query by something other than a
property. Register a handler for a
+ * {@link Query.Order} subclass to have it converted to a JPA {@link
jakarta.persistence.criteria.Order}, in
+ * the place the order has among the query's orders. The order reaches the
query through
+ * {@code order(Query.Order)} of the criteria builder or of a detached
criteria.
+ *
+ * <pre>
+ * class ByRandom extends Query.Order {
+ * ByRandom() { super('random') }
+ * }
+ * PredicateGenerator.registerOrderHandler(ByRandom) { query, root,
cb, order, context ->
+ * cb.asc(cb.function('random', Double))
+ * }
+ * </pre>
+ *
+ * @since 10.0
+ */
+ @FunctionalInterface
+ static interface OrderHandler {
+
+ /**
+ * @param criteriaQuery the query, whose result type tells a query for
the entity from a projection
+ * @param root the root of the query
+ * @param criteriaBuilder the criteria builder
+ * @param order the order to convert
+ * @param context the context of the query: its root, its joins and
its aliases
+ * @return the JPA order, or {@code null} to leave this order out of
the query
+ */
+ jakarta.persistence.criteria.Order handle(
+ CriteriaQuery<?> criteriaQuery,
+ From<?, ?> root,
+ HibernateCriteriaBuilder criteriaBuilder,
+ Query.Order order,
+ JpaQueryContext context
+ )
+
+ }
+
+ /**
+ * SPI for contributing an {@link OrderHandler} via {@link ServiceLoader}.
Register an implementation in
+ * {@code
META-INF/services/org.grails.orm.hibernate.query.PredicateGenerator$OrderHandlerProvider};
it is
+ * discovered on first query execution, like a {@link
CriterionHandlerProvider}.
+ *
+ * @since 10.0
+ */
+ static interface OrderHandlerProvider {
+
+ Class<? extends Query.Order> orderType()
+
+ OrderHandler orderHandler()
+
+ }
+
public static final Map<Class<? extends Query.Criterion>,
CriterionHandler> CUSTOM_HANDLERS =
new ConcurrentHashMap<>()
+ public static final Map<Class<? extends Query.Order>, OrderHandler>
CUSTOM_ORDER_HANDLERS =
+ new ConcurrentHashMap<>()
+
private static final AtomicBoolean SERVICE_LOADERS_INITIALIZED = new
AtomicBoolean(false)
private static void loadServiceProviders() {
if (SERVICE_LOADERS_INITIALIZED.compareAndSet(false, true)) {
Review Comment:
Fixed in b39597e052: `loadServiceProviders` now loads under a lock and
publishes the flag only after both `ServiceLoader` loops, so a caller that
arrives during the discovery waits for it; `putIfAbsent` keeps a handler
registered in code ahead of a discovered one. `OrderHandlerDiscoverySpec`
blocks one thread inside a provider found through `META-INF/services` and shows
a second lookup waiting and then receiving the handler; it fails with the
previous flag.
##########
.github/workflows/gradle.yml:
##########
@@ -19,6 +19,8 @@ on:
branches:
- '[0-9]+.[0-9]+.x'
pull_request:
+ # CHANGEME FOR ACCEPTANCE: PR triggers disabled on this stacked branch;
revert before merge
+ branches: ['CHANGEME-FOR-ACCEPTANCE']
Review Comment:
Restored: the base merge of 2026-10-22 brought the policy workflows (#16673)
and the 13 trigger blocks are identical to the base branch again; the
placeholder is gone from this branch.
##########
grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.groovy:
##########
@@ -84,6 +85,72 @@ class PredicateGenerator {
Query.Criterion criterion
)
+ /**
+ * Converts a criterion with the context of the query it belongs to.
The context resolves a property path
+ * the way the built-in criteria do: {@link
JpaQueryContext#getFullyQualifiedPath(String)} turns
+ * {@code alias.property}, for an alias created with {@code
createAlias}, or {@code association.property}
+ * into a path on the join GORM created for that association, so a
custom criterion and a GORM criterion on
+ * the same association restrict the same row instead of each joining
the association again. This default
+ * ignores the context and calls {@link #handle(AbstractQuery, From,
HibernateCriteriaBuilder, Query.Criterion)};
+ * a handler that needs the context implements {@link
ContextualCriterionHandler}.
+ *
+ * @param criteriaQuery the query or subquery the predicate is for
+ * @param root the root of that query
+ * @param criteriaBuilder the criteria builder
+ * @param criterion the criterion to convert
+ * @param context the context of the query: its root, its joins and
its aliases
+ * @return the predicate, or {@code null} to add none
+ * @since 10.0
+ */
+ default Predicate handle(
+ AbstractQuery<?> criteriaQuery,
+ From<?, ?> root,
+ HibernateCriteriaBuilder criteriaBuilder,
+ Query.Criterion criterion,
+ JpaQueryContext context
Review Comment:
Done in b39597e052: handlers now receive `QueryPathContext`, a small
interface with `getRoot()`, `getFullyQualifiedPath(String)` and
`getFullyQualifiedExpression(String)`, which `JpaQueryContext` implements.
`CriterionHandler`, `ContextualCriterionHandler`, `OrderHandler` and the guide
depend on the interface only, so the context class stays internal.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]