HHH-16396 HQL with SubQuery having same alias of root Query generates wrong SQL

This commit is contained in:
Andrea Boriero 2023-03-29 16:43:48 +02:00 committed by Steve Ebersole
parent 091aae2c3e
commit e4e25bddfc
4 changed files with 360 additions and 7 deletions

View File

@ -9,10 +9,12 @@ package org.hibernate.sql.ast.spi;
import java.util.HashMap;
import java.util.Map;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlTreeCreationLogger;
import org.hibernate.sql.ast.tree.from.CorrelatedTableGroup;
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.VirtualTableGroup;
@ -53,9 +55,9 @@ public class SimpleFromClauseAccessImpl implements FromClauseAccess {
@Override
public TableGroup findTableGroupForGetOrCreate(NavigablePath navigablePath) {
final TableGroup tableGroup = findTableGroup( navigablePath );
if ( parent != null && tableGroup instanceof VirtualTableGroup && tableGroup.getModelPart() instanceof EmbeddableValuedModelPart ) {
if ( parent != null && tableGroup != null && navigablePath.getParent() != null ) {
final NavigableRole navigableRole = tableGroup.getModelPart().getNavigableRole();
if ( navigableRole != null ) {
if ( navigableRole != null && navigableRole.getParent() != null ) {
// Traverse up the navigable path to the point where resolving the path leads us to a regular TableGroup
NavigableRole parentRole = navigableRole.getParent();
NavigablePath parentPath = navigablePath.getParent();
@ -64,7 +66,7 @@ public class SimpleFromClauseAccessImpl implements FromClauseAccess {
parentPath = parentPath.getParent();
}
// Only return the TableGroup if its regular parent TableGroup corresponds to the underlying one
if ( findTableGroup( parentPath ) == getUnderlyingTableGroup( (VirtualTableGroup) tableGroup ) ) {
if ( getUnderlyingTableGroup( findTableGroup( parentPath ) ) == getUnderlyingTableGroup( tableGroup ) ) {
return tableGroup;
}
else {
@ -75,10 +77,15 @@ public class SimpleFromClauseAccessImpl implements FromClauseAccess {
return tableGroup;
}
private TableGroup getUnderlyingTableGroup(VirtualTableGroup virtualTableGroup) {
final TableGroup tableGroup = virtualTableGroup.getUnderlyingTableGroup();
private TableGroup getUnderlyingTableGroup(TableGroup tableGroup) {
if ( tableGroup instanceof VirtualTableGroup ) {
return getUnderlyingTableGroup( (VirtualTableGroup) tableGroup );
return getUnderlyingTableGroup( ( (VirtualTableGroup) tableGroup ).getUnderlyingTableGroup() );
}
else if ( tableGroup instanceof CorrelatedTableGroup ) {
return getUnderlyingTableGroup( ( (CorrelatedTableGroup) tableGroup ).getCorrelatedTableGroup() );
}
else if ( tableGroup instanceof PluralTableGroup ) {
return ( (PluralTableGroup) tableGroup ).getElementTableGroup();
}
return tableGroup;
}

View File

@ -122,4 +122,8 @@ public class CorrelatedTableGroup extends AbstractTableGroup {
public Consumer<Predicate> getJoinPredicateConsumer() {
return joinPredicateConsumer;
}
public TableGroup getCorrelatedTableGroup(){
return correlatedTableGroup;
}
}

View File

@ -0,0 +1,167 @@
package org.hibernate.orm.test.hql;
import java.util.List;
import org.hibernate.testing.TestForIssue;
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.BeforeAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Tuple;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@DomainModel(
annotatedClasses = {
EmbeddableSubqueryWithSameAliasOfRootQueryTest.MyEntity.class,
EmbeddableSubqueryWithSameAliasOfRootQueryTest.AnotherEntity.class,
EmbeddableSubqueryWithSameAliasOfRootQueryTest.AgainAnotherEntity.class
}
)
@SessionFactory
@TestForIssue( jiraKey = "HHH-16396")
public class EmbeddableSubqueryWithSameAliasOfRootQueryTest {
private static final Long ENTITY_WITH_ASSOCIATION_ID = 1L;
private static final Long ENTITY_WITHOUT_ASSOCIATION_ID = 2L;
private static final long ANOTHER_ENTITY_ID = 3l;
private static final long AGAIN_ANOTHER_ENTITY_ID = 4l;
@BeforeAll
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
AgainAnotherEntity againAnotherEntity = new AgainAnotherEntity(AGAIN_ANOTHER_ENTITY_ID, "again");
session.persist( againAnotherEntity );
AnotherEntity anotherEntity = new AnotherEntity(ANOTHER_ENTITY_ID, "another", againAnotherEntity);
session.persist( anotherEntity );
MyEntity entity = new MyEntity( ENTITY_WITH_ASSOCIATION_ID, "with association", anotherEntity );
session.persist( entity );
MyEntity entity2 = new MyEntity( ENTITY_WITHOUT_ASSOCIATION_ID, "without any association", null );
session.persist( entity2 );
}
);
}
@Test
public void testQuery(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
List<Tuple> results = session.createQuery(
"select " +
" e.myEntityEmbeddable.otherEntity.id," +
" (select e.aString from MyEntity e where e.myEntityEmbeddable.otherEntity.id = :anotherEntityId)" +
"from MyEntity e where e.id = :id", Tuple.class )
.setParameter( "anotherEntityId", ANOTHER_ENTITY_ID )
.setParameter( "id", ENTITY_WITHOUT_ASSOCIATION_ID ).list();
assertThat( results.size() ).isEqualTo( 1 );
Tuple tuple = results.get( 0 );
assertThat( tuple.get( 0 ) ).isEqualTo( null );
assertThat( tuple.get( 1 ) ).isEqualTo( "with association" );
}
);
}
@Entity(name = "MyEntity")
public static class MyEntity {
@Id
private Long id;
private String aString;
MyEntityEmbeddable myEntityEmbeddable;
public MyEntity() {
}
public MyEntity(Long id, String aString, AnotherEntity anotherEntity) {
this.id = id;
this.aString = aString;
this.myEntityEmbeddable = new MyEntityEmbeddable( anotherEntity );
}
public Long getId() {
return id;
}
public String getaString() {
return aString;
}
}
@Embeddable
public static class MyEntityEmbeddable{
@ManyToOne
private AnotherEntity otherEntity;
public MyEntityEmbeddable() {
}
public MyEntityEmbeddable(AnotherEntity otherEntity) {
this.otherEntity = otherEntity;
}
public AnotherEntity getOtherEntity() {
return otherEntity;
}
}
@Entity(name = "AnotherEntity")
public static class AnotherEntity{
@Id
private Long id;
private String aString;
@ManyToOne
private AgainAnotherEntity otherEntity;
public AnotherEntity() {
}
public AnotherEntity(Long id, String aString, AgainAnotherEntity otherEntity) {
this.id = id;
this.aString = aString;
this.otherEntity = otherEntity;
}
public String getaString() {
return aString;
}
}
@Entity(name = "AgainAnotherEntity")
public static class AgainAnotherEntity{
@Id
private Long id;
private String aString;
@ManyToOne
private AnotherEntity otherEntity;
public AgainAnotherEntity() {
}
public AgainAnotherEntity(Long id, String aString) {
this.id = id;
this.aString = aString;
}
}
}

View File

@ -0,0 +1,175 @@
package org.hibernate.orm.test.hql;
import java.util.List;
import org.hibernate.testing.TestForIssue;
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.BeforeAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Tuple;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@DomainModel(
annotatedClasses = {
SubqueryWithSameAliasOfRootQueryTest.MyEntity.class,
SubqueryWithSameAliasOfRootQueryTest.AnotherEntity.class,
SubqueryWithSameAliasOfRootQueryTest.AgainAnotherEntity.class
}
)
@SessionFactory
@TestForIssue( jiraKey = "HHH-16396")
public class SubqueryWithSameAliasOfRootQueryTest {
private static final Long ENTITY_WITH_ASSOCIATION_ID = 1L;
private static final Long ENTITY_WITHOUT_ASSOCIATION_ID = 2L;
private static final long ANOTHER_ENTITY_ID = 3l;
private static final long AGAIN_ANOTHER_ENTITY_ID = 4l;
@BeforeAll
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
AgainAnotherEntity againAnotherEntity = new AgainAnotherEntity(AGAIN_ANOTHER_ENTITY_ID, "again");
session.persist( againAnotherEntity );
AnotherEntity anotherEntity = new AnotherEntity(ANOTHER_ENTITY_ID, "another", againAnotherEntity);
session.persist( anotherEntity );
MyEntity entity = new MyEntity( ENTITY_WITH_ASSOCIATION_ID, "with association", anotherEntity );
session.persist( entity );
MyEntity entity2 = new MyEntity( ENTITY_WITHOUT_ASSOCIATION_ID, "without any association", null );
session.persist( entity2 );
}
);
}
@Test
public void testQuery(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
List<Tuple> results = session.createQuery(
"select" +
" e.otherEntity.id," +
" (select e.aString from MyEntity e where e.otherEntity.id = :anotherEntityId)" +
"from MyEntity e where e.id = :id", Tuple.class )
.setParameter( "anotherEntityId", ANOTHER_ENTITY_ID )
.setParameter( "id", ENTITY_WITHOUT_ASSOCIATION_ID ).list();
assertThat( results.size() ).isEqualTo( 1 );
Tuple tuple = results.get( 0 );
assertThat( tuple.get( 0 ) ).isEqualTo( null );
assertThat( tuple.get( 1 ) ).isEqualTo( "with association" );
}
);
}
@Test
public void testQuery2(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
List<Tuple> results = session.createQuery(
"select" +
" e.otherEntity.id," +
" (select e.aString from MyEntity e where e.otherEntity.otherEntity.id = :anotherEntityId)" +
"from MyEntity e left join e.otherEntity o where e.id = :id", Tuple.class )
.setParameter( "anotherEntityId", AGAIN_ANOTHER_ENTITY_ID )
.setParameter( "id", ENTITY_WITHOUT_ASSOCIATION_ID ).list();
assertThat( results.size() ).isEqualTo( 1 );
Tuple tuple = results.get( 0 );
assertThat( tuple.get( 0 ) ).isEqualTo( null );
assertThat( tuple.get( 1 ) ).isEqualTo( "with association" );
}
);
}
@Entity(name = "MyEntity")
public static class MyEntity {
@Id
private Long id;
private String aString;
@OneToMany
private List<AnotherEntity> anotherEntities;
@ManyToOne
private AnotherEntity otherEntity;
public MyEntity() {
}
public MyEntity(Long id, String aString, AnotherEntity otherEntity) {
this.id = id;
this.aString = aString;
this.otherEntity = otherEntity;
}
public Long getId() {
return id;
}
public String getaString() {
return aString;
}
public AnotherEntity getOtherEntity() {
return otherEntity;
}
}
@Entity(name = "AnotherEntity")
public static class AnotherEntity{
@Id
private Long id;
private String aString;
@ManyToOne
private AgainAnotherEntity otherEntity;
public AnotherEntity() {
}
public AnotherEntity(Long id, String aString, AgainAnotherEntity otherEntity) {
this.id = id;
this.aString = aString;
this.otherEntity = otherEntity;
}
public String getaString() {
return aString;
}
}
@Entity(name = "AgainAnotherEntity")
public static class AgainAnotherEntity{
@Id
private Long id;
private String aString;
@ManyToOne
private AnotherEntity otherEntity;
public AgainAnotherEntity() {
}
public AgainAnotherEntity(Long id, String aString) {
this.id = id;
this.aString = aString;
}
}
}