This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch 8.0.x-hibernate7-dev in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit c28ebf1321e6bb66f37631ff009eba1c0c1c4f7b Author: Walter Duque de Estrada <[email protected]> AuthorDate: Fri Mar 20 14:33:30 2026 -0500 hibernate 7: 1. Enables the DSL: Adds createAlias to HibernateCriteriaBuilder and the CriteriaMethodInvoker infrastructure. 2. Solves the "Basic Collection" Gap: Introduces the HibernateAlias metadata class to handle aliasing for basic collections (like Set<String>), which GORM traditionally handled differently than standard associations. 3. Bypasses H7 Restrictions: Updates JpaFromProvider and PredicateGenerator to use these aliases to resolve the "multivalued path" errors that Hibernate 7 throws when seeing collections in IN clauses. 4. Ensures Validation: Fixes the ConfigurationException so the query engine recognizes these aliases as valid paths. --- .../grails/orm/HibernateCriteriaBuilder.java | 4 ++-- .../grails/orm/hibernate/query/HibernateQuery.java | 11 ++++++++++- .../hibernate/query/JpaCriteriaQueryCreator.java | 18 +++++++++++++++-- .../orm/hibernate/query/JpaFromProvider.java | 20 +++++++++++++------ .../orm/hibernate/query/PredicateGenerator.java | 5 ++++- .../gorm/specs/BasicCollectionInQuerySpec.groovy | 2 +- .../specs/hibernatequery/HibernateQuerySpec.groovy | 12 +++++++++++ .../JpaCriteriaQueryCreatorSpec.groovy | 23 ++++++++++++++++++++++ 8 files changed, 82 insertions(+), 13 deletions(-) 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 5d293f45cf..fd07b327d2 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 @@ -171,7 +171,7 @@ public class HibernateCriteriaBuilder extends GroovyObjectSupport implements Bui public org.grails.datastore.mapping.query.api.Criteria createAlias(String associationPath, String alias) { var prop = hibernateQuery.getEntity().getPropertyByName(associationPath); if (prop instanceof org.grails.datastore.mapping.model.types.Basic) { - hibernateQuery.getDetachedCriteria().add(new org.grails.orm.hibernate.query.HibernateAlias(associationPath, alias)); + hibernateQuery.addAlias(new org.grails.orm.hibernate.query.HibernateAlias(associationPath, alias)); return this; } hibernateQuery.getDetachedCriteria().createAlias(associationPath, alias); @@ -181,7 +181,7 @@ public class HibernateCriteriaBuilder extends GroovyObjectSupport implements Bui public org.grails.datastore.mapping.query.api.Criteria createAlias(String associationPath, String alias, int joinType) { var prop = hibernateQuery.getEntity().getPropertyByName(associationPath); if (prop instanceof org.grails.datastore.mapping.model.types.Basic) { - hibernateQuery.getDetachedCriteria().add(new org.grails.orm.hibernate.query.HibernateAlias(associationPath, alias)); + hibernateQuery.addAlias(new org.grails.orm.hibernate.query.HibernateAlias(associationPath, alias)); return this; } hibernateQuery.getDetachedCriteria().createAlias(associationPath, alias); 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 0c201a294e..d7a1cf2d38 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 @@ -82,6 +82,15 @@ public class HibernateQuery extends Query { protected Deque<Association> associationStack = new LinkedList<>(); protected DetachedCriteria<?> detachedCriteria; protected ProxyHandler proxyHandler = new HibernateProxyHandler(); + private final List<HibernateAlias> aliases = new java.util.ArrayList<>(); + + public List<HibernateAlias> getAliases() { + return Collections.unmodifiableList(aliases); + } + + public void addAlias(HibernateAlias alias) { + this.aliases.add(alias); + } private Integer fetchSize; private Integer timeout; @@ -428,7 +437,7 @@ public class HibernateQuery extends Query { public JpaCriteriaQuery<?> getJpaCriteriaQuery() { ConversionService conversionService = getSession().getMappingContext().getConversionService(); return new JpaCriteriaQueryCreator( - projections, getCriteriaBuilder(), entity, detachedCriteria, conversionService) + projections, getCriteriaBuilder(), entity, detachedCriteria, conversionService, this) .createQuery(); } diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaCriteriaQueryCreator.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaCriteriaQueryCreator.java index 05abe24f3b..59e5e4080a 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaCriteriaQueryCreator.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaCriteriaQueryCreator.java @@ -51,6 +51,7 @@ public class JpaCriteriaQueryCreator { private final PersistentEntity entity; private final DetachedCriteria<?> detachedCriteria; private final ConversionService conversionService; + private final HibernateQuery hibernateQuery; public JpaCriteriaQueryCreator( Query.ProjectionList projections, @@ -58,11 +59,22 @@ public class JpaCriteriaQueryCreator { PersistentEntity entity, DetachedCriteria<?> detachedCriteria, ConversionService conversionService) { + this(projections, criteriaBuilder, entity, detachedCriteria, conversionService, null); + } + + public JpaCriteriaQueryCreator( + Query.ProjectionList projections, + HibernateCriteriaBuilder criteriaBuilder, + PersistentEntity entity, + DetachedCriteria<?> detachedCriteria, + ConversionService conversionService, + HibernateQuery hibernateQuery) { this.projections = projections; this.criteriaBuilder = criteriaBuilder; this.entity = entity; this.detachedCriteria = detachedCriteria; this.conversionService = conversionService; + this.hibernateQuery = hibernateQuery; } public JpaCriteriaQuery<?> createQuery() { @@ -71,7 +83,8 @@ public class JpaCriteriaQueryCreator { var cq = createCriteriaQuery(projectionList); Class<?> javaClass = entity.getJavaClass(); Root<?> root = cq.from(javaClass); - var tablesByName = new JpaFromProvider(detachedCriteria, projectionList, root); + var tablesByName = new JpaFromProvider(detachedCriteria, projectionList, + hibernateQuery != null ? hibernateQuery.getAliases() : List.of(), root); assignProjections(projectionList, cq, tablesByName); assignGroupBy(cq, tablesByName); @@ -85,7 +98,8 @@ public class JpaCriteriaQueryCreator { var projectionList = collectProjections(); Class<?> javaClass = entity.getJavaClass(); Root<?> root = subquery.from(javaClass); - var tablesByName = new JpaFromProvider(detachedCriteria, projectionList, root); + var tablesByName = new JpaFromProvider(detachedCriteria, projectionList, + hibernateQuery != null ? hibernateQuery.getAliases() : List.of(), root); var aliasedProjections = new java.util.concurrent.atomic.AtomicInteger(0); var projectionExpressions = projectionList.stream() diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java index 58bf79ea26..d3cb2d9563 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/JpaFromProvider.java @@ -51,11 +51,16 @@ public class JpaFromProvider implements Cloneable { this.fromMap = new HashMap<>(fromMap); } + public JpaFromProvider(DetachedCriteria<?> detachedCriteria, List<Query.Projection> projections, From<?, ?> root) { + this(detachedCriteria, projections, List.of(), root); + } + public JpaFromProvider( DetachedCriteria<?> detachedCriteria, List<Query.Projection> projections, + List<HibernateAlias> aliases, From<?, ?> root) { - fromMap = getFromsByName(detachedCriteria, projections, root); + fromMap = getFromsByName(detachedCriteria, projections, aliases, root); } public JpaFromProvider( @@ -64,16 +69,21 @@ public class JpaFromProvider implements Cloneable { List<Query.Projection> projections, From<?, ?> root) { fromMap = new HashMap<>(parent.fromMap); - fromMap.putAll(getFromsByName(detachedCriteria, projections, root)); + fromMap.putAll(getFromsByName(detachedCriteria, projections, List.of(), root)); } public Map<String, From<?, ?>> getFromsByName() { return fromMap; } + public boolean hasAlias(String name) { + return fromMap.containsKey(name); + } + protected Map<String, From<?, ?>> getFromsByName( DetachedCriteria<?> detachedCriteria, List<Query.Projection> projections, + List<HibernateAlias> aliases, From<?, ?> root) { var detachedAssociationCriteriaList = detachedCriteria.getCriteria().stream() .map(new DetachedAssociationFunction()) @@ -84,10 +94,8 @@ public class JpaFromProvider implements Cloneable { // Also scan for HibernateAlias (basic collections) Map<String, String> basicAliasMap = new HashMap<>(); - for (Query.Criterion c : detachedCriteria.getCriteria()) { - if (c instanceof HibernateAlias ha) { - basicAliasMap.put(ha.getPath(), ha.getAlias()); - } + for (HibernateAlias ha : aliases) { + basicAliasMap.put(ha.getPath(), ha.getAlias()); } var definedAliases = detachedAssociationCriteriaList.stream() diff --git a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java index 16cfd1c1b3..2a4ba1f7d2 100644 --- a/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java +++ b/grails-data-hibernate7/core/src/main/groovy/org/grails/orm/hibernate/query/PredicateGenerator.java @@ -100,6 +100,8 @@ public class PredicateGenerator { } else if (criterion instanceof Query.NotExists c) { PersistentEntity childEntity = c.getSubquery().getPersistentEntity(); return cb.not(handleExists(cb, criteriaQuery, root, fromsByProvider, childEntity, new Query.Exists(c.getSubquery()))); + } else if (criterion instanceof HibernateAlias) { + return null; // Metadata only, handled by JpaFromProvider } throw new IllegalArgumentException("Unsupported criterion: " + criterion); } @@ -170,7 +172,8 @@ public class PredicateGenerator { Query.PropertyCriterion pc) { String propertyName = pc.getProperty(); - if (!"id".equals(propertyName) && !propertyName.contains(".") && entity.getPropertyByName(propertyName) == null) { + if (!"id".equals(propertyName) && !propertyName.contains(".") && + entity.getPropertyByName(propertyName) == null && !fromsByProvider.hasAlias(propertyName)) { throw new ConfigurationException("Property [" + propertyName + "] is not a valid property of class [" + entity.getName() + "]"); } diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/BasicCollectionInQuerySpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/BasicCollectionInQuerySpec.groovy index b5ff16b8b8..73502404e5 100644 --- a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/BasicCollectionInQuerySpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/BasicCollectionInQuerySpec.groovy @@ -82,7 +82,7 @@ class BasicCollectionInQuerySpec extends Specification { when: def results = BcStudent.createCriteria().list { createAlias("schools", "s") - 'in'("s.elements", ["SchoolB"]) + 'in'("s", ["SchoolB"]) projections { property 'email' } diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy index a58fe62514..ffea0a300e 100644 --- a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/HibernateQuerySpec.groovy @@ -1099,6 +1099,18 @@ class HibernateQuerySpec extends HibernateGormDatastoreSpec { preEvents > 0 postEvents > 0 } + + def "test add and get aliases"() { + given: + def alias = new org.grails.orm.hibernate.query.HibernateAlias("nicknames", "n") + + when: + hibernateQuery.addAlias(alias) + + then: + hibernateQuery.getAliases().size() == 1 + hibernateQuery.getAliases()[0] == alias + } } diff --git a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/JpaCriteriaQueryCreatorSpec.groovy b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/JpaCriteriaQueryCreatorSpec.groovy index 660f300d1a..e0710946af 100644 --- a/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/JpaCriteriaQueryCreatorSpec.groovy +++ b/grails-data-hibernate7/core/src/test/groovy/grails/gorm/specs/hibernatequery/JpaCriteriaQueryCreatorSpec.groovy @@ -139,6 +139,27 @@ class JpaCriteriaQueryCreatorSpec extends HibernateGormDatastoreSpec { then: noExceptionThrown() } + + def "test createQuery with HibernateAlias triggers join"() { + given: + var entity = getPersistentEntity(JpaCriteriaQueryCreatorSpecPerson) + var detachedCriteria = new DetachedCriteria(JpaCriteriaQueryCreatorSpecPerson) + + // Mock HibernateQuery to provide an alias for a basic collection + def hibernateQuery = Mock(org.grails.orm.hibernate.query.HibernateQuery) { + getAliases() >> [new org.grails.orm.hibernate.query.HibernateAlias("nicknames", "n")] + getEntity() >> entity + } + + var creator = new JpaCriteriaQueryCreator(new Query.ProjectionList(), criteriaBuilder, entity, detachedCriteria, new DefaultConversionService(), hibernateQuery) + + when: + JpaCriteriaQuery<?> query = creator.createQuery() + + then: + noExceptionThrown() + query != null + } } @Entity @@ -146,6 +167,8 @@ class JpaCriteriaQueryCreatorSpecPerson implements GormEntity<JpaCriteriaQueryCr Long id String firstName String lastName + Set<String> nicknames + static hasMany = [nicknames: String] } @Entity
