HHH-17411 Fix fetch join on treated join leads to not cause owner not selected error

This commit is contained in:
Christian Beikov 2023-11-10 18:48:49 +01:00
parent a3f7637585
commit 78c9ccf64f
14 changed files with 325 additions and 36 deletions

View File

@ -21,6 +21,7 @@ import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCteJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
@ -276,9 +277,14 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer {
@Override
public void consumeTreat(String entityName, boolean isTerminal) {
currentPath = isTerminal
? currentPath.treatAs( treatTarget( entityName ), alias)
: currentPath.treatAs( treatTarget( entityName ) );
if ( isTerminal ) {
currentPath = fetch
? ( (SqmAttributeJoin<?, ?>) currentPath ).treatAs( treatTarget( entityName ), alias, true )
: currentPath.treatAs( treatTarget( entityName ), alias );
}
else {
currentPath = currentPath.treatAs( treatTarget( entityName ) );
}
creationState.getCurrentProcessingState().getPathRegistry().register( currentPath );
}

View File

@ -132,9 +132,19 @@ public class SqmBagJoin<O, E> extends AbstractSqmPluralJoin<O,Collection<E>, E>
@Override
public <S extends E> SqmTreatedBagJoin<O,E,S> treatAs(EntityDomainType<S> treatTarget, String alias) {
return treatAs( treatTarget, alias, false );
}
@Override
public <S extends E> SqmTreatedBagJoin<O,E,S> treatAs(Class<S> treatJavaType, String alias, boolean fetch) {
return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ), alias, fetch );
}
@Override
public <S extends E> SqmTreatedBagJoin<O,E,S> treatAs(EntityDomainType<S> treatTarget, String alias, boolean fetch) {
final SqmTreatedBagJoin<O,E,S> treat = findTreat( treatTarget, alias );
if ( treat == null ) {
return addTreat( new SqmTreatedBagJoin<>( this, treatTarget, alias ) );
return addTreat( new SqmTreatedBagJoin<>( this, treatTarget, alias, fetch ) );
}
return treat;
}

View File

@ -139,9 +139,19 @@ public class SqmListJoin<O,E>
@Override
public <S extends E> SqmTreatedListJoin<O,E,S> treatAs(EntityDomainType<S> treatTarget, String alias) {
return treatAs( treatTarget, alias, false );
}
@Override
public <S extends E> SqmTreatedListJoin<O,E,S> treatAs(Class<S> treatJavaType, String alias, boolean fetch) {
return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ), alias, fetch );
}
@Override
public <S extends E> SqmTreatedListJoin<O,E,S> treatAs(EntityDomainType<S> treatTarget, String alias, boolean fetch) {
final SqmTreatedListJoin<O,E,S> treat = findTreat( treatTarget, alias );
if ( treat == null ) {
return addTreat( new SqmTreatedListJoin<>( this, treatTarget, alias ) );
return addTreat( new SqmTreatedListJoin<>( this, treatTarget, alias, fetch ) );
}
return treat;
}

View File

@ -151,9 +151,19 @@ public class SqmMapJoin<O, K, V>
@Override
public <S extends V> SqmTreatedMapJoin<O, K, V, S> treatAs(EntityDomainType<S> treatTarget, String alias) {
return treatAs( treatTarget, alias, false );
}
@Override
public <S extends V> SqmTreatedMapJoin<O, K, V, S> treatAs(Class<S> treatJavaType, String alias, boolean fetch) {
return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ), alias, fetch );
}
@Override
public <S extends V> SqmTreatedMapJoin<O, K, V, S> treatAs(EntityDomainType<S> treatTarget, String alias, boolean fetch) {
final SqmTreatedMapJoin<O, K, V, S> treat = findTreat( treatTarget, alias );
if ( treat == null ) {
return addTreat( new SqmTreatedMapJoin<>( this, treatTarget, alias ) );
return addTreat( new SqmTreatedMapJoin<>( this, treatTarget, alias, fetch ) );
}
return treat;
}

View File

