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 cf11cf0551e272a91249dff19ffe70440907a509
Author: Walter Duque de Estrada <[email protected]>
AuthorDate: Thu Feb 19 15:40:47 2026 -0600

    Extract CriteriaMethodInvoker from HibernateCriteriaBuilder
---
 .../groovy/grails/orm/CriteriaMethodInvoker.java   | 316 ++++++++++++++++++
 .../grails/orm/HibernateCriteriaBuilder.java       | 367 +++------------------
 .../grails/orm/hibernate/query/HibernateQuery.java |   4 +
 .../grails/orm/CriteriaMethodInvokerSpec.groovy    | 177 ++++++++++
 4 files changed, 552 insertions(+), 312 deletions(-)

diff --git 
a/grails-data-hibernate7/core/src/main/groovy/grails/orm/CriteriaMethodInvoker.java
 
b/grails-data-hibernate7/core/src/main/groovy/grails/orm/CriteriaMethodInvoker.java
new file mode 100644
index 0000000000..d9da84d2fe
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/main/groovy/grails/orm/CriteriaMethodInvoker.java
@@ -0,0 +1,316 @@
+package grails.orm;
+
+import grails.gorm.DetachedCriteria;
+import grails.gorm.PagedResultList;
+import groovy.lang.Closure;
+import groovy.lang.MetaMethod;
+import groovy.lang.MissingMethodException;
+import jakarta.persistence.criteria.JoinType;
+import jakarta.persistence.metamodel.Attribute;
+import jakarta.persistence.metamodel.EntityType;
+import jakarta.persistence.metamodel.Metamodel;
+import org.grails.datastore.mapping.query.Query;
+import org.grails.orm.hibernate.query.HibernateQuery;
+import org.grails.orm.hibernate.query.HibernateQueryConstants;
+import org.hibernate.SessionFactory;
+import org.springframework.beans.BeanUtils;
+
+import java.beans.PropertyDescriptor;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Helper class to handle method invocation for HibernateCriteriaBuilder.
+ */
+public class CriteriaMethodInvoker {
+
+    public static final String AND = "and"; // builder
+    public static final String IS_NULL = "isNull"; // builder
+    public static final String IS_NOT_NULL = "isNotNull"; // builder
+    public static final String NOT = "not";// builder
+    public static final String OR = "or"; // builder
+    public static final String ID_EQUALS = "idEq"; // builder
+    public static final String IS_EMPTY = "isEmpty"; //builder
+    public static final String IS_NOT_EMPTY = "isNotEmpty"; //builder
+    public static final String RLIKE = "rlike";//method
+    public static final String BETWEEN = "between";//method
+    public static final String EQUALS = "eq";//method
+    public static final String EQUALS_PROPERTY = "eqProperty";//method
+    public static final String GREATER_THAN = "gt";//method
+    public static final String GREATER_THAN_PROPERTY = "gtProperty";//method
+    public static final String GREATER_THAN_OR_EQUAL = "ge";//method
+    public static final String GREATER_THAN_OR_EQUAL_PROPERTY = 
"geProperty";//method
+    public static final String ILIKE = "ilike";//method
+    public static final String IN = "in";//method
+    public static final String LESS_THAN = "lt"; //method
+    public static final String LESS_THAN_PROPERTY = "ltProperty";//method
+    public static final String LESS_THAN_OR_EQUAL = "le";//method
+    public static final String LESS_THAN_OR_EQUAL_PROPERTY = 
"leProperty";//method
+    public static final String LIKE = "like";//method
+    public static final String NOT_EQUAL = "ne";//method
+    public static final String NOT_EQUAL_PROPERTY = "neProperty";//method
+    public static final String SIZE_EQUALS = "sizeEq"; //method
+    protected static final String ROOT_DO_CALL = "doCall";
+    protected static final String ROOT_CALL = "call";
+    protected static final String LIST_CALL = "list";
+    protected static final String LIST_DISTINCT_CALL = "listDistinct";
+    protected static final String COUNT_CALL = "count";
+    protected static final String GET_CALL = "get";
+    protected static final String SCROLL_CALL = "scroll";
+    protected static final String PROJECTIONS = "projections";
+
+    private final HibernateCriteriaBuilder builder;
+
+    public CriteriaMethodInvoker(HibernateCriteriaBuilder builder) {
+        this.builder = builder;
+    }
+
+    public Object invokeMethod(String name, Object[] args) {
+        HibernateQuery hibernateQuery = builder.getHibernateQuery();
+
+        if (isCriteriaConstructionMethod(name, args)) {
+            if (name.equals(GET_CALL)) {
+                builder.setUniqueResult(true);
+            }
+            else if (name.equals(SCROLL_CALL)) {
+                builder.setScroll(true);
+            }
+            else if (name.equals(COUNT_CALL)) {
+                builder.setCount(true);
+            }
+            else if (name.equals(LIST_DISTINCT_CALL)) {
+                builder.setDistinct(true);
+            }
+
+
+            // Check for pagination params
+            if (name.equals(LIST_CALL) && args.length == 2) {
+                builder.setPaginationEnabledList(true);
+                if (args[0] instanceof Map map ) {
+                    if (map.get("max") instanceof Number max) {
+                        hibernateQuery.maxResults(max.intValue());
+                    }
+                    if (map.get("offset") instanceof Number offset) {
+                        hibernateQuery.firstResult(offset.intValue());
+                    }
+                }
+                invokeClosureNode(args[1]);
+            }
+            else {
+                invokeClosureNode(args[0]);
+            }
+
+            Object result;
+            if (!builder.isUniqueResult()) {
+                if (builder.isDistinct()) {
+                    hibernateQuery.distinct();
+                    result = hibernateQuery.list();
+                }
+                else if (builder.isCount()) {
+                    hibernateQuery.projections().count();
+                    result = hibernateQuery.singleResult();
+                }
+                else if (builder.isPaginationEnabledList()) {
+                    Map argMap = (Map)args[0];
+                    final String sortField = (String) 
argMap.get(HibernateQueryConstants.ARGUMENT_SORT);
+                    if (sortField != null) {
+                        boolean ignoreCase = true;
+                        Object caseArg = 
argMap.get(HibernateQueryConstants.ARGUMENT_IGNORE_CASE);
+                        if (caseArg instanceof Boolean) {
+                            ignoreCase = (Boolean) caseArg;
+                        }
+                        final String orderParam = (String) 
argMap.get(HibernateQueryConstants.ARGUMENT_ORDER);
+                        final Query.Order.Direction direction = 
Query.Order.Direction.DESC.name().equalsIgnoreCase(orderParam) ? 
Query.Order.Direction.DESC : Query.Order.Direction.ASC;
+                        Query.Order order ;
+                        if (ignoreCase) {
+                            order = new Query.Order(sortField, direction);
+                            order.ignoreCase();
+                        } else {
+                            order = new Query.Order(sortField, direction);
+                        }
+                        hibernateQuery.order(order);
+                    }
+                    result = new PagedResultList<>(hibernateQuery);
+                }
+                else {
+                    result = hibernateQuery.list();
+                }
+            }
+            else {
+                result = hibernateQuery.singleResult();
+            }
+            if (!builder.isParticipate()) {
+                builder.closeSession();
+            }
+            return result;
+        }
+
+
+        MetaMethod metaMethod = builder.getMetaClass().getMetaMethod(name, 
args);
+        if (metaMethod != null) {
+            return metaMethod.invoke(builder, args);
+        }
+
+
+
+        if (isAssociationQueryMethod(args) || 
isAssociationQueryWithJoinSpecificationMethod(args)) {
+            final boolean hasMoreThanOneArg = args.length > 1;
+            Closure callable = hasMoreThanOneArg ? (Closure) args[1] : 
(Closure) args[0];
+            JoinType joinType = hasMoreThanOneArg ? 
builder.convertFromInt((Integer)args[0]) : builder.convertFromInt(0);
+
+            if (name.equals(AND)) {
+                hibernateQuery.and(callable);
+                return name;
+            }
+
+            if (name.equals(OR) ) {
+                hibernateQuery.or(callable);
+                return name;
+            }
+
+            if ( name.equals(NOT)) {
+                hibernateQuery.not(callable);
+                return name;
+            }
+
+
+            if (name.equals(PROJECTIONS) && args.length == 1 && (args[0] 
instanceof Closure)) {
+                invokeClosureNode(callable);
+                return name;
+            }
+
+            final PropertyDescriptor pd = 
BeanUtils.getPropertyDescriptor(builder.getTargetClass(), name);
+            if (pd != null && pd.getReadMethod() != null) {
+                final Metamodel metamodel = 
builder.getSessionFactory().getMetamodel();
+                final EntityType<?> entityType = 
metamodel.entity(builder.getTargetClass());
+                final Attribute<?, ?> attribute = 
entityType.getAttribute(name);
+
+                if (attribute.isAssociation()) {
+                    Class oldTargetClass = builder.getTargetClass();
+                    
builder.setTargetClass(builder.getClassForAssociationType(attribute));
+                    if (builder.getTargetClass().equals(oldTargetClass) && 
!hasMoreThanOneArg) {
+                        joinType = JoinType.LEFT; // default to left join if 
joining on the same table
+                    }
+
+                    hibernateQuery.join(name,joinType);
+                    hibernateQuery.in(name, new 
DetachedCriteria(builder.getTargetClass()).build(callable));
+                    builder.setTargetClass(oldTargetClass);
+
+                    return name;
+                }
+            }
+        }
+        else if (args.length == 1 && args[0] != null) {
+            Object value = args[0];
+            if (name.equals(ID_EQUALS)) {
+                return builder.eq("id", value);
+            }
+            if (name.equals(IS_NULL) ||
+                    name.equals(IS_NOT_NULL) ||
+                    name.equals(IS_EMPTY) ||
+                    name.equals(IS_NOT_EMPTY)) {
+                if (!(value instanceof String)) {
+                    builder.throwRuntimeException(new 
IllegalArgumentException("call to [" + name + "] with value [" +
+                            value + "] requires a String value."));
+                }
+                String propertyName = 
builder.calculatePropertyName((String)value);
+                if (name.equals(IS_NULL)) {
+                    hibernateQuery.isNull(propertyName);
+                }
+                else if (name.equals(IS_NOT_NULL)) {
+                    hibernateQuery.isNotNull(propertyName);
+                }
+                else if (name.equals(IS_EMPTY)) {
+                    hibernateQuery.isEmpty(propertyName);
+                }
+                else {
+                    hibernateQuery.isNotEmpty(propertyName);
+                }
+                return name;
+            }
+        }
+        else if (args.length >= 2 && args[0] instanceof String propertyName) {
+            propertyName = builder.calculatePropertyName(propertyName);
+            switch (name) {
+                case RLIKE:
+                    return builder.rlike(propertyName, args[1]);
+                case BETWEEN:
+                    if (args.length >= 3) {
+                        return builder.between(propertyName, args[1], args[2]);
+                    }
+                    break;
+                case EQUALS:
+                    if (args.length == 3 && args[2] instanceof Map) {
+                        return builder.eq(propertyName, args[1], (Map) 
args[2]);
+                    }
+                    return builder.eq(propertyName, args[1]);
+                case EQUALS_PROPERTY:
+                    return builder.eqProperty(propertyName, 
args[1].toString());
+                case GREATER_THAN:
+                    return builder.gt(propertyName, args[1]);
+                case GREATER_THAN_PROPERTY:
+                    return builder.gtProperty(propertyName, 
args[1].toString());
+                case GREATER_THAN_OR_EQUAL:
+                    return builder.ge(propertyName, args[1]);
+                case GREATER_THAN_OR_EQUAL_PROPERTY:
+                    return builder.geProperty(propertyName, 
args[1].toString());
+                case ILIKE:
+                    return builder.ilike(propertyName, args[1]);
+                case IN:
+                    if (args[1] instanceof Collection) {
+                        return builder.in(propertyName, (Collection) args[1]);
+                    } else if (args[1] instanceof Object[]) {
+                        return builder.in(propertyName, (Object[]) args[1]);
+                    }
+                    break;
+                case LESS_THAN:
+                    return builder.lt(propertyName, args[1]);
+                case LESS_THAN_PROPERTY:
+                    return builder.ltProperty(propertyName, 
args[1].toString());
+                case LESS_THAN_OR_EQUAL:
+                    return builder.le(propertyName, args[1]);
+                case LESS_THAN_OR_EQUAL_PROPERTY:
+                    return builder.leProperty(propertyName, 
args[1].toString());
+                case LIKE:
+                    return builder.like(propertyName, args[1]);
+                case NOT_EQUAL:
+                    return builder.ne(propertyName, args[1]);
+                case NOT_EQUAL_PROPERTY:
+                    return builder.neProperty(propertyName, 
args[1].toString());
+                case SIZE_EQUALS:
+                    if (args[1] instanceof Number) {
+                        return builder.sizeEq(propertyName, ((Number) 
args[1]).intValue());
+                    }
+                    break;
+            }
+        }
+        throw new MissingMethodException(name, HibernateCriteriaBuilder.class, 
args);
+    }
+
+    private boolean isAssociationQueryMethod(Object[] args) {
+        return args.length == 1 && args[0] instanceof Closure;
+    }
+
+    private boolean isAssociationQueryWithJoinSpecificationMethod(Object[] 
args) {
+        return args.length == 2 && (args[0] instanceof Number) && (args[1] 
instanceof Closure);
+    }
+
+
+    private boolean isCriteriaConstructionMethod(String name, Object[] args) {
+        return (name.equals(LIST_CALL) && args.length == 2 && args[0] 
instanceof Map && args[1] instanceof Closure) ||
+                (name.equals(ROOT_CALL) ||
+                        name.equals(ROOT_DO_CALL) ||
+                        name.equals(LIST_CALL) ||
+                        name.equals(LIST_DISTINCT_CALL) ||
+                        name.equals(GET_CALL) ||
+                        name.equals(COUNT_CALL) ||
+                        name.equals(SCROLL_CALL) && args.length == 1 && 
args[0] instanceof Closure);
+    }
+
+    private void invokeClosureNode(Object args) {
+        Closure<?> callable = (Closure<?>)args;
+        callable.setDelegate(builder);
+        callable.setResolveStrategy(Closure.DELEGATE_FIRST);
+        callable.call();
+    }
+}
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java
 
b/grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java
index 3a3b6b14c1..b08abe9968 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java
@@ -109,44 +109,7 @@ public class HibernateCriteriaBuilder extends 
GroovyObjectSupport implements Bui
      * to refer to standard Hibernate Type instances.
      */
 
-    public static final String AND = "and"; // builder
-    public static final String IS_NULL = "isNull"; // builder
-    public static final String IS_NOT_NULL = "isNotNull"; // builder
-    public static final String NOT = "not";// builder
-    public static final String OR = "or"; // builder
-    public static final String ID_EQUALS = "idEq"; // builder
-    public static final String IS_EMPTY = "isEmpty"; //builder
-    public static final String IS_NOT_EMPTY = "isNotEmpty"; //builder
-    public static final String RLIKE = "rlike";//method
-    public static final String BETWEEN = "between";//method
-    public static final String EQUALS = "eq";//method
-    public static final String EQUALS_PROPERTY = "eqProperty";//method
-    public static final String GREATER_THAN = "gt";//method
-    public static final String GREATER_THAN_PROPERTY = "gtProperty";//method
-    public static final String GREATER_THAN_OR_EQUAL = "ge";//method
-    public static final String GREATER_THAN_OR_EQUAL_PROPERTY = 
"geProperty";//method
-    public static final String ILIKE = "ilike";//method
-    public static final String IN = "in";//method
-    public static final String LESS_THAN = "lt"; //method
-    public static final String LESS_THAN_PROPERTY = "ltProperty";//method
-    public static final String LESS_THAN_OR_EQUAL = "le";//method
-    public static final String LESS_THAN_OR_EQUAL_PROPERTY = 
"leProperty";//method
-    public static final String LIKE = "like";//method
-    public static final String NOT_EQUAL = "ne";//method
-    public static final String NOT_EQUAL_PROPERTY = "neProperty";//method
-    public static final String SIZE_EQUALS = "sizeEq"; //method
-    public static final String ORDER_DESCENDING = "desc";
-    public static final String ORDER_ASCENDING = "asc";
-    protected static final String ROOT_DO_CALL = "doCall";
-    protected static final String ROOT_CALL = "call";
-    protected static final String LIST_CALL = "list";
-    protected static final String LIST_DISTINCT_CALL = "listDistinct";
-    protected static final String COUNT_CALL = "count";
-    protected static final String GET_CALL = "get";
-    protected static final String SCROLL_CALL = "scroll";
-    protected static final String SET_RESULT_TRANSFORMER_CALL = 
"setResultTransformer";
-    protected static final String PROJECTIONS = "projections";
-    private static final Logger log = 
LoggerFactory.getLogger(HibernateCriteriaBuilder.class);
+
 
 
     protected SessionFactory sessionFactory;
