This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch 8.0.x-hibernate7
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 337f79fff9022f4b43a3253ed895e938119ee6e0
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Fri Feb 20 09:52:44 2026 -0600

    Refactor PredicateGenerator for readability and document clause hierarchy
---
 grails-data-hibernate7/core/CLAUSE_HIERARCHY.md    |  62 +++
 .../orm/hibernate/query/PredicateGenerator.java    | 491 ++++++++++-----------
 2 files changed, 292 insertions(+), 261 deletions(-)

diff --git a/grails-data-hibernate7/core/CLAUSE_HIERARCHY.md 
b/grails-data-hibernate7/core/CLAUSE_HIERARCHY.md
new file mode 100644
index 0000000000..26bc600dfc
--- /dev/null
+++ b/grails-data-hibernate7/core/CLAUSE_HIERARCHY.md
@@ -0,0 +1,62 @@
+# Criteria and Projection Hierarchy in PredicateGenerator
+
+| Class used in `instanceof` | Class Hierarchy (from `Query.java` or GORM) |
+| :--- | :--- |
+| `Query.Junction` | `Query.Junction` -> `Query.Criterion` |
+| `Query.Disjunction` | `Query.Disjunction` -> `Query.Junction` -> 
`Query.Criterion` |
+| `Query.Conjunction` | `Query.Conjunction` -> `Query.Junction` -> 
`Query.Criterion` |
+| `Query.Negation` | `Query.Negation` -> `Query.Junction` -> `Query.Criterion` 
|
+| `Query.DistinctProjection` | `Query.DistinctProjection` -> 
`Query.Projection` |
+| `DetachedAssociationCriteria` | `DetachedAssociationCriteria` -> 
`AbstractDetachedCriteria` -> `Criteria` |
+| `Query.PropertyCriterion` | `Query.PropertyCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.Equals` | `Query.Equals` -> `Query.PropertyCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.NotEquals` | `Query.NotEquals` -> `Query.PropertyCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.IdEquals` | `Query.IdEquals` -> `Query.Criterion` |
+| `Query.GreaterThan` | `Query.GreaterThan` -> `Query.PropertyCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.GreaterThanEquals` | `Query.GreaterThanEquals` -> 
`Query.PropertyCriterion` -> `Query.PropertyNameCriterion` -> `Query.Criterion` 
|
+| `Query.LessThan` | `Query.LessThan` -> `Query.PropertyCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.LessThanEquals` | `Query.LessThanEquals` -> `Query.PropertyCriterion` 
-> `Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.SizeEquals` | `Query.SizeEquals` -> `Query.PropertyCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.SizeNotEquals` | `Query.SizeNotEquals` -> `Query.PropertyCriterion` 
-> `Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.SizeGreaterThan` | `Query.SizeGreaterThan` -> 
`Query.PropertyCriterion` -> `Query.PropertyNameCriterion` -> `Query.Criterion` 
|
+| `Query.SizeGreaterThanEquals` | `Query.SizeGreaterThanEquals` -> 
`Query.PropertyCriterion` -> `Query.PropertyNameCriterion` -> `Query.Criterion` 
|
+| `Query.SizeLessThan` | `Query.SizeLessThan` -> `Query.PropertyCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.SizeLessThanEquals` | `Query.SizeLessThanEquals` -> 
`Query.PropertyCriterion` -> `Query.PropertyNameCriterion` -> `Query.Criterion` 
|
+| `Query.Between` | `Query.Between` -> `Query.PropertyCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.ILike` | `Query.ILike` -> `Query.Like` -> `Query.PropertyCriterion` 
-> `Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.RLike` | `Query.RLike` -> `Query.Like` -> `Query.PropertyCriterion` 
-> `Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.Like` | `Query.Like` -> `Query.PropertyCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.In` | `Query.In` -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.NotIn` | `Query.NotIn` -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.SubqueryCriterion` | `Query.SubqueryCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.GreaterThanEqualsAll` | `Query.GreaterThanEqualsAll` -> 
`Query.SubqueryCriterion` -> `Query.PropertyNameCriterion` -> `Query.Criterion` 
|
+| `Query.GreaterThanAll` | `Query.GreaterThanAll` -> `Query.SubqueryCriterion` 
-> `Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.LessThanEqualsAll` | `Query.LessThanEqualsAll` -> 
`Query.SubqueryCriterion` -> `Query.PropertyNameCriterion` -> `Query.Criterion` 
|
+| `Query.LessThanAll` | `Query.LessThanAll` -> `Query.SubqueryCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.EqualsAll` | `Query.EqualsAll` -> `Query.SubqueryCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.GreaterThanEqualsSome` | `Query.GreaterThanEqualsSome` -> 
`Query.SubqueryCriterion` -> `Query.PropertyNameCriterion` -> `Query.Criterion` 
|
+| `Query.GreaterThanSome` | `Query.GreaterThanSome` -> 
`Query.SubqueryCriterion` -> `Query.PropertyNameCriterion` -> `Query.Criterion` 
|
+| `Query.LessThanEqualsSome` | `Query.LessThanEqualsSome` -> 
`Query.SubqueryCriterion` -> `Query.PropertyNameCriterion` -> `Query.Criterion` 
|
+| `Query.LessThanSome` | `Query.LessThanSome` -> `Query.SubqueryCriterion` -> 
`Query.PropertyNameCriterion` -> `Query.Criterion` |
+| `Query.IsNull` | `Query.IsNull` -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.IsNotNull` | `Query.IsNotNull` -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.IsEmpty` | `Query.IsEmpty` -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.IsNotEmpty` | `Query.IsNotEmpty` -> `Query.PropertyNameCriterion" -> 
`Query.Criterion` |
+| `Query.EqualsProperty` | `Query.EqualsProperty` -> 
`Query.PropertyComparisonCriterion" -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.NotEqualsProperty` | `Query.NotEqualsProperty` -> 
`Query.PropertyComparisonCriterion" -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.LessThanEqualsProperty` | `Query.LessThanEqualsProperty` -> 
`Query.PropertyComparisonCriterion" -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.LessThanProperty` | `Query.LessThanProperty` -> 
`Query.PropertyComparisonCriterion" -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.GreaterThanEqualsProperty` | `Query.GreaterThanEqualsProperty" -> 
`Query.PropertyComparisonCriterion" -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.GreaterThanProperty` | `Query.GreaterThanProperty" -> 
`Query.PropertyComparisonCriterion" -> `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.Exists` | `Query.Exists` -> `Query.Criterion` |
+| `Query.NotExists` | `Query.NotExists` -> `Query.Criterion` |
+| `Query.PropertyNameCriterion` | `Query.PropertyNameCriterion` -> 
`Query.Criterion` |
+| `Query.PropertyProjection` | `Query.PropertyProjection` -> 
`Query.Projection` |
+| `Query.DistinctPropertyProjection` | `Query.DistinctPropertyProjection` -> 
`Query.PropertyProjection` -> `Query.Projection` |
+| `Query.IdProjection` | `Query.IdProjection` -> `Query.Projection` |
+| `Query.CountProjection` | `Query.CountProjection` -> `Query.Projection` |
+| `Query.CountDistinctProjection` | `Query.CountDistinctProjection` -> 
`Query.PropertyProjection` -> `Query.Projection` |
+| `Query.MaxProjection` | `Query.MaxProjection` -> `Query.PropertyProjection` 
-> `Query.Projection` |
+| `Query.MinProjection` | `Query.MinProjection` -> `Query.PropertyProjection` 
-> `Query.Projection` |
+| `Query.AvgProjection` | `Query.AvgProjection` -> `Query.PropertyProjection` 
-> `Query.Projection` |
+| `Query.SumProjection` | `Query.SumProjection` -> `Query.PropertyProjection` 
-> `Query.Projection` |
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
index 40826b4f27..aafd7bccf9 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java
@@ -19,7 +19,6 @@ import org.grails.datastore.mapping.model.types.Association;
 import org.grails.datastore.mapping.query.Query;
 import org.grails.datastore.mapping.query.api.QueryableCriteria;
 import org.hibernate.query.criteria.HibernateCriteriaBuilder;
-import org.hibernate.query.criteria.JpaFunction;
 import org.hibernate.query.criteria.JpaInPredicate;
 import org.hibernate.query.criteria.JpaPredicate;
 import org.hibernate.query.sqm.tree.domain.SqmPath;
@@ -27,280 +26,264 @@ import 
org.hibernate.query.sqm.tree.predicate.SqmInListPredicate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.OffsetDateTime;
-import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Date;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Stream;
 
-
 @Slf4j
 public class PredicateGenerator {
     private static final Logger log = 
LoggerFactory.getLogger(PredicateGenerator.class);
 
     public Predicate[] getPredicates(HibernateCriteriaBuilder cb,
-                                            CriteriaQuery criteriaQuery,
-                                            From root_,
-                                            List criteriaList, JpaFromProvider 
fromsByProvider, PersistentEntity entity) {
+                                     CriteriaQuery criteriaQuery,
+                                     From root_,
+                                     List criteriaList, 
+                                     JpaFromProvider fromsByProvider, 
+                                     PersistentEntity entity) {
 
+        List<Predicate> list = ((List<Object>) criteriaList).stream()
+                .map(criterion -> handleCriterion(cb, criteriaQuery, root_, 
fromsByProvider, entity, criterion))
+                .filter(Objects::nonNull)
+                .toList();
 
-        List<Predicate> list = criteriaList.stream().
-                map((Object criterion) -> {
-                    if (criterion instanceof Query.Junction junction) {
-                        var criterionList = junction.getCriteria();
-                        var predicates = (Predicate[])this.getPredicates(cb, 
criteriaQuery, root_, (List)criterionList, fromsByProvider, entity);
-                        if (junction instanceof Query.Disjunction) {
-                            return cb.or(predicates);
-                        } else if (junction instanceof Query.Conjunction) {
-                            return cb.and(predicates);
-                        } else if (junction instanceof Query.Negation) {
-                            if (predicates.length != 1) {
-                                log.error("Must have a single predicate behind 
a not");
-                                throw new RuntimeException("Must have a single 
predicate behind a not");
-                            }
-                            return cb.not(predicates[0]);
-                        }
-                    } else if (criterion instanceof Query.DistinctProjection) {
-                        // this returns always true
-                        return cb.conjunction();
-                    } else if (criterion instanceof 
DetachedAssociationCriteria<?> c) {
-                        Join child = root_.join(c.getAssociationPath(), 
JoinType.LEFT);
-                        List<Query.Criterion> criterionList = c.getCriteria();
-                        JpaFromProvider childTablesByName = (JpaFromProvider) 
fromsByProvider.clone();
-                        childTablesByName.put("root", child);
-                        return cb.and((Predicate[])this.getPredicates(cb, 
criteriaQuery, child, (List)criterionList, childTablesByName, entity));
-                    } else if (criterion instanceof Query.PropertyCriterion 
pc) {
-                        var fullyQualifiedPath = 
fromsByProvider.getFullyQualifiedPath(pc.getProperty());
-                        
-                        if (criterion instanceof Query.NotIn c) {
-                            var queryableCriteria = 
getQueryableCriteriaFromInCriteria((Query.Criterion)criterion);
-                            if (Objects.nonNull(queryableCriteria)) {
-                                CriteriaBuilder.In value = 
getQueryableCriteriaValue(cb, criteriaQuery, fromsByProvider, entity, 
(Query.PropertyNameCriterion)criterion, queryableCriteria);
-                                return cb.not(value);
-                            } else if (Objects.nonNull(c.getSubquery())
-                                    && 
!c.getSubquery().getProjections().isEmpty()
-                                    && c.getSubquery().getProjections().get(0) 
instanceof Query.PropertyProjection
-                            ) {
-                                Subquery subquery2 = 
criteriaQuery.subquery(Number.class);
-                                Root from2 = 
subquery2.from(c.getValue().getPersistentEntity().getJavaClass());
-                                List subCriteria2 = c.getValue().getCriteria();
-                                Query.PropertyProjection projection = 
(Query.PropertyProjection) c.getSubquery().getProjections().get(0);
-                                boolean distinct = projection instanceof 
Query.DistinctPropertyProjection;
-                                JpaFromProvider newMap2 = (JpaFromProvider) 
fromsByProvider.clone();
-                                Predicate[] predicates2 = 
(Predicate[])this.getPredicates(cb, criteriaQuery, from2, (List)subCriteria2, 
newMap2, entity);
-                                
subquery2.select(from2.get(projection.getPropertyName())).distinct(distinct).where(cb.and(predicates2));
-                                return 
cb.not(cb.in(fullyQualifiedPath).value(subquery2));
-                            } else if ( Objects.nonNull(c.getSubquery())
-                                    && 
!c.getSubquery().getProjections().isEmpty()
-                                    && c.getSubquery().getProjections().get(0) 
instanceof Query.IdProjection
-                            ) {
-                                Subquery subquery2 = 
criteriaQuery.subquery(Number.class);
-                                Root from2 = 
subquery2.from(c.getValue().getPersistentEntity().getJavaClass());
-                                List subCriteria2 = c.getValue().getCriteria();
-                                JpaFromProvider newMap2 = (JpaFromProvider) 
fromsByProvider.clone();
-                                Predicate[] predicates2 = 
(Predicate[])this.getPredicates(cb, criteriaQuery, from2, (List)subCriteria2, 
newMap2, entity);
-                                
subquery2.select(from2).where(cb.and(predicates2));
-                                return 
cb.not(cb.in(root_.get("id")).value(subquery2));
-                            } else {
-                                return cb.not(cb.in(fullyQualifiedPath, 
c.getValue()));
-                            }
-                        } else if (criterion instanceof 
Query.SubqueryCriterion c) {
-                            Subquery subquery = 
criteriaQuery.subquery(Number.class);
-                            Root from = 
subquery.from(c.getValue().getPersistentEntity().getJavaClass());
-                            List subCriteria = c.getValue().getCriteria();
-                            JpaFromProvider newMap = (JpaFromProvider) 
fromsByProvider.clone();
-                            newMap.put("root", from);
-                            Predicate[] predicates = 
(Predicate[])this.getPredicates(cb, criteriaQuery, from, (List)subCriteria, 
newMap, entity);
-                            if (c instanceof Query.GreaterThanEqualsAll sc) {
-                                
subquery.select(cb.max(from.get(c.getProperty()))).where(cb.and(predicates));
-                                return 
cb.greaterThanOrEqualTo(fromsByProvider.getFullyQualifiedPath(sc.getProperty()),
 subquery);
-                            } else if (c instanceof Query.GreaterThanAll sc) {
-                                
subquery.select(cb.max(from.get(c.getProperty()))).where(cb.and(predicates));
-                                return 
cb.greaterThan(fromsByProvider.getFullyQualifiedPath(sc.getProperty()), 
subquery);
-                            } else if (c instanceof Query.LessThanEqualsAll 
sc) {
-                                
subquery.select(cb.min(from.get(c.getProperty()))).where(cb.and(predicates));
-                                return 
cb.lessThanOrEqualTo(fromsByProvider.getFullyQualifiedPath(sc.getProperty()), 
subquery);
-                            } else if (c instanceof Query.LessThanAll sc) {
-                                
subquery.select(cb.min(from.get(c.getProperty()))).where(cb.and(predicates));
-                                return 
cb.lessThan(fromsByProvider.getFullyQualifiedPath(sc.getProperty()), subquery);
-                            } else if (c instanceof Query.EqualsAll sc) {
-                                
subquery.select(from.get(c.getProperty())).where(cb.and(predicates));
-                                return 
cb.equal(fromsByProvider.getFullyQualifiedPath(sc.getProperty()), subquery);
-                            } else if (c instanceof 
Query.GreaterThanEqualsSome sc) {
-                                
subquery.select(cb.max(from.get(c.getProperty()))).where(cb.or(predicates));
-                                return 
cb.greaterThanOrEqualTo(fromsByProvider.getFullyQualifiedPath(sc.getProperty()),
 subquery);
-                            } else if (c instanceof Query.GreaterThanSome sc) {
-                                
subquery.select(cb.max(from.get(c.getProperty()))).where(cb.or(predicates));
-                                return 
cb.greaterThan(fromsByProvider.getFullyQualifiedPath(sc.getProperty()), 
subquery);
-                            } else if (c instanceof Query.LessThanEqualsSome 
sc) {
-                                
subquery.select(cb.min(from.get(c.getProperty()))).where(cb.or(predicates));
-                                return 
cb.lessThanOrEqualTo(fromsByProvider.getFullyQualifiedPath(sc.getProperty()), 
subquery);
-                            } else if (c instanceof Query.LessThanSome sc) {
-                                
subquery.select(cb.min(from.get(c.getProperty()))).where(cb.or(predicates));
-                                return 
cb.lessThan(fromsByProvider.getFullyQualifiedPath(sc.getProperty()), subquery);
-                            }
-                        } else if (criterion instanceof Query.In c) {
-                            var queryableCriteria = 
getQueryableCriteriaFromInCriteria((Query.Criterion)criterion);
-                            if (Objects.nonNull(queryableCriteria)) {
-                                CriteriaBuilder.In value = 
getQueryableCriteriaValue(cb, criteriaQuery, fromsByProvider, entity, 
(Query.PropertyNameCriterion)criterion, queryableCriteria);
-                                return value;
-                            } else if (!c.getValues().isEmpty()) {
-                                boolean areGormEntities = 
c.getValues().stream().allMatch(GormEntity.class::isInstance);
-                                if (areGormEntities) {
-                                    List<GormEntity> gormEntities = new 
ArrayList<>(c.getValues());
-                                    Path id = 
criteriaQuery.from(gormEntities.get(0).getClass()).get("id");
-                                    Collection newValues = 
gormEntities.stream().map(GormEntity::ident).toList();
-                                    return cb.in(id, newValues);
-                                }
-                                return cb.in(fullyQualifiedPath, 
c.getValues());
-                            }
-                        } else if (criterion instanceof Query.ILike c) {
-                            return cb.ilike(fullyQualifiedPath, 
c.getValue().toString());
-                        } else if (criterion instanceof Query.RLike c) {
-                            String pattern = c.getPattern();
-                            pattern = pattern.replaceAll("^/|/$", "");
-                            return cb.equal(cb.function(
-                                    GrailsRLikeFunctionContributor.RLIKE,      
     // The name we registered
-                                    Boolean.class,     // Expected return type
-                                    fullyQualifiedPath, // The property path
-                                    cb.literal(pattern)), true);
-                        } else if (criterion instanceof Query.Like c) {
-                            return cb.like(fullyQualifiedPath, 
c.getValue().toString());
-                        } else if (criterion instanceof Query.Equals c) {
-                            return cb.equal(fullyQualifiedPath, c.getValue());
-                        } else if (criterion instanceof Query.NotEquals c) {
-                            var notEqualToValue = 
cb.notEqual(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
c.getValue());
-                            var isNull = cb.isNull(fullyQualifiedPath);
-                            return cb.or(notEqualToValue, isNull);
-                        } else if (criterion instanceof Query.IdEquals c) {
-                            return cb.equal(root_.get("id"), c.getValue());
-                        } else if (criterion instanceof Query.GreaterThan c) {
-                            return cb.gt(fullyQualifiedPath, 
getNumericValue(c));
-                        } else if (criterion instanceof 
Query.GreaterThanEquals c) {
-                            return cb.ge(fullyQualifiedPath, 
getNumericValue(c));
-                        } else if (criterion instanceof Query.LessThan c) {
-                            return cb.lt(fullyQualifiedPath, 
getNumericValue(c));
-                        } else if (criterion instanceof Query.LessThanEquals 
c) {
-                            return cb.le(fullyQualifiedPath, 
getNumericValue(c));
-                        } else if (criterion instanceof Query.SizeEquals c) {
-                            return cb.equal(cb.size(fullyQualifiedPath), 
c.getValue());
-                        } else if (criterion instanceof Query.SizeNotEquals c) 
{
-                            return cb.notEqual(cb.size(fullyQualifiedPath), 
c.getValue());
-                        } else if (criterion instanceof Query.SizeGreaterThan 
c) {
-                            return cb.gt(cb.size(fullyQualifiedPath), 
getNumericValue(c));
-                        } else if (criterion instanceof 
Query.SizeGreaterThanEquals c) {
-                            return cb.ge(cb.size(fullyQualifiedPath), 
getNumericValue(c));
-                        } else if (criterion instanceof Query.SizeLessThan c) {
-                            return cb.lt(cb.size(fullyQualifiedPath), 
getNumericValue(c));
-                        } else if (criterion instanceof 
Query.SizeLessThanEquals c) {
-                            return cb.le(cb.size(fullyQualifiedPath), 
getNumericValue(c));
-                        } else if (criterion instanceof Query.Between c) {
-                            return cb.between(fullyQualifiedPath, (Comparable) 
c.getFrom(), (Comparable) c.getTo());
-                        }
-                    } else if (criterion instanceof 
Query.PropertyComparisonCriterion c) {
-                        if (criterion instanceof Query.EqualsProperty) {
-                            return 
cb.equal(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
root_.get(c.getOtherProperty()));
-                        } else if (criterion instanceof 
Query.NotEqualsProperty) {
-                            return 
cb.notEqual(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
root_.get(c.getOtherProperty()));
-                        } else if (criterion instanceof 
Query.LessThanEqualsProperty) {
-                            return 
cb.le(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
root_.get(c.getOtherProperty()));
-                        } else if (criterion instanceof 
Query.LessThanProperty) {
-                            return 
cb.lt(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
root_.get(c.getOtherProperty()));
-                        } else if (criterion instanceof 
Query.GreaterThanEqualsProperty) {
-                            return 
cb.ge(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
root_.get(c.getOtherProperty()));
-                        } else if (criterion instanceof 
Query.GreaterThanProperty) {
-                            return 
cb.gt(fromsByProvider.getFullyQualifiedPath(c.getProperty()), 
root_.get(c.getOtherProperty()));
-                        }
-                    } else if (criterion instanceof 
Query.PropertyNameCriterion c) {
-                        if (criterion instanceof Query.IsNull) {
-                            return 
cb.isNull(fromsByProvider.getFullyQualifiedPath(c.getProperty()));
-                        } else if (criterion instanceof Query.IsNotNull) {
-                            return 
cb.isNotNull(fromsByProvider.getFullyQualifiedPath(c.getProperty()));
-                        } else if (criterion instanceof Query.IsEmpty) {
-                            return 
cb.isEmpty(fromsByProvider.getFullyQualifiedPath(c.getProperty()));
-                        } else if (criterion instanceof Query.IsNotEmpty) {
-                            return 
cb.isNotEmpty(fromsByProvider.getFullyQualifiedPath(c.getProperty()));
-                        }
-                    } else if (criterion instanceof Query.Exists c) {
-                        Subquery subquery = 
criteriaQuery.subquery(Integer.class);
-                        PersistentEntity childPersistentEntity = 
c.getSubquery().getPersistentEntity();
-                        Root subRoot = 
subquery.from(childPersistentEntity.getJavaClass());
+        if (list.isEmpty()) {
+            list = List.of(cb.equal(cb.literal(1), cb.literal(1)));
+        }
+        return list.toArray(new Predicate[0]);
+    }
 
-                        JpaFromProvider newMap = (JpaFromProvider) 
fromsByProvider.clone();
-                        newMap.put("root", subRoot);
-                        var predicates = (Predicate[])this.getPredicates(cb, 
criteriaQuery, subRoot, (List)c.getSubquery().getCriteria(), newMap, entity);
+    private Predicate handleCriterion(HibernateCriteriaBuilder cb, 
CriteriaQuery criteriaQuery, From root_, JpaFromProvider fromsByProvider, 
PersistentEntity entity, Object criterion) {
+        if (criterion instanceof Query.Junction junction) {
+            return handleJunction(cb, criteriaQuery, root_, fromsByProvider, 
entity, junction);
+        } else if (criterion instanceof Query.DistinctProjection) {
+            return cb.conjunction();
+        } else if (criterion instanceof DetachedAssociationCriteria<?> c) {
+            return handleAssociationCriteria(cb, criteriaQuery, root_, 
fromsByProvider, entity, c);
+        } else if (criterion instanceof Query.PropertyCriterion pc) {
+            return handlePropertyCriterion(cb, criteriaQuery, root_, 
fromsByProvider, entity, pc);
+        } else if (criterion instanceof Query.PropertyComparisonCriterion c) {
+            return handlePropertyComparisonCriterion(cb, fromsByProvider, 
root_, c);
+        } else if (criterion instanceof Query.PropertyNameCriterion c) {
+            return handlePropertyNameCriterion(cb, fromsByProvider, c);
+        } else if (criterion instanceof Query.Exists c) {
+            return handleExists(cb, criteriaQuery, root_, fromsByProvider, 
entity, c);
+        } else if (criterion instanceof Query.NotExists c) {
+            return cb.not(handleExists(cb, criteriaQuery, root_, 
fromsByProvider, entity, new Query.Exists(c.getSubquery())));
+        }
+        throw new IllegalArgumentException("Unsupported criterion: " + 
criterion);
+    }
 
-                        var existsPredicate = getExistsPredicate(cb, root_, 
childPersistentEntity, subRoot);
-                        Predicate[] allPredicates = Stream.concat(
-                                Arrays.stream(predicates),
-                                Stream.of(existsPredicate)
-                        ).toArray(Predicate[]::new);
+    private Predicate handleJunction(HibernateCriteriaBuilder cb, 
CriteriaQuery criteriaQuery, From root_, JpaFromProvider fromsByProvider, 
PersistentEntity entity, Query.Junction junction) {
+        var predicates = getPredicates(cb, criteriaQuery, root_, 
junction.getCriteria(), fromsByProvider, entity);
+        if (junction instanceof Query.Disjunction) {
+            return cb.or(predicates);
+        } else if (junction instanceof Query.Conjunction) {
+            return cb.and(predicates);
+        } else if (junction instanceof Query.Negation) {
+            if (predicates.length != 1) {
+                log.error("Must have a single predicate behind a not");
+                throw new RuntimeException("Must have a single predicate 
behind a not");
+            }
+            return cb.not(predicates[0]);
+        }
+        return null;
+    }
 
-                        
subquery.select(cb.literal(1)).where(cb.and(allPredicates));
-                        JpaPredicate exists = cb.exists(subquery);
-                        return exists;
-                    } else if (criterion instanceof Query.NotExists c) {
-                        Subquery subquery = 
criteriaQuery.subquery(Integer.class);
-                        PersistentEntity childPersistentEntity = 
c.getSubquery().getPersistentEntity();
-                        Root subRoot = 
subquery.from(childPersistentEntity.getJavaClass());
+    private Predicate handleAssociationCriteria(HibernateCriteriaBuilder cb, 
CriteriaQuery criteriaQuery, From root_, JpaFromProvider fromsByProvider, 
PersistentEntity entity, DetachedAssociationCriteria<?> c) {
+        Join child = root_.join(c.getAssociationPath(), JoinType.LEFT);
+        JpaFromProvider childTablesByName = (JpaFromProvider) 
fromsByProvider.clone();
+        childTablesByName.put("root", child);
+        return cb.and(getPredicates(cb, criteriaQuery, child, c.getCriteria(), 
childTablesByName, entity));
+    }
 
-                        JpaFromProvider newMap = (JpaFromProvider) 
fromsByProvider.clone();
-                        newMap.put("root", subRoot);
-                        var predicates = (Predicate[])this.getPredicates(cb, 
criteriaQuery, subRoot, (List)c.getSubquery().getCriteria(), newMap, entity);
+    private Predicate handlePropertyCriterion(HibernateCriteriaBuilder cb, 
CriteriaQuery criteriaQuery, From root_, JpaFromProvider fromsByProvider, 
PersistentEntity entity, Query.PropertyCriterion pc) {
+        var fullyQualifiedPath = 
fromsByProvider.getFullyQualifiedPath(pc.getProperty());
 
-                        var existsPredicate = getExistsPredicate(cb, root_, 
childPersistentEntity, subRoot);
-                        Predicate[] allPredicates = Stream.concat(
-                                Arrays.stream(predicates),
-                                Stream.of(existsPredicate)
-                        ).toArray(Predicate[]::new);
+        if (pc instanceof Query.NotIn c) {
+            return handleNotIn(cb, criteriaQuery, fromsByProvider, entity, c, 
fullyQualifiedPath);
+        } else if (pc instanceof Query.SubqueryCriterion c) {
+            return handleSubqueryCriterion(cb, criteriaQuery, fromsByProvider, 
entity, c);
+        } else if (pc instanceof Query.In c) {
+            return handleIn(cb, criteriaQuery, fromsByProvider, entity, c, 
fullyQualifiedPath);
+        } else if (pc instanceof Query.ILike c) {
+            return cb.ilike(fullyQualifiedPath, c.getValue().toString());
+        } else if (pc instanceof Query.RLike c) {
+            return handleRLike(cb, fullyQualifiedPath, c);
+        } else if (pc instanceof Query.Like c) {
+            return cb.like(fullyQualifiedPath, c.getValue().toString());
+        } else if (pc instanceof Query.Equals c) {
+            return cb.equal(fullyQualifiedPath, c.getValue());
+        } else if (pc instanceof Query.NotEquals c) {
+            return cb.or(cb.notEqual(fullyQualifiedPath, c.getValue()), 
cb.isNull(fullyQualifiedPath));
+        } else if (pc instanceof Query.IdEquals c) {
+            return cb.equal(root_.get("id"), c.getValue());
+        } else if (pc instanceof Query.GreaterThan c) {
+            return cb.gt(fullyQualifiedPath, getNumericValue(c));
+        } else if (pc instanceof Query.GreaterThanEquals c) {
+            return cb.ge(fullyQualifiedPath, getNumericValue(c));
+        } else if (pc instanceof Query.LessThan c) {
+            return cb.lt(fullyQualifiedPath, getNumericValue(c));
+        } else if (pc instanceof Query.LessThanEquals c) {
+            return cb.le(fullyQualifiedPath, getNumericValue(c));
+        } else if (pc instanceof Query.SizeEquals c) {
+            return cb.equal(cb.size(fullyQualifiedPath), c.getValue());
+        } else if (pc instanceof Query.SizeNotEquals c) {
+            return cb.notEqual(cb.size(fullyQualifiedPath), c.getValue());
+        } else if (pc instanceof Query.SizeGreaterThan c) {
+            return cb.gt(cb.size(fullyQualifiedPath), getNumericValue(c));
+        } else if (pc instanceof Query.SizeGreaterThanEquals c) {
+            return cb.ge(cb.size(fullyQualifiedPath), getNumericValue(c));
+        } else if (pc instanceof Query.SizeLessThan c) {
+            return cb.lt(cb.size(fullyQualifiedPath), getNumericValue(c));
+        } else if (pc instanceof Query.SizeLessThanEquals c) {
+            return cb.le(cb.size(fullyQualifiedPath), getNumericValue(c));
+        } else if (pc instanceof Query.Between c) {
+            return cb.between(fullyQualifiedPath, (Comparable) c.getFrom(), 
(Comparable) c.getTo());
+        }
+        return null;
+    }
 
-                        
subquery.select(cb.literal(1)).where(cb.and(allPredicates));
-                        JpaPredicate exists = cb.exists(subquery);
-                        return cb.not(exists);
-                    }
+    private Predicate handleNotIn(HibernateCriteriaBuilder cb, CriteriaQuery 
criteriaQuery, JpaFromProvider fromsByProvider, PersistentEntity entity, 
Query.NotIn c, Path fullyQualifiedPath) {
+        var queryableCriteria = getQueryableCriteriaFromInCriteria(c);
+        if (Objects.nonNull(queryableCriteria)) {
+            return cb.not(getQueryableCriteriaValue(cb, criteriaQuery, 
fromsByProvider, entity, c, queryableCriteria));
+        } else if (Objects.nonNull(c.getSubquery()) && 
!c.getSubquery().getProjections().isEmpty()) {
+            Subquery subquery2 = criteriaQuery.subquery(Number.class);
+            Root from2 = 
subquery2.from(c.getValue().getPersistentEntity().getJavaClass());
+            JpaFromProvider newMap2 = (JpaFromProvider) 
fromsByProvider.clone();
+            var projection = c.getSubquery().getProjections().get(0);
+            if (projection instanceof Query.PropertyProjection pp) {
+                boolean distinct = projection instanceof 
Query.DistinctPropertyProjection;
+                Predicate[] predicates2 = getPredicates(cb, criteriaQuery, 
from2, c.getValue().getCriteria(), newMap2, entity);
+                
subquery2.select(from2.get(pp.getPropertyName())).distinct(distinct).where(cb.and(predicates2));
+                return cb.not(cb.in(fullyQualifiedPath).value(subquery2));
+            } else if (projection instanceof Query.IdProjection) {
+                Predicate[] predicates2 = getPredicates(cb, criteriaQuery, 
from2, c.getValue().getCriteria(), newMap2, entity);
+                subquery2.select(from2).where(cb.and(predicates2));
+                return cb.not(cb.in(fullyQualifiedPath).value(subquery2));
+            }
+        }
+        return cb.not(cb.in(fullyQualifiedPath, c.getValue()));
+    }
 
-                    throw new IllegalArgumentException("Unsupported criterion: 
" + criterion);
-                }).filter(Objects::nonNull).toList();
-        if (list.isEmpty()) {
-            list = List.of(cb.equal(cb.literal(1),cb.literal(1)));
+    private Predicate handleIn(HibernateCriteriaBuilder cb, CriteriaQuery 
criteriaQuery, JpaFromProvider fromsByProvider, PersistentEntity entity, 
Query.In c, Path fullyQualifiedPath) {
+        var queryableCriteria = getQueryableCriteriaFromInCriteria(c);
+        if (Objects.nonNull(queryableCriteria)) {
+            return getQueryableCriteriaValue(cb, criteriaQuery, 
fromsByProvider, entity, c, queryableCriteria);
+        } else if (!c.getValues().isEmpty()) {
+            boolean areGormEntities = 
c.getValues().stream().allMatch(GormEntity.class::isInstance);
+            if (areGormEntities) {
+                List<GormEntity> gormEntities = new ArrayList<>(c.getValues());
+                Path id = 
criteriaQuery.from(gormEntities.get(0).getClass()).get("id");
+                Collection newValues = 
gormEntities.stream().map(GormEntity::ident).toList();
+                return cb.in(id, newValues);
+            }
+            return cb.in(fullyQualifiedPath, c.getValues());
         }
-        return list.toArray(new Predicate[0]);
+        return null;
+    }
+
+    private Predicate handleRLike(HibernateCriteriaBuilder cb, Path 
fullyQualifiedPath, Query.RLike c) {
+        String pattern = c.getPattern().replaceAll("^/|/$", "");
+        return cb.equal(cb.function(GrailsRLikeFunctionContributor.RLIKE, 
Boolean.class, fullyQualifiedPath, cb.literal(pattern)), true);
+    }
+
+    private Predicate handleSubqueryCriterion(HibernateCriteriaBuilder cb, 
CriteriaQuery criteriaQuery, JpaFromProvider fromsByProvider, PersistentEntity 
entity, Query.SubqueryCriterion c) {
+        Subquery subquery = criteriaQuery.subquery(Number.class);
+        Root from = 
subquery.from(c.getValue().getPersistentEntity().getJavaClass());
+        JpaFromProvider newMap = (JpaFromProvider) fromsByProvider.clone();
+        newMap.put("root", from);
+        Predicate[] predicates = getPredicates(cb, criteriaQuery, from, 
c.getValue().getCriteria(), newMap, entity);
+        Path path = fromsByProvider.getFullyQualifiedPath(c.getProperty());
+
+        if (c instanceof Query.GreaterThanEqualsAll) {
+            
subquery.select(cb.max(from.get(c.getProperty()))).where(cb.and(predicates));
+            return cb.greaterThanOrEqualTo(path, subquery);
+        } else if (c instanceof Query.GreaterThanAll) {
+            
subquery.select(cb.max(from.get(c.getProperty()))).where(cb.and(predicates));
+            return cb.greaterThan(path, subquery);
+        } else if (c instanceof Query.LessThanEqualsAll) {
+            
subquery.select(cb.min(from.get(c.getProperty()))).where(cb.and(predicates));
+            return cb.lessThanOrEqualTo(path, subquery);
+        } else if (c instanceof Query.LessThanAll) {
+            
subquery.select(cb.min(from.get(c.getProperty()))).where(cb.and(predicates));
+            return cb.lessThan(path, subquery);
+        } else if (c instanceof Query.EqualsAll) {
+            
subquery.select(from.get(c.getProperty())).where(cb.and(predicates));
+            return cb.equal(path, subquery);
+        } else if (c instanceof Query.GreaterThanEqualsSome) {
+            
subquery.select(cb.max(from.get(c.getProperty()))).where(cb.or(predicates));
+            return cb.greaterThanOrEqualTo(path, subquery);
+        } else if (c instanceof Query.GreaterThanSome) {
+            
subquery.select(cb.max(from.get(c.getProperty()))).where(cb.or(predicates));
+            return cb.greaterThan(path, subquery);
+        } else if (c instanceof Query.LessThanEqualsSome) {
+            
subquery.select(cb.min(from.get(c.getProperty()))).where(cb.or(predicates));
+            return cb.lessThanOrEqualTo(path, subquery);
+        } else if (c instanceof Query.LessThanSome) {
+            
subquery.select(cb.min(from.get(c.getProperty()))).where(cb.or(predicates));
+            return cb.lessThan(path, subquery);
+        }
+        return null;
+    }
+
+    private Predicate 
handlePropertyComparisonCriterion(HibernateCriteriaBuilder cb, JpaFromProvider 
fromsByProvider, From root_, Query.PropertyComparisonCriterion c) {
+        Path path = fromsByProvider.getFullyQualifiedPath(c.getProperty());
+        Path otherPath = root_.get(c.getOtherProperty());
+        if (c instanceof Query.EqualsProperty) return cb.equal(path, 
otherPath);
+        if (c instanceof Query.NotEqualsProperty) return cb.notEqual(path, 
otherPath);
+        if (c instanceof Query.LessThanEqualsProperty) return cb.le(path, 
otherPath);
+        if (c instanceof Query.LessThanProperty) return cb.lt(path, otherPath);
+        if (c instanceof Query.GreaterThanEqualsProperty) return cb.ge(path, 
otherPath);
+        if (c instanceof Query.GreaterThanProperty) return cb.gt(path, 
otherPath);
+        return null;
+    }
+
+    private Predicate handlePropertyNameCriterion(HibernateCriteriaBuilder cb, 
JpaFromProvider fromsByProvider, Query.PropertyNameCriterion c) {
+        Path path = fromsByProvider.getFullyQualifiedPath(c.getProperty());
+        if (c instanceof Query.IsNull) return cb.isNull(path);
+        if (c instanceof Query.IsNotNull) return cb.isNotNull(path);
+        if (c instanceof Query.IsEmpty) return cb.isEmpty(path);
+        if (c instanceof Query.IsNotEmpty) return cb.isNotEmpty(path);
+        return null;
+    }
+
+    private Predicate handleExists(HibernateCriteriaBuilder cb, CriteriaQuery 
criteriaQuery, From root_, JpaFromProvider fromsByProvider, PersistentEntity 
entity, Query.Exists c) {
+        Subquery subquery = criteriaQuery.subquery(Integer.class);
+        PersistentEntity childPersistentEntity = 
c.getSubquery().getPersistentEntity();
+        Root subRoot = subquery.from(childPersistentEntity.getJavaClass());
+        JpaFromProvider newMap = (JpaFromProvider) fromsByProvider.clone();
+        newMap.put("root", subRoot);
+        var predicates = getPredicates(cb, criteriaQuery, subRoot, 
c.getSubquery().getCriteria(), newMap, entity);
+        var existsPredicate = getExistsPredicate(cb, root_, 
childPersistentEntity, subRoot);
+        Predicate[] allPredicates = Stream.concat(Arrays.stream(predicates), 
Stream.of(existsPredicate)).toArray(Predicate[]::new);
+        subquery.select(cb.literal(1)).where(cb.and(allPredicates));
+        return cb.exists(subquery);
     }
 
     private CriteriaBuilder.In 
getQueryableCriteriaValue(HibernateCriteriaBuilder cb, CriteriaQuery 
criteriaQuery, JpaFromProvider fromsByProvider, PersistentEntity entity, 
Query.PropertyNameCriterion criterion, QueryableCriteria queryableCriteria) {
         var projection = findPropertyOrIdProjection(queryableCriteria);
         var subProperty = findSubproperty(projection);
-        var path = getPathFromInCriterion(fromsByProvider, criterion);
+        var path = 
fromsByProvider.getFullyQualifiedPath(criterion.getProperty());
         var in = findInPredicate(cb, projection, path, subProperty);
         var subquery = 
criteriaQuery.subquery(getJavaTypeOfInClause((SqmInListPredicate) in));
         var from = 
subquery.from(queryableCriteria.getPersistentEntity().getJavaClass());
-        var subCriteria = queryableCriteria.getCriteria();
         var clonedProviderByName = (JpaFromProvider) fromsByProvider.clone();
         clonedProviderByName.put("root", from);
-        var predicates = this.getPredicates(cb, criteriaQuery, from, 
subCriteria, clonedProviderByName, entity);
-        
subquery.select(clonedProviderByName.getFullyQualifiedPath(subProperty))
-                .distinct(true)
-                .where(cb.and(predicates));
-        CriteriaBuilder.In value = in.value(subquery);
-        return value;
+        var predicates = getPredicates(cb, criteriaQuery, from, 
queryableCriteria.getCriteria(), clonedProviderByName, entity);
+        
subquery.select(clonedProviderByName.getFullyQualifiedPath(subProperty)).distinct(true).where(cb.and(predicates));
+        return in.value(subquery);
     }
 
     private Predicate getExistsPredicate(HibernateCriteriaBuilder cb, From 
root_, PersistentEntity childPersistentEntity, Root subRoot) {
-        Association owner = childPersistentEntity
-                .getAssociations()
-                .stream()
+        Association owner = childPersistentEntity.getAssociations().stream()
                 .filter(assoc -> 
assoc.getAssociatedEntity().getJavaClass().equals(root_.getJavaType()))
                 .findFirst().orElseThrow();
-        Predicate existsPredicate = cb.equal(subRoot.get(owner.getName()), 
root_);
-        return existsPredicate;
+        return cb.equal(subRoot.get(owner.getName()), root_);
     }
 
     @SuppressWarnings("rawtypes")
@@ -309,46 +292,32 @@ public class PredicateGenerator {
     }
 
     private String findSubproperty(Object projection) {
-        return projection instanceof Query.PropertyProjection ? 
((Query.PropertyProjection) projection).getPropertyName() :"id" ;
+        return projection instanceof Query.PropertyProjection ? 
((Query.PropertyProjection) projection).getPropertyName() : "id";
     }
 
     @SuppressWarnings("unchecked")
     private Query.Projection findPropertyOrIdProjection(QueryableCriteria 
queryableCriteria) {
-        return (Query.Projection ) queryableCriteria.getProjections()
-                .stream().
-                filter(projection1 -> projection1 instanceof 
Query.PropertyProjection || projection1 instanceof Query.IdProjection)
-                .findFirst()
-                .orElse(new Query.IdProjection());
+        return (Query.Projection) queryableCriteria.getProjections().stream()
+                .filter(p -> p instanceof Query.PropertyProjection || p 
instanceof Query.IdProjection)
+                .findFirst().orElse(new Query.IdProjection());
     }
 
     @SuppressWarnings("rawtypes")
     private QueryableCriteria 
getQueryableCriteriaFromInCriteria(Query.Criterion criterion) {
-        return criterion instanceof Query.In  ? ((Query.In) 
criterion).getSubquery() : ((Query.NotIn) criterion).getSubquery();
-    }
-
-    @SuppressWarnings("rawtypes")
-    private Path getPathFromInCriterion(JpaFromProvider tablesByName, 
Query.PropertyNameCriterion criterion) {
-        return tablesByName.getFullyQualifiedPath(criterion.getProperty());
+        return criterion instanceof Query.In ? ((Query.In) 
criterion).getSubquery() : ((Query.NotIn) criterion).getSubquery();
     }
 
-
     @SuppressWarnings("rawtypes")
     private Class getJavaTypeOfInClause(SqmInListPredicate predicate) {
-        return Optional.ofNullable(predicate.getTestExpression()
-                .getExpressible())
-                .map(expressible ->  
expressible.getExpressibleJavaType().getJavaTypeClass())
+        return 
Optional.ofNullable(predicate.getTestExpression().getExpressible())
+                .map(expressible -> 
expressible.getExpressibleJavaType().getJavaTypeClass())
                 .orElse(null);
     }
 
     private Number getNumericValue(Query.PropertyCriterion criterion) {
         Object value = criterion.getValue();
-        if (value instanceof Number) {
-            return (Number) value;
-        }
-        String operationName = criterion.getClass().getSimpleName();
-        throw new ConfigurationException(
-                String.format("Operation '%s' on property '%s' only accepts a 
numeric value, but received a %s",
-                        operationName, criterion.getProperty(), (value == null 
? "null" : value.getClass().getName())));
+        if (value instanceof Number num) return num;
+        throw new ConfigurationException(String.format("Operation '%s' on 
property '%s' only accepts a numeric value, but received a %s",
+                criterion.getClass().getSimpleName(), criterion.getProperty(), 
(value == null ? "null" : value.getClass().getName())));
     }
-
 }


Reply via email to