diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java index 0eb0826f87fb..9ffdfe6dac06 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java @@ -407,7 +407,9 @@ private static boolean supportsFunctionalDependency(Dialect dialect, EntityMappi final FunctionalDependencyAnalysisSupport analysisSupport = dialect.getFunctionalDependencyAnalysisSupport(); if ( analysisSupport.supportsAnalysis() ) { if ( entityMappingType.getSqmMultiTableMutationStrategy() == null ) { - return true; + // A subquery may be used to render a single-table inheritance subtype, in which case + // we cannot use functional dependency analysis unless the dialect supports table groups + return analysisSupport.supportsTableGroups() || entityMappingType.getSuperMappingType() == null; } else { return analysisSupport.supportsTableGroups() && ( analysisSupport.supportsConstants() || diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceToOneSubtypeJoinGroupByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceToOneSubtypeJoinGroupByTest.java new file mode 100644 index 000000000000..c9c915d9f7b9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceToOneSubtypeJoinGroupByTest.java @@ -0,0 +1,164 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.inheritance; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Tuple; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel( annotatedClasses = { + InheritanceToOneSubtypeJoinGroupByTest.Base.class, + InheritanceToOneSubtypeJoinGroupByTest.EntityA.class, + InheritanceToOneSubtypeJoinGroupByTest.EntityB.class, + InheritanceToOneSubtypeJoinGroupByTest.WhitelistEntry.class, +} ) +@SessionFactory +public class InheritanceToOneSubtypeJoinGroupByTest { + @Test + void testGroupByA(SessionFactoryScope scope) { + scope.inSession( session -> { + final EntityA result = session.createQuery( + "SELECT a FROM WhitelistEntry we JOIN we.primaryKey.a a group by a", + EntityA.class + ).getSingleResult(); + assertThat( result.getAName() ).isEqualTo( "a" ); + } ); + } + + @Test + void testGroupByB(SessionFactoryScope scope) { + scope.inSession( session -> { + final Tuple result = session.createQuery( + "SELECT b.id, b.bName FROM WhitelistEntry we JOIN we.primaryKey.b b group by b", + Tuple.class + ).getSingleResult(); + assertThat( result.get( 1, String.class ) ).isEqualTo( "b" ); + } ); + } + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final EntityA a = new EntityA(); + a.setAName( "a" ); + final EntityB b = new EntityB(); + b.setBName( "b" ); + final WhitelistEntry whitelistEntry = new WhitelistEntry(); + whitelistEntry.setName( "whitelistEntry" ); + final WhitelistEntryPK primaryKey = new WhitelistEntryPK(); + primaryKey.setA( a ); + primaryKey.setB( b ); + whitelistEntry.setPrimaryKey( primaryKey ); + session.persist( a ); + session.persist( b ); + session.persist( whitelistEntry ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + + @Entity(name = "Base") + @Inheritance + public static class Base { + @Id + @GeneratedValue + private Long id; + } + + + @Entity(name = "EntityA") + public static class EntityA extends Base { + private String aName; + + public String getAName() { + return aName; + } + + public void setAName(String name) { + this.aName = name; + } + } + + @Entity(name = "EntityB") + public static class EntityB extends Base { + private String bName; + + public String getBName() { + return bName; + } + + public void setBName(String name) { + this.bName = name; + } + } + + @Embeddable + public static class WhitelistEntryPK { + @ManyToOne + private EntityB b; + + @ManyToOne + private EntityA a; + + public WhitelistEntryPK() { + } + + public EntityB getB() { + return b; + } + + public void setB(EntityB b) { + this.b = b; + } + + public EntityA getA() { + return a; + } + + public void setA(EntityA a) { + this.a = a; + } + } + + @Entity(name = "WhitelistEntry") + public static class WhitelistEntry { + @EmbeddedId + private WhitelistEntryPK primaryKey; + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public WhitelistEntryPK getPrimaryKey() { + return primaryKey; + } + + public void setPrimaryKey(WhitelistEntryPK primaryKey) { + this.primaryKey = primaryKey; + } + } +}