@ -132,9 +132,19 @@ public class SqmSetJoin<O, E>
@Override
public <S extends E> SqmTreatedSetJoin<O,E,S> treatAs(EntityDomainType<S> treatTarget, String alias) {
return treatAs( treatTarget, alias, false );
}
@Override
public <S extends E> SqmTreatedSetJoin<O,E,S> treatAs(Class<S> treatJavaType, String alias, boolean fetch) {
return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ), alias, fetch );
}
@Override
public <S extends E> SqmTreatedSetJoin<O,E,S> treatAs(EntityDomainType<S> treatTarget, String alias, boolean fetch) {
final SqmTreatedSetJoin<O, E, S> treat = findTreat( treatTarget, alias );
if ( treat == null ) {
return addTreat( new SqmTreatedSetJoin<>( this, treatTarget, alias ) );
return addTreat( new SqmTreatedSetJoin<>( this, treatTarget, alias, fetch ) );
}
return treat;
}

View File

@ -110,9 +110,19 @@ public class SqmSingularJoin<O,T> extends AbstractSqmAttributeJoin<O,T> {
@Override
public <S extends T> SqmTreatedSingularJoin<O,T,S> treatAs(EntityDomainType<S> treatTarget, String alias) {
return treatAs( treatTarget, alias, false );
}
@Override
public <S extends T> SqmTreatedSingularJoin<O,T,S> treatAs(Class<S> treatJavaType, String alias, boolean fetch) {
return treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ), alias, fetch );
}
@Override
public <S extends T> SqmTreatedSingularJoin<O,T,S> treatAs(EntityDomainType<S> treatTarget, String alias, boolean fetch) {
final SqmTreatedSingularJoin<O, T, S> treat = findTreat( treatTarget, alias );
if ( treat == null ) {
return addTreat( new SqmTreatedSingularJoin<>( this, treatTarget, alias ) );
return addTreat( new SqmTreatedSingularJoin<>( this, treatTarget, alias, fetch ) );
}
return treat;
}

View File

@ -26,6 +26,14 @@ public class SqmTreatedBagJoin<O,T, S extends T> extends SqmBagJoin<O,S> impleme
SqmBagJoin<O, T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
this( wrappedPath, treatTarget, alias, false );
}
public SqmTreatedBagJoin(
SqmBagJoin<O, T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -35,7 +43,7 @@ public class SqmTreatedBagJoin<O,T, S extends T> extends SqmBagJoin<O,S> impleme
(BagPersistentAttribute<O, S>) wrappedPath.getAttribute(),
alias,
wrappedPath.getSqmJoinType(),
wrappedPath.isFetched(),
fetched,
wrappedPath.nodeBuilder()
);
this.treatTarget = treatTarget;
@ -46,7 +54,8 @@ public class SqmTreatedBagJoin<O,T, S extends T> extends SqmBagJoin<O,S> impleme
NavigablePath navigablePath,
SqmBagJoin<O, T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -76,7 +85,8 @@ public class SqmTreatedBagJoin<O,T, S extends T> extends SqmBagJoin<O,S> impleme
getNavigablePath(),
wrappedPath.copy( context ),
treatTarget,
getExplicitAlias()
getExplicitAlias(),
isFetched()
)
);
copyTo( path, context );

View File

@ -28,6 +28,14 @@ public class SqmTreatedListJoin<O,T, S extends T> extends SqmListJoin<O,S> imple
SqmListJoin<O, T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
this( wrappedPath, treatTarget, alias, false );
}
public SqmTreatedListJoin(
SqmListJoin<O, T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -37,7 +45,7 @@ public class SqmTreatedListJoin<O,T, S extends T> extends SqmListJoin<O,S> imple
(ListPersistentAttribute<O, S>) wrappedPath.getAttribute(),
alias,
wrappedPath.getSqmJoinType(),
wrappedPath.isFetched(),
fetched,
wrappedPath.nodeBuilder()
);
this.treatTarget = treatTarget;
@ -48,7 +56,8 @@ public class SqmTreatedListJoin<O,T, S extends T> extends SqmListJoin<O,S> imple
NavigablePath navigablePath,
SqmListJoin<O, T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -56,7 +65,7 @@ public class SqmTreatedListJoin<O,T, S extends T> extends SqmListJoin<O,S> imple
(ListPersistentAttribute<O, S>) wrappedPath.getAttribute(),
alias,
wrappedPath.getSqmJoinType(),
wrappedPath.isFetched(),
fetched,
wrappedPath.nodeBuilder()
);
this.treatTarget = treatTarget;
@ -75,7 +84,8 @@ public class SqmTreatedListJoin<O,T, S extends T> extends SqmListJoin<O,S> imple
getNavigablePath(),
wrappedPath.copy( context ),
treatTarget,
getExplicitAlias()
getExplicitAlias(),
isFetched()
)
);
copyTo( path, context );

