From f7709e76102501cf37458717118c0ebd89a5b010 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 10 Nov 2023 15:28:00 +0100 Subject: [PATCH] HHH-17102 Fix @SqlResultSetMapping issue for joined inheritance entity result --- .../query/SqlResultSetMappingDescriptor.java | 6 +- ...CaseStatementDiscriminatorMappingImpl.java | 4 +- ...xplicitColumnDiscriminatorMappingImpl.java | 13 +- .../internal/ResultMementoEntityJpa.java | 2 +- .../CompleteFetchBuilderBasicPart.java | 13 +- .../CompleteResultBuilderEntityJpa.java | 2 +- .../query/resultmapping/InheritanceTests.java | 156 ++++++++++++++++++ 7 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/resultmapping/InheritanceTests.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDescriptor.java b/hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDescriptor.java index 519e8f1f78..21bef14203 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/query/SqlResultSetMappingDescriptor.java @@ -276,11 +276,7 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr String discriminatorColumn, NavigablePath entityPath) { final EntityDiscriminatorMapping discriminatorMapping = entityMapping.getDiscriminatorMapping(); - if ( discriminatorMapping == null ) { - return null; - } - - if ( discriminatorColumn == null ) { + if ( discriminatorMapping == null || discriminatorColumn == null || !entityMapping.hasSubclasses() ) { return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java index b89c3a0441..6abb62a1e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java @@ -178,10 +178,10 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator @Override public String getContainingTableExpression() { - throw new UnsupportedOperationException(); +// throw new UnsupportedOperationException(); // // this *should* only be used to create the sql-expression key, so just // // using the primary table expr should be fine -// return entityDescriptor.getRootTableName(); + return getEntityDescriptor().getMappedTableDetails().getTableName(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitColumnDiscriminatorMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitColumnDiscriminatorMappingImpl.java index 791af9dd46..3fe32ec2fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitColumnDiscriminatorMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ExplicitColumnDiscriminatorMappingImpl.java @@ -14,11 +14,14 @@ import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.type.BasicType; +import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey; + /** * @author Steve Ebersole */ @@ -80,7 +83,15 @@ public class ExplicitColumnDiscriminatorMappingImpl extends AbstractDiscriminato SqlAstCreationState creationState) { final SqlExpressionResolver expressionResolver = creationState.getSqlExpressionResolver(); final TableReference tableReference = tableGroup.resolveTableReference( navigablePath, tableExpression ); - return expressionResolver.resolveSqlExpression( tableReference, this ); + + return expressionResolver.resolveSqlExpression( + createColumnReferenceKey( + tableGroup.getPrimaryTableReference(), + getSelectionExpression(), + jdbcMappingToUse + ), + processingState -> new ColumnReference( tableReference, this ) + ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java index 515505db7c..57f9d62942 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ResultMementoEntityJpa.java @@ -59,7 +59,7 @@ public class ResultMementoEntityJpa implements ResultMementoEntity, FetchMemento ResultSetMappingResolutionContext context) { final EntityDiscriminatorMapping discriminatorMapping = entityDescriptor.getDiscriminatorMapping(); final BasicValuedFetchBuilder discriminatorFetchBuilder; - if ( discriminatorMapping == null ) { + if ( discriminatorMapping == null || !entityDescriptor.hasSubclasses() ) { assert discriminatorMemento == null; discriminatorFetchBuilder = null; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderBasicPart.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderBasicPart.java index 0ae7621b6d..bc3b9034b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderBasicPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderBasicPart.java @@ -13,7 +13,8 @@ import java.util.function.BiFunction; import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.BasicValuedModelPart; -import org.hibernate.query.results.ResultsHelper; +import org.hibernate.metamodel.mapping.DiscriminatorMapping; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.spi.NavigablePath; import org.hibernate.query.results.BasicValuedFetchBuilder; import org.hibernate.query.results.DomainResultCreationStateImpl; @@ -112,9 +113,17 @@ public class CompleteFetchBuilderBasicPart implements CompleteFetchBuilder, Basi final int valuesArrayPosition = jdbcPositionToValuesArrayPosition( jdbcPosition ); + final JdbcMapping jdbcMapping; + if ( referencedModelPart instanceof DiscriminatorMapping ) { + jdbcMapping = ( (DiscriminatorMapping) referencedModelPart ).getUnderlyingJdbcMapping(); + } + else { + jdbcMapping = referencedModelPart.getJdbcMapping(); + } + // we just care about the registration here. The ModelPart will find it later creationStateImpl.resolveSqlExpression( - createColumnReferenceKey( tableReference, referencedModelPart ), + createColumnReferenceKey( tableReference, referencedModelPart.getSelectablePath(), jdbcMapping ), processingState -> new ResultSetMappingSqlSelection( valuesArrayPosition, referencedModelPart ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java index 6d74c5aff8..3297808684 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java @@ -60,7 +60,7 @@ public class CompleteResultBuilderEntityJpa implements CompleteResultBuilderEnti } else { // discriminated - assert discriminatorFetchBuilder != null; + assert !entityDescriptor.hasSubclasses() || discriminatorFetchBuilder != null; } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/resultmapping/InheritanceTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/resultmapping/InheritanceTests.java new file mode 100644 index 0000000000..7edf1bd079 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/resultmapping/InheritanceTests.java @@ -0,0 +1,156 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.query.resultmapping; + +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityResult; +import jakarta.persistence.FieldResult; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.NamedNativeQuery; +import jakarta.persistence.SqlResultSetMapping; +import jakarta.persistence.Table; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; + +@DomainModel( annotatedClasses = { + InheritanceTests.Parent.class, + InheritanceTests.Child.class +} ) +@SessionFactory +@JiraKey("HHH-17102") +public class InheritanceTests { + + @BeforeEach + public void prepareData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.save( new Child( 1L, "test", 123 ) ); + } + ); + } + + @AfterEach + public void cleanUpData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "delete Child" ).executeUpdate(); + } + ); + } + + @Test + public void testResultSetMappingForParentEntity(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final List children = session.createNamedQuery( "Parent", Parent.class ).getResultList(); + assertEquals( 1, children.size() ); + assertEquals( 1L, children.get( 0 ).id ); + assertEquals( "test", children.get( 0 ).name ); + assertInstanceOf( Child.class, children.get( 0 ) ); + assertEquals( 123, ( (Child) children.get( 0 ) ).test ); + } + ); + } + + @Test + public void testResultSetMappingForChildEntity(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final List children = session.createNamedQuery( "Child", Child.class ).getResultList(); + assertEquals( 1, children.size() ); + assertEquals( 1L, children.get( 0 ).id ); + assertEquals( "test", children.get( 0 ).name ); + assertEquals( 123, children.get( 0 ).test ); + } + ); + } + + @SqlResultSetMapping( + name = "ParentResult", + entities = { + @EntityResult( + entityClass = Parent.class, + discriminatorColumn = "discr", + fields = { + @FieldResult(name = "id", column = "id"), + @FieldResult(name = "name", column = "name"), + @FieldResult(name = "test", column = "test") + } + ) + } + ) + @NamedNativeQuery( + name = "Parent", + query = "SELECT p.id as id, case when c.id is not null then 1 else 0 end as discr, p.name as name, c.test as test FROM parent p left join child c on p.id = c.id", + resultSetMapping = "ParentResult" + ) + @Entity(name = "Parent") + @Table(name = "parent") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Parent { + @Id + @Column(name = "id") + Long id; + String name; + + public Parent() { + } + + public Parent(Long id, String name) { + this.id = id; + this.name = name; + } + } + + @SqlResultSetMapping( + name = "ChildResult", + entities = { + @EntityResult( + entityClass = Child.class, + fields = { + @FieldResult(name = "id", column = "id"), + @FieldResult(name = "name", column = "name"), + @FieldResult(name = "test", column = "test") + } + ) + } + ) + @NamedNativeQuery( + name = "Child", + query = "SELECT c.id as id, 'test' as name, c.test FROM child c", + resultSetMapping = "ChildResult" + ) + @Entity(name = "Child") + @Table(name = "child") + public static class Child extends Parent { + @Column(name = "test") + int test; + + public Child() { + } + + public Child(Long id, String name, int test) { + super( id, name ); + this.test = test; + } + } +} \ No newline at end of file