HHH-18584 fix logic for deciding if something is implicitly selectable

implicit joins should not be added to the select list!

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-09-07 13:02:16 +02:00
parent 306991f8d9
commit 0c1a1e9832
7 changed files with 63 additions and 62 deletions

View File

@ -1286,10 +1286,10 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
}
}
@SuppressWarnings("rawtypes")
private EntityDomainType<R> getResultEntity() {
final JpaMetamodelImplementor jpaMetamodel = creationContext.getJpaMetamodel();
if ( expectedResultEntity != null ) {
@SuppressWarnings("rawtypes")
final EntityDomainType entityDescriptor = jpaMetamodel.entity( expectedResultEntity );
if ( entityDescriptor == null ) {
throw new SemanticException( "Query has no 'from' clause, and the result type '"
@ -1331,7 +1331,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
// we found an entity with the alias 'this'
// assigned explicitly, JPA says we should
// infer the select list 'select this'
SqmSelectClause selectClause = new SqmSelectClause( false, 1, nodeBuilder );
final SqmSelectClause selectClause =
new SqmSelectClause( false, 1, nodeBuilder );
selectClause.addSelection( new SqmSelection<>( sqmRoot, "this", nodeBuilder) );
return selectClause;
}
@ -1353,7 +1354,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
// we may safely assume the query returns that entity
if ( fromClause.getNumberOfRoots() == 1 ) {
final SqmRoot<?> sqmRoot = fromClause.getRoots().get(0);
if ( sqmRoot.hasTrueJoin() ) {
if ( sqmRoot.hasImplicitlySelectableJoin() ) {
// the entity has joins, and doesn't explicitly have
// the alias 'this', so the 'select' list cannot be
// inferred
@ -1365,7 +1366,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
// 'this', and that we should infer 'select this', but we
// accept even the case where the entity has an explicit
// alias, and infer 'select explicit_alias'
SqmSelectClause selectClause = new SqmSelectClause( false, 1, nodeBuilder );
final SqmSelectClause selectClause =
new SqmSelectClause( false, 1, nodeBuilder );
selectClause.addSelection( new SqmSelection<>( sqmRoot, sqmRoot.getAlias(), nodeBuilder) );
return selectClause;
}
@ -1398,7 +1400,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
else {
// exactly one root entity, return it
// (joined entities are not returned)
final SqmSelectClause selectClause = new SqmSelectClause( false, 1, nodeBuilder );
final SqmSelectClause selectClause =
new SqmSelectClause( false, 1, nodeBuilder );
selectClause.addSelection( new SqmSelection<>( sqmRoot, sqmRoot.getAlias(), nodeBuilder) );
return selectClause;
}

View File

@ -12,7 +12,6 @@ import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.criteria.JpaPredicate;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmJoinable;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.spi.SqmCreationHelper;
import org.hibernate.query.sqm.tree.SqmJoinType;
@ -33,48 +32,38 @@ public abstract class AbstractSqmAttributeJoin<L, R>
extends AbstractSqmJoin<L, R>
implements SqmAttributeJoin<L, R> {
private boolean fetched;
private final boolean implicitJoin;
private boolean fetchJoin;
@SuppressWarnings({ "rawtypes", "unchecked" })
public AbstractSqmAttributeJoin(
SqmFrom<?, L> lhs,
SqmJoinable joinedNavigable,
String alias,
SqmJoinType joinType,
boolean fetched,
NodeBuilder nodeBuilder) {
//noinspection StringEquality
this(
lhs,
joinedNavigable.createNavigablePath( lhs, alias ),
joinedNavigable,
alias == SqmCreationHelper.IMPLICIT_ALIAS ? null : alias,
joinType,
fetched,
nodeBuilder
);
}
@SuppressWarnings("rawtypes")
protected AbstractSqmAttributeJoin(
SqmFrom<?, L> lhs,
NavigablePath navigablePath,
SqmJoinable joinedNavigable,
SqmPathSource<R> joinedNavigable,
String alias,
SqmJoinType joinType,
boolean fetched,
boolean fetchJoin,
NodeBuilder nodeBuilder) {
//noinspection unchecked
super(
navigablePath,
(SqmPathSource<R>) joinedNavigable,
joinedNavigable,
lhs,
alias,
isImplicitAlias( alias ) ? null : alias,
joinType,
nodeBuilder
);
this.fetched = fetched;
this.fetchJoin = fetchJoin;
validateFetchAlias( alias );
implicitJoin = isImplicitAlias( alias ); //TODO: add a parameter
}
@SuppressWarnings("StringEquality")
private static boolean isImplicitAlias(String alias) {
return alias == SqmCreationHelper.IMPLICIT_ALIAS;
}
@Override
public boolean isImplicitJoin() {
return implicitJoin;
}
@Override
@ -89,7 +78,7 @@ public abstract class AbstractSqmAttributeJoin<L, R>
@Override
public boolean isFetched() {
return fetched;
return fetchJoin;
}
@Override
@ -100,11 +89,11 @@ public abstract class AbstractSqmAttributeJoin<L, R>
@Override
public void clearFetched() {
fetched = false;
fetchJoin = false;
}
private void validateFetchAlias(String alias) {
if ( fetched && alias != null && nodeBuilder().isJpaQueryComplianceEnabled() ) {
if ( fetchJoin && alias != null && nodeBuilder().isJpaQueryComplianceEnabled() ) {
throw new IllegalStateException(
"The JPA specification does not permit specifying an alias for fetch joins."
);

View File

@ -60,7 +60,6 @@ import jakarta.persistence.metamodel.PluralAttribute;
import jakarta.persistence.metamodel.SetAttribute;
import jakarta.persistence.metamodel.SingularAttribute;
import static org.hibernate.metamodel.AttributeClassification.EMBEDDED;
import static org.hibernate.query.sqm.internal.SqmUtil.findCompatibleFetchJoin;
/**
@ -163,9 +162,8 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
SqmPath<?> resolvedPath = null;
for ( SqmJoin<?, ?> sqmJoin : getSqmJoins() ) {
// We can only match singular joins here, as plural path parts are interpreted like sub-queries
if ( sqmJoin instanceof SqmSingularJoin<?, ?>
if ( sqmJoin instanceof SqmSingularJoin<?, ?> attributeJoin
&& name.equals( sqmJoin.getReferencedPathSource().getPathName() ) ) {
final SqmAttributeJoin<?, ?> attributeJoin = (SqmAttributeJoin<?, ?>) sqmJoin;
if ( attributeJoin.getOn() == null ) {
// todo (6.0): to match the expectation of the JPA spec I think we also have to check
// that the join type is INNER or the default join type for the attribute,
@ -223,8 +221,7 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
public void removeLeftFetchJoins() {
if ( joins != null ) {
for ( SqmJoin<T, ?> join : new ArrayList<>(joins) ) {
if ( join instanceof SqmAttributeJoin ) {
final SqmAttributeJoin<T, ?> attributeJoin = (SqmAttributeJoin<T, ?>) join;
if ( join instanceof SqmAttributeJoin<T, ?> attributeJoin ) {
if ( attributeJoin.isFetched() ) {
if ( join.getSqmJoinType() == SqmJoinType.LEFT ) {
joins.remove( join );
@ -306,11 +303,10 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
}
@Override
public boolean hasTrueJoin() {
public boolean hasImplicitlySelectableJoin() {
return getSqmJoins().stream()
.anyMatch( sqmJoin -> sqmJoin instanceof SqmAttributeJoin<?,?> attributeJoin
&& !attributeJoin.isFetched()
&& attributeJoin.getAttribute().getAttributeClassification()!=EMBEDDED );
&& attributeJoin.isImplicitlySelectable() );
}
@Override
@ -642,8 +638,7 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
@Override
public <X> JpaCrossJoin<X> crossJoin(EntityDomainType<X> entity) {
//noinspection unchecked
final SqmCrossJoin<X> crossJoin = new SqmCrossJoin<>( entity, null, (SqmRoot<X>) findRoot() );
final SqmCrossJoin<X> crossJoin = new SqmCrossJoin<>( entity, null, findRoot() );
// noinspection unchecked
addSqmJoin( (SqmJoin<T, ?>) crossJoin );
return crossJoin;

View File

@ -35,7 +35,15 @@ public abstract class AbstractSqmPluralJoin<L,C,E>
SqmJoinType joinType,
boolean fetched,
NodeBuilder nodeBuilder) {
super( lhs, joinedNavigable, alias, joinType, fetched, nodeBuilder );
super(
lhs,
joinedNavigable.createNavigablePath( lhs, alias ),
joinedNavigable,
alias,
joinType,
fetched,
nodeBuilder
);
}
protected AbstractSqmPluralJoin(

View File

@ -15,7 +15,6 @@ import org.hibernate.metamodel.model.domain.TreatableDomainType;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.spi.NavigablePath;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SqmJoinable;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmFrom;
@ -25,6 +24,7 @@ import org.hibernate.query.sqm.tree.from.SqmTreatedAttributeJoin;
* @author Steve Ebersole
*/
public class SqmSingularJoin<O,T> extends AbstractSqmAttributeJoin<O,T> implements SqmSingularValuedJoin<O,T> {
public SqmSingularJoin(
SqmFrom<?,O> lhs,
SingularPersistentAttribute<O, T> joinedNavigable,
@ -32,17 +32,15 @@ public class SqmSingularJoin<O,T> extends AbstractSqmAttributeJoin<O,T> implemen
SqmJoinType joinType,
boolean fetched,
NodeBuilder nodeBuilder) {
super( lhs, joinedNavigable, alias, joinType, fetched, nodeBuilder );
}
public SqmSingularJoin(
SqmFrom<?,O> lhs,
SqmJoinable<? extends O, T> joinedNavigable,
String alias,
SqmJoinType joinType,
boolean fetched,
NodeBuilder nodeBuilder) {
super( lhs, joinedNavigable, alias, joinType, fetched, nodeBuilder );
super(
lhs,
joinedNavigable.createNavigablePath( lhs, alias ),
joinedNavigable,
alias,
joinType,
fetched,
nodeBuilder
);
}
@Override

View File

@ -30,7 +30,7 @@ public interface SqmAttributeJoin<O,T> extends SqmJoin<O,T>, JpaFetch<O,T>, JpaJ
@Override
default boolean isImplicitlySelectable() {
return !isFetched();
return !isFetched() && !isImplicitJoin();
}
@Override
@ -39,8 +39,16 @@ public interface SqmAttributeJoin<O,T> extends SqmJoin<O,T>, JpaFetch<O,T>, JpaJ
@Override
JavaType<T> getJavaTypeDescriptor();
/**
* Is this a fetch join?
*/
boolean isFetched();
/**
* Is this an implicit join inferred from a path expression?
*/
boolean isImplicitJoin();
@Internal
void clearFetched();

View File

@ -102,7 +102,7 @@ public interface SqmFrom<L, R> extends SqmVisitableNode, SqmPath<R>, JpaFrom<L,
<Y> SqmEntityJoin<R, Y> join(Class<Y> entityClass, JoinType joinType);
@Incubating
boolean hasTrueJoin();
boolean hasImplicitlySelectableJoin();
@Override
<A> SqmSingularJoin<R, A> join(SingularAttribute<? super R, A> attribute);