@@ -157,24 +120,19 @@ public class HibernateCriteriaBuilder extends 
GroovyObjectSupport implements Bui
     protected boolean participate;
     protected boolean scroll;
     protected boolean count;
-    protected Query.ProjectionList projectionList = new Query.ProjectionList();
     protected List<String> aliasStack = new ArrayList<String>();
-    protected Map<String, String> aliasMap = new HashMap<String, String>();
     protected static final String ALIAS = "_alias";
-    protected ResultTransformer resultTransformer;
-    protected int aliasCount;
     protected boolean paginationEnabledList = false;
     protected ConversionService conversionService;
     protected int defaultFlushMode;
     protected AbstractHibernateDatastore datastore;
     protected org.hibernate.query.criteria.HibernateCriteriaBuilder cb;
     protected Root root;
-    protected Subquery subquery;
     protected HibernateQuery hibernateQuery;
     private boolean shouldLock;
     private boolean shouldCache;
     private boolean readOnly;
-    private boolean distinct = false;
+    protected boolean distinct = false;
 
     @SuppressWarnings("rawtypes")
     public HibernateCriteriaBuilder(Class targetClass, SessionFactory 
sessionFactory, AbstractHibernateDatastore datastore) {
@@ -1133,7 +1091,7 @@ public class HibernateCriteriaBuilder extends 
GroovyObjectSupport implements Bui
     @Override
     public Object list(@DelegatesTo(Criteria.class) Closure c) {
         hibernateQuery.setDetachedCriteria(new DetachedCriteria(targetClass));
-        return invokeMethod(LIST_CALL, new Object[]{c});
+        return invokeMethod(CriteriaMethodInvoker.LIST_CALL, new Object[]{c});
     }
 
     public List list() {
@@ -1147,22 +1105,22 @@ public class HibernateCriteriaBuilder extends 
GroovyObjectSupport implements Bui
     @Override
     public Object list(Map params, @DelegatesTo(Criteria.class) Closure c) {
         hibernateQuery.setDetachedCriteria(new DetachedCriteria(targetClass));
-        return invokeMethod(LIST_CALL, new Object[]{params, c});
+        return invokeMethod(CriteriaMethodInvoker.LIST_CALL, new 
Object[]{params, c});
     }
     
     @Override
     public Object listDistinct(@DelegatesTo(Criteria.class) Closure c) {
-        return invokeMethod(LIST_DISTINCT_CALL, new Object[]{c});
+        return invokeMethod(CriteriaMethodInvoker.LIST_DISTINCT_CALL, new 
Object[]{c});
     }
 
     @Override
     public Object get(@DelegatesTo(Criteria.class) Closure c) {
-        return invokeMethod(GET_CALL, new Object[]{c});
+        return invokeMethod(CriteriaMethodInvoker.GET_CALL, new Object[]{c});
     }
     
     @Override
     public Object scroll(@DelegatesTo(Criteria.class) Closure c) {
-        return invokeMethod(SCROLL_CALL, new Object[]{c});
+        return invokeMethod(CriteriaMethodInvoker.SCROLL_CALL, new 
Object[]{c});
     }
 
     public JoinType convertFromInt(Integer from) {
@@ -1176,279 +1134,76 @@ public class HibernateCriteriaBuilder extends 
GroovyObjectSupport implements Bui
     @SuppressWarnings("rawtypes")
     @Override
     public Object invokeMethod(String name, Object obj) {
+        Object[] args = obj.getClass().isArray() ? (Object[])obj : (obj 
instanceof Collection ? ((Collection)obj).toArray() : new Object[]{obj});
+        return new CriteriaMethodInvoker(this).invokeMethod(name, args);
+    }
 
-        Object[] args = obj.getClass().isArray() ? (Object[])obj : new 
Object[]{obj};
-
-        if (paginationEnabledList && SET_RESULT_TRANSFORMER_CALL.equals(name) 
&& args.length == 1 &&
-                args[0] instanceof ResultTransformer) {
-            hibernateQuery.setResultTransformer((ResultTransformer) args[0]);
-            return null;
-        }
-
-        if (isCriteriaConstructionMethod(name, args)) {
-            if (name.equals(GET_CALL)) {
-                uniqueResult = true;
-            }
-            else if (name.equals(SCROLL_CALL)) {
-                scroll = true;
-            }
-            else if (name.equals(COUNT_CALL)) {
-                count = true;
-            }
-            else if (name.equals(LIST_DISTINCT_CALL)) {
-                distinct = true;
-            }
-
-
-            // Check for pagination params
-            if (name.equals(LIST_CALL) && args.length == 2) {
-                paginationEnabledList = true;
-                if (args[0] instanceof Map map ) {
-                    if (map.get("max") instanceof Number max) {
-                        hibernateQuery.maxResults(max.intValue());
-                    }
-                    if (map.get("offset") instanceof Number offset) {
-                        hibernateQuery.firstResult(offset.intValue());
-                    }
-                }
-                invokeClosureNode(args[1]);
-            }
-            else {
-                invokeClosureNode(args[0]);
-            }
-
-            Object result;
-            if (!uniqueResult) {
-                if (distinct) {
-                    hibernateQuery.distinct();
-                    result = hibernateQuery.list();
-                }
-                else if (count) {
-                    hibernateQuery.projections().count();
-                    result = hibernateQuery.singleResult();
-                }
-                else if (paginationEnabledList) {
-                    Map argMap = (Map)args[0];
-                    final String sortField = (String) 
argMap.get(HibernateQueryConstants.ARGUMENT_SORT);
-                    if (sortField != null) {
-                        boolean ignoreCase = true;
-                        Object caseArg = 
argMap.get(HibernateQueryConstants.ARGUMENT_IGNORE_CASE);
-                        if (caseArg instanceof Boolean) {
-                            ignoreCase = (Boolean) caseArg;
-                        }
-                        final String orderParam = (String) 
argMap.get(HibernateQueryConstants.ARGUMENT_ORDER);
-                        final Query.Order.Direction direction = 
Query.Order.Direction.DESC.name().equalsIgnoreCase(orderParam) ? 
Query.Order.Direction.DESC : Query.Order.Direction.ASC;
-                        Query.Order order ;
-                        if (ignoreCase) {
-                            order = new Query.Order(sortField, direction);
-                            order.ignoreCase();
-                        } else {
-                            order = new Query.Order(sortField, direction);
-                        }
-                        hibernateQuery.order(order);
-                    }
-                    result = new PagedResultList<>(hibernateQuery);
-                }
-                else {
-                    result = hibernateQuery.list();
-                }
-            }
-            else {
-                result = hibernateQuery.singleResult();
-            }
-            if (!participate) {
-                closeSession();
-            }
-            return result;
-        }
-
-
-        MetaMethod metaMethod = getMetaClass().getMetaMethod(name, args);
-        if (metaMethod != null) {
-            return metaMethod.invoke(this, args);
-        }
-
-
-
-        if (isAssociationQueryMethod(args) || 
isAssociationQueryWithJoinSpecificationMethod(args)) {
-            final boolean hasMoreThanOneArg = args.length > 1;
-            Closure callable = hasMoreThanOneArg ? (Closure) args[1] : 
(Closure) args[0];
-            JoinType joinType = hasMoreThanOneArg ? 
convertFromInt((Integer)args[0]) : convertFromInt(0);
-
-            if (name.equals(AND)) {
-                hibernateQuery.and(callable);
-                return name;
-            }
-
-            if (name.equals(OR) ) {
-                hibernateQuery.or(callable);
-                return name;
-            }
-
-            if ( name.equals(NOT)) {
-                hibernateQuery.not(callable);
-                return name;
-            }
-
-
-            if (name.equals(PROJECTIONS) && args.length == 1 && (args[0] 
instanceof Closure)) {
-                invokeClosureNode(callable);
-                return name;
-            }
 
-            final PropertyDescriptor pd = 
BeanUtils.getPropertyDescriptor(targetClass, name);
-            if (pd != null && pd.getReadMethod() != null) {
-                final Metamodel metamodel = sessionFactory.getMetamodel();
-                final EntityType<?> entityType = metamodel.entity(targetClass);
-                final Attribute<?, ?> attribute = 
entityType.getAttribute(name);
+    /**
+     * Returns the criteria instance
+     * @return The criteria instance
+     */
+    public CriteriaQuery getInstance() {
+        return criteriaQuery;
+    }
 
-                if (attribute.isAssociation()) {
-                    Class oldTargetClass = targetClass;
-                    targetClass = getClassForAssociationType(attribute);
-                    if (targetClass.equals(oldTargetClass) && 
!hasMoreThanOneArg) {
-                        joinType = JoinType.LEFT; // default to left join if 
joining on the same table
-                    }
+    /**
+     * Set whether a unique result should be returned
+     */
+    public boolean isUniqueResult() {
+        return uniqueResult;
+    }
 
-                    hibernateQuery.join(name,joinType);
-                    hibernateQuery.in(name, new 
DetachedCriteria(targetClass).build(callable));
-                    targetClass = oldTargetClass;
+    public boolean isDistinct() {
+        return distinct;
+    }
 
-                    return name;
-                }
-            }
-        }
-        else if (args.length == 1 && args[0] != null) {
-            Object value = args[0];
-            if (name.equals(ID_EQUALS)) {
-                return eq("id", value);
-            }
-            if (name.equals(IS_NULL) ||
-                    name.equals(IS_NOT_NULL) ||
-                    name.equals(IS_EMPTY) ||
-                    name.equals(IS_NOT_EMPTY)) {
-                if (!(value instanceof String)) {
-                    throwRuntimeException(new IllegalArgumentException("call 
to [" + name + "] with value [" +
-                            value + "] requires a String value."));
-                }
-                String propertyName = calculatePropertyName((String)value);
-                if (name.equals(IS_NULL)) {
-                    hibernateQuery.isNull(propertyName);
-                }
-                else if (name.equals(IS_NOT_NULL)) {
-                    hibernateQuery.isNotNull(propertyName);
-                }
-                else if (name.equals(IS_EMPTY)) {
-                    hibernateQuery.isEmpty(propertyName);
-                }
-                else {
-                    hibernateQuery.isNotEmpty(propertyName);
-                }
-                return name;
-            }
-        }
-        else if (args.length >= 2 && args[0] instanceof String propertyName) {
-            propertyName = calculatePropertyName(propertyName);
-            switch (name) {
-                case RLIKE:
-                    return rlike(propertyName, args[1]);
-                case BETWEEN:
-                    if (args.length >= 3) {
-                        return between(propertyName, args[1], args[2]);
-                    }
-                    break;
-                case EQUALS:
-                    if (args.length == 3 && args[2] instanceof Map) {
-                        return eq(propertyName, args[1], (Map) args[2]);
-                    }
-                    return eq(propertyName, args[1]);
-                case EQUALS_PROPERTY:
-                    return eqProperty(propertyName, args[1].toString());
-                case GREATER_THAN:
-                    return gt(propertyName, args[1]);
-                case GREATER_THAN_PROPERTY:
-                    return gtProperty(propertyName, args[1].toString());
-                case GREATER_THAN_OR_EQUAL:
-                    return ge(propertyName, args[1]);
-                case GREATER_THAN_OR_EQUAL_PROPERTY:
-                    return geProperty(propertyName, args[1].toString());
-                case ILIKE:
-                    return ilike(propertyName, args[1]);
-                case IN:
-                    if (args[1] instanceof Collection) {
-                        return in(propertyName, (Collection) args[1]);
-                    } else if (args[1] instanceof Object[]) {
-                        return in(propertyName, (Object[]) args[1]);
-                    }
-                    break;
-                case LESS_THAN:
-                    return lt(propertyName, args[1]);
-                case LESS_THAN_PROPERTY:
-                    return ltProperty(propertyName, args[1].toString());
-                case LESS_THAN_OR_EQUAL:
-                    return le(propertyName, args[1]);
-                case LESS_THAN_OR_EQUAL_PROPERTY:
-                    return leProperty(propertyName, args[1].toString());
-                case LIKE:
-                    return like(propertyName, args[1]);
-                case NOT_EQUAL:
-                    return ne(propertyName, args[1]);
-                case NOT_EQUAL_PROPERTY:
-                    return neProperty(propertyName, args[1].toString());
-                case SIZE_EQUALS:
-                    if (args[1] instanceof Number) {
-                        return sizeEq(propertyName, ((Number) 
args[1]).intValue());
-                    }
-                    break;
-            }
-        }
-        throw new MissingMethodException(name, getClass(), args);
+    public boolean isCount() {
+        return count;
     }
 
+    public void setUniqueResult(boolean uniqueResult) {
+        this.uniqueResult = uniqueResult;
+    }
 
-    private boolean isAssociationQueryMethod(Object[] args) {
-        return args.length == 1 && args[0] instanceof Closure;
+    public boolean isPaginationEnabledList() {
+        return paginationEnabledList;
     }
 
-    private boolean isAssociationQueryWithJoinSpecificationMethod(Object[] 
args) {
-        return args.length == 2 && (args[0] instanceof Number) && (args[1] 
instanceof Closure);
+    public void setPaginationEnabledList(boolean paginationEnabledList) {
+        this.paginationEnabledList = paginationEnabledList;
     }
 
+    public void setScroll(boolean scroll) {
+        this.scroll = scroll;
+    }
 
-    private boolean isCriteriaConstructionMethod(String name, Object[] args) {
-        return (name.equals(LIST_CALL) && args.length == 2 && args[0] 
instanceof Map && args[1] instanceof Closure) ||
-                (name.equals(ROOT_CALL) ||
-                        name.equals(ROOT_DO_CALL) ||
-                        name.equals(LIST_CALL) ||
-                        name.equals(LIST_DISTINCT_CALL) ||
-                        name.equals(GET_CALL) ||
-                        name.equals(COUNT_CALL) ||
-                        name.equals(SCROLL_CALL) && args.length == 1 && 
args[0] instanceof Closure);
+    public void setCount(boolean count) {
+        this.count = count;
     }
 
+    public void setDistinct(boolean distinct) {
+        this.distinct = distinct;
+    }
 
+    public HibernateQuery getHibernateQuery() {
+        return hibernateQuery;
+    }
 
-    private void invokeClosureNode(Object args) {
-        Closure<?> callable = (Closure<?>)args;
-        callable.setDelegate(this);
-        callable.setResolveStrategy(Closure.DELEGATE_FIRST);
-        callable.call();
+    public boolean isParticipate() {
+        return participate;
     }
 
+    public void setTargetClass(Class<?> targetClass) {
+        this.targetClass = targetClass;
+    }
 
-    /**
-     * Returns the criteria instance
-     * @return The criteria instance
-     */
-    public CriteriaQuery getInstance() {
-        return criteriaQuery;
+    public SessionFactory getSessionFactory() {
+        return sessionFactory;
     }
 
-    /**
-     * Set whether a unique result should be returned
-     * @param uniqueResult True if a unique result should be returned
-     */
-    public void setUniqueResult(boolean uniqueResult) {
-        this.uniqueResult = uniqueResult;
+    public org.hibernate.query.criteria.HibernateCriteriaBuilder 
getCriteriaBuilder() {
+        return cb;
     }
 
     protected Class getClassForAssociationType(Attribute<?, ?> type) {
@@ -1458,18 +1213,6 @@ public class HibernateCriteriaBuilder extends 
GroovyObjectSupport implements Bui
         return type.getJavaType();
     }
 
-    /**
-     * instances of this class are pushed onto the logicalExpressionStack
-     * to represent all the unfinished "and", "or", and "not" expressions.
-     */
-    protected class LogicalExpression {
-        public final Object name;
-
-        public LogicalExpression(Object name) {
-            this.name = name;
-        }
-    }
-
     /**
      * Throws a runtime exception where necessary to ensure the session gets 
closed
      */
diff --git 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateQuery.java
 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateQuery.java
index 0dd747c2de..1507ed21ab 100644
--- 
a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateQuery.java
+++ 
b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/HibernateQuery.java
@@ -99,6 +99,10 @@ public class HibernateQuery extends Query {
         this.resultTransformer = resultTransformer;
     }
 
+    public ResultTransformer getResultTransformer() {
+        return resultTransformer;
+    }
+
     @Override
     protected Object resolveIdIfEntity(Object value) {
         // for Hibernate queries, the object itself is used in queries, not 
the id
diff --git 
a/grails-data-hibernate7/core/src/test/groovy/grails/orm/CriteriaMethodInvokerSpec.groovy
 
b/grails-data-hibernate7/core/src/test/groovy/grails/orm/CriteriaMethodInvokerSpec.groovy
new file mode 100644
index 0000000000..4cc10bcfdf
--- /dev/null
+++ 
b/grails-data-hibernate7/core/src/test/groovy/grails/orm/CriteriaMethodInvokerSpec.groovy
@@ -0,0 +1,177 @@
+package grails.orm
+
+import groovy.lang.Closure
+import groovy.lang.MissingMethodException
+import org.grails.orm.hibernate.query.HibernateQuery
+import org.hibernate.SessionFactory
+import spock.lang.Specification
+
+class CriteriaMethodInvokerSpec extends Specification {
+
+    HibernateCriteriaBuilder builder = Mock(HibernateCriteriaBuilder)
+    HibernateQuery query = Mock(HibernateQuery)
+    CriteriaMethodInvoker invoker = new CriteriaMethodInvoker(builder)
+
+    def setup() {
+        builder.getHibernateQuery() >> query
+        _ * builder.isPaginationEnabledList() >> false
+    }
+
+    void "test invokeMethod handles list call"() {
+        given:
+        def closure = { eq("foo", "bar") }
+
+        when:
+        invoker.invokeMethod("list", [closure] as Object[])
+
+        then:
+        1 * builder.isUniqueResult() >> false
+        1 * builder.isDistinct() >> false
+        1 * builder.isCount() >> false
+        1 * query.list() >> []
+        1 * builder.isParticipate() >> true
+    }
+
+    void "test invokeMethod handles get call"() {
+        given:
+        def closure = { eq("foo", "bar") }
+
+        when:
+        invoker.invokeMethod("get", [closure] as Object[])
+
+        then:
+        1 * builder.setUniqueResult(true)
+        1 * builder.isUniqueResult() >> true
+        1 * query.singleResult() >> null
+        1 * builder.isParticipate() >> true
+    }
+
+    void "test invokeMethod handles count call"() {
+        given:
+        def closure = { eq("foo", "bar") }
+        def projectionList = new 
org.grails.datastore.mapping.query.Query.ProjectionList()
+
+        when:
+        invoker.invokeMethod("count", [closure] as Object[])
+
+        then:
+        1 * builder.setCount(true)
+        1 * builder.isUniqueResult() >> false
+        1 * builder.isCount() >> true
+        1 * query.projections() >> projectionList
+        1 * query.singleResult() >> 0L
+        1 * builder.isParticipate() >> true
+    }
+
+    void "test invokeMethod handles listDistinct call"() {
+        given:
+        def closure = { eq("foo", "bar") }
+
+        when:
+        invoker.invokeMethod("listDistinct", [closure] as Object[])
+
+        then:
+        1 * builder.setDistinct(true)
+        1 * builder.isUniqueResult() >> false
+        1 * builder.isDistinct() >> true
+        1 * query.distinct()
+        1 * query.list() >> []
+        1 * builder.isParticipate() >> true
+    }
+
+    void "test invokeMethod handles pagination"() {
+        given:
+        def params = [max: 10, offset: 5]
+        def closure = { }
+
+        when:
+        invoker.invokeMethod("list", [params, closure] as Object[])
+
+        then:
+        _ * builder.isPaginationEnabledList() >> false // initially false
+        1 * builder.setPaginationEnabledList(true)
+        1 * query.maxResults(10)
+        1 * query.firstResult(5)
+        1 * builder.isUniqueResult() >> false
+        _ * builder.isPaginationEnabledList() >> true // then true
+    }
+
+    void "test invokeMethod handles criteria methods"() {
+        when:
+        invoker.invokeMethod("eq", ["prop", "value"] as Object[])
+
+        then:
+        _ * builder.isPaginationEnabledList() >> false
+        _ * builder.getMetaClass() >> 
GroovySystem.metaClassRegistry.getMetaClass(HibernateCriteriaBuilder)
+        1 * builder.eq("prop", "value")
+    }
+
+    void "test invokeMethod handles projections block"() {
+        given:
+        def closure = { sum("balance") }
+
+        when:
+        invoker.invokeMethod("projections", [closure] as Object[])
+
+        then:
+        _ * builder.isPaginationEnabledList() >> false
+        1 * builder.getHibernateQuery() >> query
+        // The projections block calls invokeClosureNode which delegates to 
the builder
+    }
+
+    void "test invokeMethod handles association query"() {
+        given:
+        def closure = { eq("amount", 10) }
+
+        when:
+        invoker.invokeMethod("transactions", [closure] as Object[])
+
+        then:
+        _ * builder.isPaginationEnabledList() >> false
+        _ * builder.getTargetClass() >> InvokerAccount
+        1 * builder.getSessionFactory() >> Mock(SessionFactory) {
+            getMetamodel() >> Mock(jakarta.persistence.metamodel.Metamodel) {
+                entity(InvokerAccount) >> 
Mock(jakarta.persistence.metamodel.EntityType) {
+                    getAttribute("transactions") >> 
Mock(jakarta.persistence.metamodel.Attribute) {
+                        isAssociation() >> true
+                    }
+                }
+            }
+        }
+        1 * builder.getClassForAssociationType(_) >> InvokerTransaction
+        1 * query.join("transactions", _)
+        1 * query.in("transactions", _)
+    }
+
+    void "test invokeMethod handles and/or/not junctions"() {
+        given:
+        def closure = { eq("foo", "bar") }
+
+        when:
+        invoker.invokeMethod("and", [closure] as Object[])
+        invoker.invokeMethod("or", [closure] as Object[])
+        invoker.invokeMethod("not", [closure] as Object[])
+
+        then:
+        1 * builder.and(closure)
+        1 * builder.or(closure)
+        1 * builder.not(closure)
+    }
+
+    void "test invokeMethod throws MissingMethodException"() {
+        when:
+        invoker.invokeMethod("nonExistent", [] as Object[])
+
+        then:
+        _ * builder.isPaginationEnabledList() >> false
+        _ * builder.getMetaClass() >> 
GroovySystem.metaClassRegistry.getMetaClass(HibernateCriteriaBuilder)
+        thrown(MissingMethodException)
+    }
+}
+
+class InvokerAccount {
+    String firstName
+    Set<InvokerTransaction> transactions
+}
+class InvokerTransaction {}
+


Reply via email to