View File

@ -24,6 +24,14 @@ public class SqmTreatedMapJoin<O, K, V, S extends V> extends SqmMapJoin<O, K, S>
SqmMapJoin<O, K, V> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
this( wrappedPath, treatTarget, alias, false );
}
public SqmTreatedMapJoin(
SqmMapJoin<O, K, V> wrappedPath,
EntityDomainType<S> treatTarget,
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -33,7 +41,7 @@ public class SqmTreatedMapJoin<O, K, V, S extends V> extends SqmMapJoin<O, K, S>
( (SqmMapJoin<O, K, S>) wrappedPath ).getModel(),
alias,
wrappedPath.getSqmJoinType(),
wrappedPath.isFetched(),
fetched,
wrappedPath.nodeBuilder()
);
this.treatTarget = treatTarget;
@ -44,7 +52,8 @@ public class SqmTreatedMapJoin<O, K, V, S extends V> extends SqmMapJoin<O, K, S>
NavigablePath navigablePath,
SqmMapJoin<O, K, V> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -52,7 +61,7 @@ public class SqmTreatedMapJoin<O, K, V, S extends V> extends SqmMapJoin<O, K, S>
( (SqmMapJoin<O, K, S>) wrappedPath ).getModel(),
alias,
wrappedPath.getSqmJoinType(),
wrappedPath.isFetched(),
fetched,
wrappedPath.nodeBuilder()
);
this.treatTarget = treatTarget;
@ -71,7 +80,8 @@ public class SqmTreatedMapJoin<O, K, V, S extends V> extends SqmMapJoin<O, K, S>
getNavigablePath(),
wrappedPath.copy( context ),
treatTarget,
getExplicitAlias()
getExplicitAlias(),
isFetched()
)
);
copyTo( path, context );

View File

@ -26,6 +26,14 @@ public class SqmTreatedSetJoin<O,T, S extends T> extends SqmSetJoin<O,S> impleme
SqmSetJoin<O, T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
this( wrappedPath, treatTarget, alias, false );
}
public SqmTreatedSetJoin(
SqmSetJoin<O, T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -35,7 +43,7 @@ public class SqmTreatedSetJoin<O,T, S extends T> extends SqmSetJoin<O,S> impleme
(SetPersistentAttribute<O, S>) wrappedPath.getAttribute(),
alias,
wrappedPath.getSqmJoinType(),
wrappedPath.isFetched(),
fetched,
wrappedPath.nodeBuilder()
);
this.treatTarget = treatTarget;
@ -46,7 +54,8 @@ public class SqmTreatedSetJoin<O,T, S extends T> extends SqmSetJoin<O,S> impleme
NavigablePath navigablePath,
SqmSetJoin<O, T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -54,7 +63,7 @@ public class SqmTreatedSetJoin<O,T, S extends T> extends SqmSetJoin<O,S> impleme
(SetPersistentAttribute<O, S>) wrappedPath.getAttribute(),
alias,
wrappedPath.getSqmJoinType(),
wrappedPath.isFetched(),
fetched,
wrappedPath.nodeBuilder()
);
this.treatTarget = treatTarget;
@ -73,7 +82,8 @@ public class SqmTreatedSetJoin<O,T, S extends T> extends SqmSetJoin<O,S> impleme
getNavigablePath(),
wrappedPath.copy( context ),
treatTarget,
getExplicitAlias()
getExplicitAlias(),
isFetched()
)
);
copyTo( path, context );

