Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public void consumeIdentifier(String identifier, boolean isBase, boolean isTermi
assert delegate != null;
delegate.consumeIdentifier(
identifier,
isTerminal,
!nested && isTerminal,
// Non-nested joins shall allow reuse, but nested ones (i.e. in treat)
// only allow join reuse for non-terminal parts
Expand Down Expand Up @@ -254,7 +255,7 @@ else if ( fetch ) {
}

private interface ConsumerDelegate {
void consumeIdentifier(String identifier, boolean isTerminal, boolean allowReuse);
void consumeIdentifier(String identifier, boolean originallyTerminal, boolean isTerminal, boolean allowReuse);
void consumeTreat(String typeName, boolean isTerminal);
SemanticPathPart getConsumedPart();
}
Expand Down Expand Up @@ -282,11 +283,11 @@ private AttributeJoinDelegate(
}

@Override
public void consumeIdentifier(String identifier, boolean isTerminal, boolean allowReuse) {
public void consumeIdentifier(String identifier, boolean originallyTerminal, boolean isTerminal, boolean allowReuse) {
currentPath = createJoin(
currentPath,
identifier,
joinType,
originallyTerminal ? joinType : SqmJoinType.INNER,
alias,
fetch,
isTerminal,
Expand Down Expand Up @@ -347,11 +348,11 @@ public ExpectingEntityJoinDelegate(
this.fetch = fetch;
this.alias = alias;

consumeIdentifier( identifier, isTerminal, true );
consumeIdentifier( identifier, isTerminal, isTerminal, true );
}

@Override
public void consumeIdentifier(String identifier, boolean isTerminal, boolean allowReuse) {
public void consumeIdentifier(String identifier, boolean originallyTerminal, boolean isTerminal, boolean allowReuse) {
if ( !path.isEmpty() ) {
path.append( '.' );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void testJoinFetchesByPath(SessionFactoryScope scope) {
session -> {
final String qry = "SELECT user "
+ "FROM User user "
+ "LEFT OUTER JOIN FETCH user.contact "
+ "JOIN FETCH user.contact "
+ "LEFT OUTER JOIN FETCH user.contact.emailAddresses2 "
+ "LEFT OUTER JOIN FETCH user.contact.emailAddresses";
User user = (User) session.createQuery( qry ).uniqueResult();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public void testGetMultiColumnSameNameAsArgFunctionHQL(SessionFactoryScope sessi
sessionFactory.inTransaction(s -> {
EntityWithFunctionAsColumnHolder holder1 = (EntityWithFunctionAsColumnHolder) s.createQuery(
"from EntityWithFunctionAsColumnHolder h left join fetch h.entityWithArgFunctionAsColumns " +
"left join fetch h.nextHolder left join fetch h.nextHolder.entityWithArgFunctionAsColumns " +
"join fetch h.nextHolder left join fetch h.nextHolder.entityWithArgFunctionAsColumns " +
"where h.nextHolder is not null" )
.uniqueResult();
assertTrue( Hibernate.isInitialized( holder1.getEntityWithArgFunctionAsColumns() ) );
Expand Down Expand Up @@ -193,7 +193,7 @@ public void testGetMultiColumnSameNameAsNoArgFunctionHQL(SessionFactoryScope fac
var hql = """
from EntityWithFunctionAsColumnHolder h
left join fetch h.entityWithNoArgFunctionAsColumns
left join fetch h.nextHolder
join fetch h.nextHolder
left join fetch h.nextHolder.entityWithNoArgFunctionAsColumns
where h.nextHolder is not null
""";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.query.hql;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
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;

/**
* @author Christian Beikov
*/
@DomainModel(annotatedClasses = {
ImplicitNestedJoinTest.RootEntity.class,
ImplicitNestedJoinTest.FirstLevelReferencedEntity.class,
ImplicitNestedJoinTest.SecondLevelReferencedEntityA.class,
ImplicitNestedJoinTest.SecondLevelReferencedEntityB.class
})
@SessionFactory
@Jira("https://hibernate.atlassian.net/browse/HHH-19905")
public class ImplicitNestedJoinTest {

@Test
public void testInnerAndLeftJoin(SessionFactoryScope scope) {
scope.inSession( session -> {
final var resultList = session.createQuery(
"select r.id from RootEntity r"
+ " join r.firstLevelReference.secondLevelReferenceA sa "
+ " left join r.firstLevelReference.secondLevelReferenceB sb",
Long.class
).getResultList();
assertThat( resultList ).hasSize( 2 ).containsExactlyInAnyOrder( 1L, 2L );
} );
}

@Test
public void testLeftAndInnerJoin(SessionFactoryScope scope) {
scope.inSession( session -> {
final var resultList = session.createQuery(
"select r.id from RootEntity r"
+ " left join r.firstLevelReference.secondLevelReferenceA sa "
+ " join r.firstLevelReference.secondLevelReferenceB sb",
Long.class
).getResultList();
assertThat( resultList ).hasSize( 1 ).containsExactly( 1L );
} );
}

@Test
public void testBothInnerJoins(SessionFactoryScope scope) {
scope.inSession( session -> {
final var resultList = session.createQuery(
"select r.id from RootEntity r"
+ " join r.firstLevelReference.secondLevelReferenceA sa "
+ " join r.firstLevelReference.secondLevelReferenceB sb",
Long.class
).getResultList();
assertThat( resultList ).hasSize( 1 ).containsExactly( 1L );
} );
}

@BeforeAll
public void setUp(SessionFactoryScope scope) {
scope.inTransaction( session -> {
// create some test data : one first level reference with both second level references, and
// another first level reference with only one second level reference
SecondLevelReferencedEntityA secondLevelA = new SecondLevelReferencedEntityA();
secondLevelA.id = 1L;
secondLevelA.name = "Second Level A";
session.persist( secondLevelA );
SecondLevelReferencedEntityB secondLevelB = new SecondLevelReferencedEntityB();
secondLevelB.id = 1L;
session.persist( secondLevelB );
FirstLevelReferencedEntity firstLevel1 = new FirstLevelReferencedEntity();
firstLevel1.id = 1L;
firstLevel1.secondLevelReferenceA = secondLevelA;
firstLevel1.secondLevelReferenceB = secondLevelB;
session.persist( firstLevel1 );
RootEntity root1 = new RootEntity();
root1.id = 1L;
root1.firstLevelReference = firstLevel1;
session.persist( root1 );
FirstLevelReferencedEntity firstLevel2 = new FirstLevelReferencedEntity();
firstLevel2.id = 2L;
firstLevel2.secondLevelReferenceA = secondLevelA;
session.persist( firstLevel2 );
RootEntity root2 = new RootEntity();
root2.id = 2L;
root2.firstLevelReference = firstLevel2;
session.persist( root2 );
} );
}

@AfterAll
public void tearDown(SessionFactoryScope scope) {
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
}

@Entity(name = "RootEntity")
public static class RootEntity {
@Id
private Long id;

@ManyToOne
private FirstLevelReferencedEntity firstLevelReference;
}

@Entity(name = "FirstLevelReferencedEntity")
public static class FirstLevelReferencedEntity {
@Id
private Long id;
@ManyToOne
private SecondLevelReferencedEntityA secondLevelReferenceA;
@ManyToOne
private SecondLevelReferencedEntityB secondLevelReferenceB;

}

@Entity(name = "SecondLevelReferencedEntityA")
public static class SecondLevelReferencedEntityA {
@Id
private Long id;
private String name;
}

@Entity(name = "SecondLevelReferencedEntityB")
public static class SecondLevelReferencedEntityB {
@Id
private Long id;
}
}
Loading