This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch merge-hibernate6 in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit 773f97c652b647bd1fafceceb09ad8f84b4dcbe7 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Sat Jun 21 17:26:52 2025 -0500 Heavy refactoring --- .../hibernate/query/AbstractHibernateQuery.java | 147 +++++++-------------- .../orm/hibernate/query/AliasMapEntryFunction.java | 16 +++ .../query/DetachedAssociationFunction.java | 30 +++++ .../orm/hibernate/query/JpaFromProvider.java | 105 +++++++++++++++ .../orm/hibernate/query/ProjectionPredicate.java | 47 +++++++ 5 files changed, 245 insertions(+), 100 deletions(-) diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java index c165cd758c..9fedb54bed 100644 --- a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AbstractHibernateQuery.java @@ -38,6 +38,7 @@ import org.grails.orm.hibernate.IHibernateTemplate; import org.hibernate.NonUniqueResultException; import org.hibernate.SessionFactory; import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.hibernate.query.criteria.JpaCriteriaQuery; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.transform.ResultTransformer; import org.springframework.core.convert.ConversionService; @@ -489,43 +490,19 @@ public abstract class AbstractHibernateQuery extends Query { } protected org.hibernate.query.Query createQuery() { - HibernateCriteriaBuilder cb = getCriteriaBuilder(); - - - List<DetachedAssociationCriteria> detachedAssociationCriteria = getDetachedAssociationCriteria(); - - Map<String, DetachedAssociationCriteria> aliasMap = detachedAssociationCriteria.stream() - .collect(Collectors.toMap( - DetachedAssociationCriteria::getAssociationPath, - criteria ->criteria, (oldValue,newValue) -> newValue) - ); + var projectionList = collectProjections(); + var cq = createCriteriaQuery(projectionList); + From root = cq.from(entity.getJavaClass()); + var jpaFromProvider = new JpaFromProvider(detachedCriteria); + Map<String, From> tablesByName = jpaFromProvider.getFromsByName(cq, root); - List<Projection> projections = collectProjections(); + assignProjections(projectionList, cq, tablesByName); List<GroupPropertyProjection> groupProjections = collectGroupProjections(); - - List<String> joinColumns = Stream.concat(aliasMap.keySet().stream(), collectJoinColumns().stream()).distinct().toList(); - CriteriaQuery cq = projections.stream() - .filter( it -> !(it instanceof DistinctProjection || it instanceof DistinctPropertyProjection)) - .toList().size() > 1 ? cb.createQuery(Object[].class) : cb.createQuery(Object.class); - projections.stream() - .filter( it -> it instanceof DistinctProjection || it instanceof DistinctPropertyProjection) - .findFirst() - .ifPresent(projection -> { - cq.distinct(true); - }); - From root = cq.from(entity.getJavaClass()); - Map<String, From> fromMap = detachedAssociationCriteria.stream() - .collect(Collectors.toMap( - DetachedAssociationCriteria::getAssociationPath, - criteria -> cq.from(criteria.getAssociation().getOwner().getJavaClass()) , (oldValue,newValue) -> newValue) - ); - fromMap.put("root", root); - Map<String, From> tablesByName = assignJoinTables(joinColumns, root,aliasMap, fromMap); - assignProjections(projections, cb, root, cq, tablesByName); assignGroupBy(groupProjections, root, cq, tablesByName); - assignOrderBy(cq, cb, tablesByName); + var cb = getCriteriaBuilder(); + assignOrderBy(cq, tablesByName); assignCriteria(cq, cb, root,tablesByName); org.hibernate.query.Query query = getSessionFactory() @@ -545,26 +522,38 @@ public abstract class AbstractHibernateQuery extends Query { return query; } + + + @SuppressWarnings("unchecked") + private Map<String, JoinType> getDetachedCriteriaJoinTypes() { + return detachedCriteria.getJoinTypes(); + } + + private JpaCriteriaQuery<?> createCriteriaQuery(List<Projection> projections) { + var cb = getCriteriaBuilder(); + var cq = projections.stream() + .filter( it -> !(it instanceof DistinctProjection || it instanceof DistinctPropertyProjection)) + .toList().size() > 1 ? cb.createQuery(Object[].class) : cb.createQuery(Object.class); + projections.stream() + .filter( it -> it instanceof DistinctProjection || it instanceof DistinctPropertyProjection) + .findFirst() + .ifPresent(projection -> { + cq.distinct(true); + }); + return cq; + } + + @SuppressWarnings("unchecked") + private List<Query.Criterion> getDetachedCriteria() { + return detachedCriteria.getCriteria(); + } + private List<DetachedAssociationCriteria> getDetachedAssociationCriteria() { - List<DetachedAssociationCriteria> detachedAssociationCriteria = detachedCriteria.getCriteria() + return getDetachedCriteria() .stream() - .map(o -> { - if (o instanceof In c && Objects.nonNull(c.getSubquery()) ) { - return c.getSubquery().getCriteria(); - } else if (o instanceof Exists c && Objects.nonNull(c.getSubquery()) ) { - return c.getSubquery().getCriteria(); - } else if (o instanceof NotExists c && Objects.nonNull(c.getSubquery()) ) { - return c.getSubquery().getCriteria(); - } else if (o instanceof SubqueryCriterion c && Objects.nonNull(c.getValue()) ) { - return c.getValue().getCriteria(); - } - return List.of(o); - }) - .flatMap(list -> ((List) list).stream()) - .filter(DetachedAssociationCriteria.class::isInstance) - .map(DetachedAssociationCriteria.class::cast) + .map(new DetachedAssociationFunction()) + .flatMap(List::stream) .toList(); - return detachedAssociationCriteria; } private List<String> collectJoinColumns() { @@ -587,22 +576,22 @@ public abstract class AbstractHibernateQuery extends Query { } private List<Projection> collectProjections() { - List<Projection> projections = projections().getProjectionList() + return projections().getProjectionList() .stream() - .filter(combinePredicates(projectionPredicates)) + .filter(new ProjectionPredicate()) .toList(); - return projections; } private void assignCriteria(CriteriaQuery cq, HibernateCriteriaBuilder cb, From root, Map<String, From> tablesByName) { - List<Criterion> criteriaList = (List<Criterion>)detachedCriteria.getCriteria(); + List<Criterion> criteriaList = getDetachedCriteria(); if (!criteriaList.isEmpty()) { jakarta.persistence.criteria.Predicate[] predicates = PredicateGenerator.getPredicates(cb, cq, root, criteriaList, tablesByName); cq.where(cb.and(predicates)); } } - private void assignOrderBy(CriteriaQuery cq, HibernateCriteriaBuilder cb, Map<String, From> tablesByName) { + private void assignOrderBy(CriteriaQuery cq, Map<String, From> tablesByName) { + var cb = getCriteriaBuilder(); List<Order> orders = detachedCriteria.getOrders(); if (!orders.isEmpty()) { cq.orderBy(orders @@ -643,10 +632,10 @@ public abstract class AbstractHibernateQuery extends Query { } } - private void assignProjections(List<Projection> projections, HibernateCriteriaBuilder cb, From root, CriteriaQuery cq, Map<String, From> tablesByName) { + private void assignProjections(List<Projection> projections, CriteriaQuery cq, Map<String, From> tablesByName) { List<Expression> projectionExpressions = projections .stream() - .map(projectionToJpaExpression(cb, tablesByName)) + .map(projectionToJpaExpression(tablesByName)) .filter(Objects::nonNull) .map(Expression.class::cast) .toList(); @@ -655,55 +644,13 @@ public abstract class AbstractHibernateQuery extends Query { } else if (projectionExpressions.size() > 1){ cq.multiselect(projectionExpressions); } else { - cq.select(root); - } - } - - private Map<String, From> assignJoinTables(List<String> joinColumns, From root, Map<String,DetachedAssociationCriteria> aliasMap, Map<String, From> fromMap) { - Map<String, JoinType> joinTypes = detachedCriteria.getJoinTypes(); - //The join column is column for joining from the root entity - Map<String, From> tablesByName = joinColumns.stream().map(joinColumn -> { - JoinType joinType = joinTypes.entrySet() - .stream() - .filter(entry -> entry.getKey().equals(joinColumn)) - .map(Map.Entry::getValue) - .findFirst() - .orElse(JoinType.INNER); - From from = fromMap.computeIfAbsent(joinColumn, s -> fromMap.get("root")); - Join table = from.join(joinColumn, joinType); - String column = aliasColumn(aliasMap, joinColumn, table); - return new AbstractMap.SimpleEntry<>(column, table); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - tablesByName.put("root", root); - return tablesByName; - } - - private static String aliasColumn(Map<String, DetachedAssociationCriteria> aliasMap, String associationPath, Join table) { - // Attempt to find specific criteria configuration for this association path - DetachedAssociationCriteria criteria = aliasMap.get(associationPath); - - if (criteria != null) { - // If criteria configuration exists: - // Determine the alias: use the one from criteria if it's not null, - // otherwise default back to using the associationPath itself. - String aliasToUse = Objects.requireNonNullElse(criteria.getAlias(), associationPath); - - // Apply the determined alias explicitly to the Join object - table.alias(aliasToUse); - - // Return the alias that was determined and applied - return aliasToUse; - } else { - // If no specific criteria configuration was found, - // return the original associationPath as the implicit alias. - // We don't explicitly call table.alias() here, letting JPA/Hibernate handle defaults. - return associationPath; + cq.select(tablesByName.get("root")); } } private Function<Projection, JpaExpression> projectionToJpaExpression( - HibernateCriteriaBuilder cb, Map<String, From> tablesByName) { + var cb = getCriteriaBuilder(); return projection -> { if (countProjectionPredicate.test(projection)) { return cb.count(tablesByName.get("root")); diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AliasMapEntryFunction.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AliasMapEntryFunction.java new file mode 100644 index 0000000000..458f916c86 --- /dev/null +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/AliasMapEntryFunction.java @@ -0,0 +1,16 @@ +package org.grails.orm.hibernate.query; + +import org.grails.datastore.gorm.query.criteria.DetachedAssociationCriteria; + +import java.util.Map; +import java.util.function.Function; + +public class AliasMapEntryFunction + implements + Function<DetachedAssociationCriteria, + Map.Entry<String, DetachedAssociationCriteria>> { + @Override + public Map.Entry<String, DetachedAssociationCriteria> apply(DetachedAssociationCriteria detachedAssociationCriteria) { + return Map.entry(detachedAssociationCriteria.getAssociationPath(), detachedAssociationCriteria); + } +} diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/DetachedAssociationFunction.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/DetachedAssociationFunction.java new file mode 100644 index 0000000000..186d73dbe3 --- /dev/null +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/DetachedAssociationFunction.java @@ -0,0 +1,30 @@ +package org.grails.orm.hibernate.query; + +import org.grails.datastore.gorm.query.criteria.DetachedAssociationCriteria; +import org.grails.datastore.mapping.query.Query; + +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +public class DetachedAssociationFunction implements Function<Query.Criterion, List<DetachedAssociationCriteria>> { + @Override + public List<DetachedAssociationCriteria> apply(Query.Criterion o) { + List<Query.Criterion> criteria; + if (o instanceof Query.In c && Objects.nonNull(c.getSubquery()) ) { + criteria = c.getSubquery().getCriteria(); + } else if (o instanceof Query.Exists c && Objects.nonNull(c.getSubquery()) ) { + criteria = c.getSubquery().getCriteria(); + } else if (o instanceof Query.NotExists c && Objects.nonNull(c.getSubquery()) ) { + criteria = c.getSubquery().getCriteria(); + } else if (o instanceof Query.SubqueryCriterion c && Objects.nonNull(c.getValue()) ) { + criteria = c.getValue().getCriteria(); + } else { + criteria = List.of(o); + } + return criteria.stream() + .filter(DetachedAssociationCriteria.class::isInstance) + .map(DetachedAssociationCriteria.class::cast) + .toList(); + } +} diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java new file mode 100644 index 0000000000..8038900800 --- /dev/null +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java @@ -0,0 +1,105 @@ +package org.grails.orm.hibernate.query; + +import grails.gorm.DetachedCriteria; +import jakarta.persistence.FetchType; +import jakarta.persistence.criteria.From; +import jakarta.persistence.criteria.JoinType; +import org.grails.datastore.gorm.query.criteria.DetachedAssociationCriteria; +import org.grails.datastore.mapping.query.Query; +import org.hibernate.query.criteria.JpaCriteriaQuery; + +import java.util.AbstractMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class JpaFromProvider { + + private final DetachedCriteria detachedCriteria; + + public JpaFromProvider(DetachedCriteria detachedCriteria) { + this.detachedCriteria = detachedCriteria; + } + + public Map<String, From> getFromsByName(JpaCriteriaQuery<?> cq, From root) { + var detachedAssociationCriteriaList = getDetachedAssociationCriteria(); + + var aliasMap = createAliasMap(detachedAssociationCriteriaList); + //The join column is column for joining from the root entity + var detachedFroms = createDetachedFroms(cq, detachedAssociationCriteriaList); + Map<String, From> fromsByName = Stream.concat(aliasMap.keySet().stream(), collectJoinColumns().stream()) + .distinct() + .map(joinColumn -> { + var table = detachedFroms.computeIfAbsent(joinColumn, s -> root).join(joinColumn, getJoinType(joinColumn)); + // Attempt to find specific criteria configuration for this association path + var column = Optional.ofNullable(aliasMap.get(joinColumn)) + .map(detachedAssociationCriteria -> Objects.requireNonNullElse(detachedAssociationCriteria.getAlias(), joinColumn)) + .orElse(joinColumn); + table.alias(column); + return new AbstractMap.SimpleEntry<>(column, table); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + fromsByName.put("root", root); + return fromsByName; + } + + private JoinType getJoinType(String joinColumn) { + return getDetachedCriteriaJoinTypes() + .entrySet() + .stream() + .filter(entry -> entry.getKey().equals(joinColumn)) + .map(Map.Entry::getValue) + .findFirst() + .orElse(JoinType.INNER); + } + + private Map<String, From> createDetachedFroms(JpaCriteriaQuery<?> cq, List<DetachedAssociationCriteria> detachedAssociationCriteriaList) { + return detachedAssociationCriteriaList.stream() + .collect(Collectors.toMap( + DetachedAssociationCriteria::getAssociationPath, + criteria -> cq.from(criteria.getAssociation().getOwner().getJavaClass()) , (oldValue, newValue) -> newValue) + ); + } + + private Map<String, DetachedAssociationCriteria> createAliasMap(List<DetachedAssociationCriteria> detachedAssociationCriteriaList) { + return detachedAssociationCriteriaList.stream() + .map(new AliasMapEntryFunction()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @SuppressWarnings("unchecked") + private List<Query.Criterion> getDetachedCriteria() { + return detachedCriteria.getCriteria(); + } + + private List<DetachedAssociationCriteria> getDetachedAssociationCriteria() { + return getDetachedCriteria() + .stream() + .map(new DetachedAssociationFunction()) + .flatMap(List::stream) + .toList(); + } + + private List<String> collectJoinColumns() { + return getDetachedCriteriaFetchStrategies() + .entrySet() + .stream() + .filter(entry -> entry.getValue().equals(FetchType.EAGER)) + .map(Map.Entry::getKey) + .toList(); + } + + @SuppressWarnings("unchecked") + private Map<String, JoinType> getDetachedCriteriaJoinTypes() { + return detachedCriteria.getJoinTypes(); + } + + @SuppressWarnings("unchecked") + private Map<String,FetchType> getDetachedCriteriaFetchStrategies() { + return detachedCriteria.getFetchStrategies(); + } + + +} diff --git a/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/ProjectionPredicate.java b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/ProjectionPredicate.java new file mode 100644 index 0000000000..123daa609f --- /dev/null +++ b/grails-data-hibernate6/core/src/main/groovy/org/grails/orm/hibernate/query/ProjectionPredicate.java @@ -0,0 +1,47 @@ +package org.grails.orm.hibernate.query; + +import org.grails.datastore.mapping.query.Query; + +import java.util.Arrays; +import java.util.function.Predicate; + +public class ProjectionPredicate + implements Predicate<Query.Projection> { + + @Override + public boolean test(Query.Projection projection) { + return combinePredicates(projectionPredicates).test(projection); + } + + private final Predicate<Query.Projection> idProjectionPredicate = projection -> projection instanceof Query.IdProjection; + private final Predicate<Query.Projection> distinctProjectionPredicate = projection -> projection instanceof Query.DistinctProjection; + private final Predicate<Query.Projection> countProjectionPredicate = projection -> projection instanceof Query.CountProjection; + private final Predicate<Query.Projection> countDistinctProjection = projection -> projection instanceof Query.CountDistinctProjection; + private final Predicate<Query.Projection> maxProjectionPredicate = projection -> projection instanceof Query.MaxProjection; + private final Predicate<Query.Projection> minProjectionPredicate = projection -> projection instanceof Query.MinProjection; + private final Predicate<Query.Projection> sumProjectionPredicate = projection -> projection instanceof Query.SumProjection; + private final Predicate<Query.Projection> avgProjectionPredicate = projection -> projection instanceof Query.AvgProjection; + private final Predicate<Query.Projection> propertyProjectionPredicate = projection -> projection instanceof Query.PropertyProjection; + + @SuppressWarnings("unchecked") + Predicate<Query.Projection>[] projectionPredicates = new Predicate[] { + idProjectionPredicate + , propertyProjectionPredicate + , countProjectionPredicate + , countDistinctProjection + , maxProjectionPredicate + , minProjectionPredicate + , sumProjectionPredicate + , avgProjectionPredicate + , distinctProjectionPredicate + } ; + + @SafeVarargs + private static <T> Predicate<T> combinePredicates(Predicate<T>... predicates) { + return Arrays.stream(predicates) + .reduce(Predicate::or) + .orElse(x -> true); + } + + +}