View File

@ -25,6 +25,14 @@ public class SqmTreatedSingularJoin<O,T, S extends T> extends SqmSingularJoin<O,
SqmSingularJoin<O,T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
this( wrappedPath, treatTarget, alias, false );
}
public SqmTreatedSingularJoin(
SqmSingularJoin<O,T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -35,7 +43,7 @@ public class SqmTreatedSingularJoin<O,T, S extends T> extends SqmSingularJoin<O,
(SingularPersistentAttribute<O, S>) wrappedPath.getAttribute(),
alias,
wrappedPath.getSqmJoinType(),
wrappedPath.isFetched(),
fetched,
wrappedPath.nodeBuilder()
);
this.treatTarget = treatTarget;
@ -46,7 +54,8 @@ public class SqmTreatedSingularJoin<O,T, S extends T> extends SqmSingularJoin<O,
NavigablePath navigablePath,
SqmSingularJoin<O,T> wrappedPath,
EntityDomainType<S> treatTarget,
String alias) {
String alias,
boolean fetched) {
//noinspection unchecked
super(
wrappedPath.getLhs(),
@ -54,7 +63,7 @@ public class SqmTreatedSingularJoin<O,T, S extends T> extends SqmSingularJoin<O,
(SingularPersistentAttribute<O, S>) wrappedPath.getAttribute(),
alias,
wrappedPath.getSqmJoinType(),
wrappedPath.isFetched(),
fetched,
wrappedPath.nodeBuilder()
);
this.treatTarget = treatTarget;
@ -73,7 +82,8 @@ public class SqmTreatedSingularJoin<O,T, S extends T> extends SqmSingularJoin<O,
getNavigablePath(),
wrappedPath.copy( context ),
treatTarget,
getExplicitAlias()
getExplicitAlias(),
isFetched()
)
);
copyTo( path, context );

View File

@ -48,6 +48,16 @@ public interface SqmAttributeJoin<O,T> extends SqmQualifiedJoin<O,T>, JpaFetch<O
@Override
<S extends T> SqmAttributeJoin<O, S> treatAs(EntityDomainType<S> treatTarget);
@Override
<S extends T> SqmAttributeJoin<O, S> treatAs(Class<S> treatJavaType, String alias);
@Override
<S extends T> SqmAttributeJoin<O, S> treatAs(EntityDomainType<S> treatJavaType, String alias);
<S extends T> SqmAttributeJoin<O, S> treatAs(Class<S> treatJavaType, String alias, boolean fetch);
<S extends T> SqmAttributeJoin<O, S> treatAs(EntityDomainType<S> treatJavaType, String alias, boolean fetch);
/*
@deprecated not used anymore
*/

View File

