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 {} +
