HHH-17033 Fix invalid SQL being generated for implicit join in entity join on clause

This commit is contained in:
Christian Beikov 2023-08-07 18:48:40 +02:00
parent 185cfbc4cc
commit feb3b3fc36
4 changed files with 119 additions and 21 deletions

View File

@ -3435,6 +3435,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
tableGroup,
null
);
lhsTableGroup.addTableGroupJoin( tableGroupJoin );
if ( sqmJoin.getJoinPredicate() != null ) {
final SqmJoin<?, ?> oldJoin = currentlyProcessingJoin;
@ -3448,9 +3449,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
+ " (specify a join condition with 'on' or use 'cross join')" );
}
// Note that we add the entity join after processing the predicate because implicit joins needed in there
// can be just ordered right before the entity join without changing the semantics
lhsTableGroup.addTableGroupJoin( tableGroupJoin );
if ( transitive ) {
consumeExplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() );
}
@ -3490,6 +3488,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
queryPartTableGroup,
null
);
parentTableGroup.addTableGroupJoin( tableGroupJoin );
// add any additional join restrictions
if ( sqmJoin.getJoinPredicate() != null ) {
@ -3499,9 +3498,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
currentlyProcessingJoin = oldJoin;
}
// Note that we add the entity join after processing the predicate because implicit joins needed in there
// can be just ordered right before the entity join without changing the semantics
parentTableGroup.addTableGroupJoin( tableGroupJoin );
if ( transitive ) {
consumeExplicitJoins( sqmJoin, queryPartTableGroup );
}
@ -3524,6 +3520,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
tableGroup,
null
);
parentTableGroup.addTableGroupJoin( tableGroupJoin );
// add any additional join restrictions
if ( sqmJoin.getJoinPredicate() != null ) {
@ -3533,9 +3530,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
currentlyProcessingJoin = oldJoin;
}
// Note that we add the entity join after processing the predicate because implicit joins needed in there
// can be just ordered right before the entity join without changing the semantics
parentTableGroup.addTableGroupJoin( tableGroupJoin );
if ( transitive ) {
consumeExplicitJoins( sqmJoin, tableGroup );
}
@ -3785,12 +3779,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
false,
this
);
// Implicit joins in the ON clause of attribute joins need to be added as nested table group joins
// We don't have to do that for entity joins etc. as these do not have an inherent dependency on the lhs.
// We can just add the implicit join before the currently processing join
// See consumeEntityJoin for details
final boolean nested = currentClauseStack.getCurrent() == Clause.FROM
&& currentlyProcessingJoin instanceof SqmAttributeJoin<?, ?>;
// Implicit joins in the ON clause need to be added as nested table group joins
final boolean nested = currentClauseStack.getCurrent() == Clause.FROM;
if ( nested ) {
parentTableGroup.addNestedTableGroupJoin( tableGroupJoin );
}

View File

@ -191,6 +191,11 @@ public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, SqmPat
return join;
}
}
for ( TableGroupJoin join : getNestedTableGroupJoins() ) {
if ( join.getJoinedGroup() == tableGroup ) {
return join;
}
}
return null;
}

View File

@ -206,14 +206,15 @@ public class EntityJoinTest {
assertThat( roots.size(), is( 1 ) );
final TableGroup rootTableGroup = roots.get( 0 );
assertThat( rootTableGroup.getTableGroupJoins().size(), is( 2 ) );
assertThat( rootTableGroup.getTableGroupJoins().size(), is( 1 ) );
assertThat( rootTableGroup.getNestedTableGroupJoins().size(), is( 1 ) );
// The first table group is an uninitialized lazy table group for the path in the on clause
final TableGroupJoin firstTableGroupJoin = rootTableGroup.getTableGroupJoins().get( 0 );
assertThat( firstTableGroupJoin.getJoinedGroup(), instanceOf( LazyTableGroup.class ) );
assertThat( ((LazyTableGroup) firstTableGroupJoin.getJoinedGroup()).getUnderlyingTableGroup(), is( CoreMatchers.nullValue() ) );
// An uninitialized lazy table group for the path in the on clause
final TableGroupJoin nestedTableGroupJoin = rootTableGroup.getNestedTableGroupJoins().get( 0 );
assertThat( nestedTableGroupJoin.getJoinedGroup(), instanceOf( LazyTableGroup.class ) );
assertThat( ((LazyTableGroup) nestedTableGroupJoin.getJoinedGroup()).getUnderlyingTableGroup(), is( CoreMatchers.nullValue() ) );
final TableGroupJoin tableGroupJoin = rootTableGroup.getTableGroupJoins().get( 1 );
final TableGroupJoin tableGroupJoin = rootTableGroup.getTableGroupJoins().get( 0 );
assertThat( tableGroupJoin.getJoinedGroup().getModelPart(), is( customerEntityDescriptor ) );
}
);

View File

@ -0,0 +1,102 @@
/*
* 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.hql;
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.Test;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
/**
* @author Christian Beikov
*/
@DomainModel( annotatedClasses = {
ImplicitJoinInOnClauseTest.RootEntity.class,
ImplicitJoinInOnClauseTest.FirstLevelReferencedEntity.class,
ImplicitJoinInOnClauseTest.SecondLevelReferencedEntityA.class,
ImplicitJoinInOnClauseTest.SecondLevelReferencedEntityB.class,
ImplicitJoinInOnClauseTest.ThirdLevelReferencedEntity.class,
ImplicitJoinInOnClauseTest.UnrelatedEntity.class
})
@SessionFactory
@JiraKey( "HHH-17033" )
public class ImplicitJoinInOnClauseTest {
@Test
public void testImplicitJoinInEntityJoinPredicate(SessionFactoryScope scope) {
scope.inTransaction(
(session) -> {
// this should get financial records which have a lastUpdateBy user set
List<Object[]> result = session.createQuery(
"select r.id, flr.id, ur1.id, ur2.id, ur3.id from RootEntity r " +
"inner join r.firstLevelReference as flr " +
"left join UnrelatedEntity ur1 on ur1.id = flr.secondLevelReferenceA.id " +
"left join UnrelatedEntity ur2 on ur2.id = flr.secondLevelReferenceB.id " +
"left join UnrelatedEntity ur3 on ur3.id = flr.secondLevelReferenceB.thirdLevelReference.id",
Object[].class
).list();
}
);
}
@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;
@ManyToOne
private ThirdLevelReferencedEntity thirdLevelReference;
}
@Entity(name = "ThirdLevelReferencedEntity")
public static class ThirdLevelReferencedEntity {
@Id
private Long id;
private String name;
}
@Entity(name = "UnrelatedEntity")
public static class UnrelatedEntity {
@Id
private Long id;
private String name;
}
}