You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/02/19 21:54:27 UTC
(grails-core) 24/25: Extract CriteriaMethodInvoker from HibernateCriteriaBuilder
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 <wb...@mac.com>
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 {}
+