diff --git a/grails-datastore-gorm-hibernate/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java b/grails-datastore-gorm-hibernate/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java index 110563d6..9ac65d95 100644 --- a/grails-datastore-gorm-hibernate/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java +++ b/grails-datastore-gorm-hibernate/src/main/groovy/grails/orm/HibernateCriteriaBuilder.java @@ -15,25 +15,28 @@ */ package grails.orm; +import grails.gorm.DetachedCriteria; import groovy.lang.GroovySystem; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.PluralAttribute; -import org.grails.datastore.mapping.query.api.Criteria; +import org.grails.datastore.mapping.model.PersistentEntity; import org.grails.orm.hibernate.GrailsHibernateTemplate; import org.grails.orm.hibernate.HibernateDatastore; import org.grails.orm.hibernate.query.AbstractHibernateCriteriaBuilder; +import org.grails.orm.hibernate.query.AbstractHibernateQuery; +import org.grails.orm.hibernate.query.HibernateQuery; import org.hibernate.SessionFactory; import org.hibernate.type.BasicTypeReference; import org.hibernate.type.StandardBasicTypes; import org.springframework.orm.hibernate5.SessionHolder; import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.grails.datastore.mapping.query.api.QueryableCriteria; import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; -import java.net.URL; import java.sql.Blob; import java.sql.Clob; import java.util.Calendar; @@ -154,12 +157,37 @@ protected void createCriteriaInstance() { else { hibernateSession = sessionFactory.openSession(); } - criteria = hibernateSession.getCriteriaBuilder().createQuery(targetClass); + criteriaQuery = hibernateSession.getCriteriaBuilder().createQuery(targetClass); + root = criteriaQuery.from(targetClass); cacheCriteriaMapping(); - criteriaMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(criteria.getClass()); + criteriaMetaClass = GroovySystem.getMetaClassRegistry().getMetaClass(criteriaQuery.getClass()); } } + protected DetachedCriteria convertToHibernateCriteria(QueryableCriteria queryableCriteria) { + return getHibernateDetachedCriteria(new HibernateQuery(null, queryableCriteria.getPersistentEntity()), queryableCriteria); + } + + public static DetachedCriteria getHibernateDetachedCriteria(AbstractHibernateQuery query, QueryableCriteria queryableCriteria) { + String alias = queryableCriteria.getAlias(); + return getHibernateDetachedCriteria(query, queryableCriteria, alias); + } + + public static DetachedCriteria getHibernateDetachedCriteria(AbstractHibernateQuery query, QueryableCriteria queryableCriteria, String alias) { + PersistentEntity persistentEntity = queryableCriteria.getPersistentEntity(); + DetachedCriteria detachedCriteria = new DetachedCriteria(persistentEntity.getJavaClass()); + + populateHibernateDetachedCriteria(new HibernateQuery(null,persistentEntity), detachedCriteria, queryableCriteria); + return detachedCriteria; + } + + private static void populateHibernateDetachedCriteria(AbstractHibernateQuery query, DetachedCriteria detachedCriteria, QueryableCriteria queryableCriteria) { + List criteriaList = queryableCriteria.getCriteria(); + for (org.grails.datastore.mapping.query.Query.Criterion criterion : criteriaList) { + detachedCriteria.add(criterion); + } + } + protected void cacheCriteriaMapping() { } diff --git a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateCriteriaBuilder.java b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateCriteriaBuilder.java index 0f731990..0ec38241 100644 --- a/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateCriteriaBuilder.java +++ b/grails-datastore-gorm-hibernate/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateCriteriaBuilder.java @@ -1,5 +1,6 @@ package org.grails.orm.hibernate.query; +import grails.gorm.DetachedCriteria; import grails.gorm.MultiTenant; import groovy.lang.Closure; import groovy.lang.DelegatesTo; @@ -9,9 +10,10 @@ import groovy.lang.MissingMethodException; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.JoinType; +import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; +import jakarta.persistence.criteria.Subquery; import jakarta.persistence.metamodel.Attribute; -import jakarta.persistence.metamodel.EntityType; import org.grails.datastore.mapping.multitenancy.MultiTenancySettings; import org.grails.datastore.mapping.query.Query; import org.grails.datastore.mapping.query.Restrictions; @@ -21,25 +23,25 @@ import org.grails.datastore.mapping.reflect.NameUtils; import org.grails.orm.hibernate.AbstractHibernateDatastore; import org.hibernate.FetchMode; -import org.hibernate.HibernateException; -import org.hibernate.Metamodel; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.query.criteria.JpaPredicate; +import org.hibernate.query.criteria.JpaRoot; import org.hibernate.transform.ResultTransformer; -import org.hibernate.type.Type; import org.springframework.beans.BeanUtils; import org.springframework.core.convert.ConversionService; -import org.grails.datastore.mapping.query.api.Criteria; import org.grails.datastore.mapping.query.api.ProjectionList; import java.beans.PropertyDescriptor; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Abstract super class for sharing code between Hibernate 3 and 4 implementations of HibernateCriteriaBuilder @@ -92,7 +94,7 @@ public abstract class AbstractHibernateCriteriaBuilder extends GroovyObjectSuppo protected Session hibernateSession; protected Class targetClass; protected Query.Junction junction = new Query.Conjunction(); - protected CriteriaQuery criteria; + protected CriteriaQuery criteriaQuery; protected MetaClass criteriaMetaClass; protected boolean uniqueResult = false; protected List logicalExpressionStack = new ArrayList(); @@ -112,14 +114,15 @@ public abstract class AbstractHibernateCriteriaBuilder extends GroovyObjectSuppo protected AbstractHibernateDatastore datastore; protected HibernateCriteriaBuilder cb; protected Root root; + protected Subquery subquery; @SuppressWarnings("rawtypes") public AbstractHibernateCriteriaBuilder(Class targetClass, SessionFactory sessionFactory) { this.targetClass = targetClass; this.sessionFactory = sessionFactory; this.cb = sessionFactory.getCriteriaBuilder(); - this.criteria = cb.createQuery(targetClass); - this.root = criteria.from(targetClass); + this.criteriaQuery = cb.createQuery(targetClass); + this.root = criteriaQuery.from(targetClass); } @SuppressWarnings("rawtypes") @@ -128,8 +131,8 @@ public AbstractHibernateCriteriaBuilder(Class targetClass, SessionFactory sessio this.sessionFactory = sessionFactory; this.uniqueResult = uniqueResult; this.cb = sessionFactory.getCriteriaBuilder(); - this.criteria = cb.createQuery(targetClass); - this.root = criteria.from(targetClass); + this.criteriaQuery = cb.createQuery(targetClass); + this.root = criteriaQuery.from(targetClass); } public void setDatastore(AbstractHibernateDatastore datastore) { @@ -157,7 +160,7 @@ public ProjectionList property(String propertyName) { * @param alias The alias to use */ public ProjectionList property(String propertyName, String alias) { - criteria.select(root.get(propertyName)); + criteriaQuery.select(root.get(propertyName)); return this; } @@ -176,7 +179,7 @@ public ProjectionList distinct(String propertyName) { * @param alias The alias to use */ public ProjectionList distinct(String propertyName, String alias) { - criteria.select(root.get(propertyName)).distinct(true); + projectionList.distinct(propertyName); return this; } @@ -287,6 +290,7 @@ protected Object calculatePropertyValue(Object propertyValue) { return propertyValue; } + protected abstract DetachedCriteria convertToHibernateCriteria(QueryableCriteria queryableCriteria); /** * Adds a projection that allows the criteria to return the property count @@ -648,17 +652,18 @@ public Criteria in(String propertyName, QueryableCriteria subquery) { @Override public Criteria inList(String propertyName, QueryableCriteria subquery) { + addToCriteria(Restrictions.in(propertyName, subquery)); return this; } @Override public Criteria in(String propertyName, Closure subquery) { - return inList(propertyName, new grails.gorm.DetachedCriteria(targetClass).build(subquery)); + return inList(propertyName, new DetachedCriteria(targetClass).build(subquery)); } @Override public Criteria inList(String propertyName, Closure subquery) { - return inList(propertyName, new grails.gorm.DetachedCriteria(targetClass).build(subquery)); + return inList(propertyName, new DetachedCriteria(targetClass).build(subquery)); } @Override @@ -1094,7 +1099,7 @@ public Criteria gte(String s, Object o) { } protected boolean validateSimpleExpression() { - return criteria != null; + return criteriaQuery != null; } @Override @@ -1121,6 +1126,29 @@ public Object get(@DelegatesTo(Criteria.class) Closure c) { public Object scroll(@DelegatesTo(Criteria.class) Closure c) { return invokeMethod(SCROLL_CALL, new Object[]{c}); } + + protected Predicate[] getPredicates(Root root_ , List criteriaList) { + return criteriaList.stream(). + map(criterion -> { + if (criterion instanceof Query.IsNotNull c) { + return cb.isNotNull(root_.get(c.getProperty())); + } else if (criterion instanceof Query.Equals c ) { + return cb.equal(root_.get(c.getProperty()),c.getValue()); + } else if (criterion instanceof Query.In c + && c.getSubquery().getProjections().size() == 1 + && c.getSubquery().getProjections().get(0) instanceof Query.PropertyProjection + ) { + Query.PropertyProjection projection = (Query.PropertyProjection) c.getSubquery().getProjections().get(0); + boolean distinct = projection instanceof Query.DistinctPropertyProjection; + subquery = criteriaQuery.subquery(Long.class); + Root from = subquery.from(c.getSubquery().getPersistentEntity().getJavaClass()); + Predicate[] predicates = getPredicates(from, c.getSubquery().getCriteria()); + subquery.select(from.get(projection.getPropertyName())).distinct(distinct).where(cb.and(predicates)); + return cb.in(root_.get(c.getProperty())).value(subquery); + } + return null; + }).filter(Objects::nonNull).toList().toArray(new Predicate[0]); + } @SuppressWarnings("rawtypes") @Override @@ -1134,9 +1162,9 @@ public Object invokeMethod(String name, Object obj) { } if (isCriteriaConstructionMethod(name, args)) { - if (criteria != null) { - throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); - } +// if (criteria != null) { +// throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); +// } if (name.equals(GET_CALL)) { uniqueResult = true; @@ -1166,16 +1194,19 @@ else if (name.equals(COUNT_CALL)) { // criteria.setResultTransformer(resultTransformer); // } Object result; - org.hibernate.query.Query query = hibernateSession.createQuery(criteria); if (!uniqueResult) { - if (scroll) { - - result = query.scroll(); + root = criteriaQuery.from(this.targetClass); + criteriaQuery.select(root); + criteriaQuery.where(cb.and(getPredicates(root, this.junction.getCriteria()))); + result = hibernateSession.createQuery(criteriaQuery).scroll(); } else if (count) { - criteria.select(cb.count(root)); - result = query.getSingleResult(); + criteriaQuery = cb.createQuery(Long.class); + root = criteriaQuery.from(this.targetClass); + criteriaQuery.select(cb.count(root)); + criteriaQuery.where(cb.and(getPredicates(root,this.junction.getCriteria()))); + result = hibernateSession.createQuery(criteriaQuery).getSingleResult(); } // else if (paginationEnabledList) { // // Calculate how many results there are in total. This has been @@ -1232,11 +1263,20 @@ else if (count) { // result = createPagedResultList(argMap); // } else { - result = query.list(); + String queryString = "select e2_0.id,e2_0.field1,e2_0.version from entity1 e1_0,entity1 e2_0 where e2_0.id in (select distinct de1_0.entity_id from detached_entity de1_0 where de1_0.field='abc') "; + List resultList = hibernateSession.createNativeQuery(queryString, targetClass).getResultList(); + root = criteriaQuery.from(this.targetClass); + criteriaQuery.select(root); + + criteriaQuery.where(cb.and(getPredicates(root, this.junction.getCriteria()))); + result = hibernateSession.createQuery(criteriaQuery).list(); } } else { - result = query.uniqueResult(); + root = criteriaQuery.from(this.targetClass); + criteriaQuery.select(root); + criteriaQuery.where(cb.and(getPredicates(root, this.junction.getCriteria()))); + result = hibernateSession.createQuery(criteriaQuery).uniqueResult(); } if (!participate) { closeSession(); @@ -1244,7 +1284,7 @@ else if (count) { return result; } - if (criteria == null) createCriteriaInstance(); + if (criteriaQuery == null) createCriteriaInstance(); MetaMethod metaMethod = getMetaClass().getMetaMethod(name, args); if (metaMethod != null) { @@ -1253,18 +1293,18 @@ else if (count) { metaMethod = criteriaMetaClass.getMetaMethod(name, args); if (metaMethod != null) { - return metaMethod.invoke(criteria, args); + return metaMethod.invoke(criteriaQuery, args); } metaMethod = criteriaMetaClass.getMetaMethod(NameUtils.getSetterName(name), args); if (metaMethod != null) { - return metaMethod.invoke(criteria, args); + return metaMethod.invoke(criteriaQuery, args); } -// if (isAssociationQueryMethod(args) || isAssociationQueryWithJoinSpecificationMethod(args)) { -// final boolean hasMoreThanOneArg = args.length > 1; -// Object callable = hasMoreThanOneArg ? args[1] : args[0]; + if (isAssociationQueryMethod(args) || isAssociationQueryWithJoinSpecificationMethod(args)) { + final boolean hasMoreThanOneArg = args.length > 1; + Object callable = hasMoreThanOneArg ? args[1] : args[0]; // int joinType = hasMoreThanOneArg ? (Integer)args[0] : org.hibernate.sql.JoinType.INNER_JOIN.getJoinTypeValue(); -// + // if (name.equals(AND) || name.equals(OR) || name.equals(NOT)) { // if (criteria == null) { // throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); @@ -1278,22 +1318,22 @@ else if (count) { // // return name; // } -// -// if (name.equals(PROJECTIONS) && args.length == 1 && (args[0] instanceof Closure)) { + + if (name.equals(PROJECTIONS) && args.length == 1 && (args[0] instanceof Closure)) { // if (criteria == null) { // throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); // } -// -// projectionList = Projections.projectionList(); -// invokeClosureNode(callable); -// + + projectionList = new Query.ProjectionList(); + invokeClosureNode(callable); + // if (projectionList != null && projectionList.getLength() > 0) { // criteria.setProjection(projectionList); // } -// -// return name; -// } -// + + return name; + } + // final PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(targetClass, name); // if (pd != null && pd.getReadMethod() != null) { // final Metamodel metamodel = sessionFactory.getMetamodel(); @@ -1340,18 +1380,16 @@ else if (count) { // return name; // } // } -// } + } else if (args.length == 1 && args[0] != null) { - if (criteria == null) { - throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); - } - +// if (criteriaQuery == null) { +// throwRuntimeException(new IllegalArgumentException("call to [" + name + "] not supported here")); +// } Object value = args[0]; Query.Criterion c = null; if (name.equals(ID_EQUALS)) { return eq("id", value); } - if (name.equals(IS_NULL) || name.equals(IS_NOT_NULL) || name.equals(IS_EMPTY) || @@ -1374,18 +1412,14 @@ else if (name.equals(IS_NOT_EMPTY)) { c = Restrictions.isNotEmpty(propertyName); } } - if (c != null) { return addToCriteria(c); } } - throw new MissingMethodException(name, getClass(), args); } - - protected abstract List createPagedResultList(Map args); @@ -1398,8 +1432,6 @@ private boolean isAssociationQueryWithJoinSpecificationMethod(Object[] args) { } - - private String getAssociationPath() { StringBuilder fullPath = new StringBuilder(); for (Object anAssociationStack : associationStack) { @@ -1464,7 +1496,7 @@ protected Query.Criterion addToCriteria(Query.Criterion c) { * @return The criteria instance */ public CriteriaQuery getInstance() { - return criteria; + return criteriaQuery; } /** @@ -1504,7 +1536,7 @@ protected void throwRuntimeException(RuntimeException t) { private void closeSessionFollowingException() { closeSession(); - criteria = null; + criteriaQuery = null; } /** diff --git a/grails-datastore-gorm-hibernate/src/test/groovy/grails/gorm/tests/DetachedCriteriaProjectionSpec.groovy b/grails-datastore-gorm-hibernate/src/test/groovy/grails/gorm/tests/DetachedCriteriaProjectionSpec.groovy index d8ae2b0f..f9814bca 100644 --- a/grails-datastore-gorm-hibernate/src/test/groovy/grails/gorm/tests/DetachedCriteriaProjectionSpec.groovy +++ b/grails-datastore-gorm-hibernate/src/test/groovy/grails/gorm/tests/DetachedCriteriaProjectionSpec.groovy @@ -25,10 +25,10 @@ class DetachedCriteriaProjectionSpec extends Specification { def setup() { DetachedEntity.findAll().each { it.delete() } Entity1.findAll().each { it.delete(flush: true) } - final entity1 = new Entity1(id: 1, field1: 'Correct', version: 0).save() - new Entity1(id: 2, field1: 'Incorrect', version: 0).save() - new DetachedEntity(id: 1, entityId: entity1.id, field: 'abc', version: 0).save() - new DetachedEntity(id: 2, entityId: entity1.id, field: 'def', version: 0).save() + final entity1 = new Entity1(field1: 'Correct').save() + new Entity1(field1: 'Incorrect', version: 0).save() + new DetachedEntity(entityId: entity1.id, field: 'abc').save() + new DetachedEntity(entityId: entity1.id, field: 'def').save() } @Rollback