@ -489,7 +489,10 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
}
for ( SqmRoot<?> root : roots ) {
validateFetchOwners( selectedFromSet, root );
validateFetchOwners( selectedFromSet, root, root );
for ( SqmFrom<?, ?> sqmTreat : root.getSqmTreats() ) {
validateFetchOwners( selectedFromSet, root, sqmTreat );
}
}
}
@ -531,8 +534,8 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
}
}
private void validateFetchOwners(Set<SqmFrom<?, ?>> selectedFromSet, SqmFrom<?, ?> owner) {
for ( SqmJoin<?, ?> sqmJoin : owner.getSqmJoins() ) {
private void validateFetchOwners(Set<SqmFrom<?, ?>> selectedFromSet, SqmFrom<?, ?> owner, SqmFrom<?, ?> joinContainer) {
for ( SqmJoin<?, ?> sqmJoin : joinContainer.getSqmJoins() ) {
if ( sqmJoin instanceof SqmAttributeJoin<?, ?> ) {
final SqmAttributeJoin<?, ?> attributeJoin = (SqmAttributeJoin<?, ?>) sqmJoin;
if ( attributeJoin.isFetched() ) {
@ -541,19 +544,27 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
continue;
}
}
validateFetchOwners( selectedFromSet, sqmJoin );
}
for ( SqmFrom<?, ?> sqmTreat : owner.getSqmTreats() ) {
validateFetchOwners( selectedFromSet, sqmTreat );
for ( SqmFrom<?, ?> sqmTreat : sqmJoin.getSqmTreats() ) {
if ( sqmTreat instanceof SqmAttributeJoin<?, ?> ) {
final SqmAttributeJoin<?, ?> attributeJoin = (SqmAttributeJoin<?, ?>) sqmTreat;
if ( attributeJoin.isFetched() ) {
assertFetchOwner( selectedFromSet, owner, attributeJoin );
// Only need to check the first level
continue;
}
}
validateFetchOwners( selectedFromSet, sqmJoin, sqmTreat );
}
validateFetchOwners( selectedFromSet, sqmJoin, sqmJoin );
}
}
private void assertFetchOwner(Set<SqmFrom<?, ?>> selectedFromSet, SqmFrom<?, ?> owner, SqmJoin<?, ?> sqmJoin) {
private void assertFetchOwner(Set<SqmFrom<?, ?>> selectedFromSet, SqmFrom<?, ?> owner, SqmJoin<?, ?> fetchJoin) {
if ( !selectedFromSet.contains( owner ) ) {
throw new SemanticException(
"Query specified join fetching, but the owner " +
"of the fetched association was not present in the select list " +
"[" + sqmJoin.asLoggableText() + "]"
"[" + fetchJoin.asLoggableText() + "]"
);
}
}

View File

@ -0,0 +1,162 @@
package org.hibernate.orm.test.query.hql.treat;
import java.util.List;
import org.hibernate.query.spi.QueryImplementor;
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.BeforeAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.OneToOne;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@DomainModel(
annotatedClasses = {
HqlTreatJoinFetchTest.TestEntity.class,
HqlTreatJoinFetchTest.BaseEntity.class,
HqlTreatJoinFetchTest.JoinedEntity.class
}
)
@SessionFactory
@JiraKey("HHH-17411")
public class HqlTreatJoinFetchTest {
@BeforeAll
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
JoinedEntity joined = new JoinedEntity( 1, "joined" );
TestEntity testEntity = new TestEntity( 2, "test", joined );
session.persist( testEntity );
session.persist( joined );
}
);
}
@Test
public void testTreatJoinFetch(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
QueryImplementor<TestEntity> query = session.createQuery(
"select t from TestEntity t join fetch treat(t.joined as JoinedEntity) j left join fetch j.testEntity e",
TestEntity.class
);
List<TestEntity> result = query.list();
assertThat( result.size() ).isEqualTo( 1 );
}
);
}
@Test
public void testJoinFetchRootTreat(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
QueryImplementor<TestEntity> query = session.createQuery(
"select t from BaseEntity t join fetch treat(t as JoinedEntity).testEntity j left join fetch j.joined2 e",
TestEntity.class
);
query.list();
}
);
}
@MappedSuperclass
public static abstract class AbstractEntity {
public AbstractEntity() {
}
public AbstractEntity(BaseEntity joined) {
this.joined = joined;
}
@OneToOne
private BaseEntity joined;
public BaseEntity getJoined() {
return joined;
}
}
@Entity(name = "TestEntity")
public static class TestEntity extends AbstractEntity {
@Id
private long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private JoinedEntity joined2;
public TestEntity() {
}
public TestEntity(long id, String name, JoinedEntity joined) {
super( joined );
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
}
@Entity(name = "BaseEntity")
public static abstract class BaseEntity {
@Id
private long id;
private String name;
public BaseEntity() {
}
public BaseEntity(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
}
@Entity(name = "JoinedEntity")
public static class JoinedEntity extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
private TestEntity testEntity;
public JoinedEntity() {
}
public JoinedEntity(long id, String name) {
super( id, name );
}
public TestEntity getTestEntity() {
return testEntity;
}
}
}