HHH-16396 HQL with SubQuery having same alias of root Query generates wrong SQL
This commit is contained in:
parent
091aae2c3e
commit
e4e25bddfc
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -122,4 +122,8 @@ public class CorrelatedTableGroup extends AbstractTableGroup {
|
|||
public Consumer<Predicate> getJoinPredicateConsumer() {
|
||||
return joinPredicateConsumer;
|
||||
}
|
||||
|
||||
public TableGroup getCorrelatedTableGroup(){
|
||||
return correlatedTableGroup;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue