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

View File

@ -12,7 +12,6 @@ import org.hibernate.query.criteria.JpaExpression;
import org.hibernate.query.criteria.JpaPredicate; import org.hibernate.query.criteria.JpaPredicate;
import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmJoinable;
import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.spi.SqmCreationHelper;
import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.SqmJoinType;
@ -33,48 +32,38 @@ public abstract class AbstractSqmAttributeJoin<L, R>
extends AbstractSqmJoin<L, R> extends AbstractSqmJoin<L, R>
implements SqmAttributeJoin<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( protected AbstractSqmAttributeJoin(
SqmFrom<?, L> lhs, SqmFrom<?, L> lhs,
NavigablePath navigablePath, NavigablePath navigablePath,
SqmJoinable joinedNavigable, SqmPathSource<R> joinedNavigable,
String alias, String alias,
SqmJoinType joinType, SqmJoinType joinType,
boolean fetched, boolean fetchJoin,
NodeBuilder nodeBuilder) { NodeBuilder nodeBuilder) {
//noinspection unchecked
super( super(
navigablePath, navigablePath,
(SqmPathSource<R>) joinedNavigable, joinedNavigable,
lhs, lhs,
alias, isImplicitAlias( alias ) ? null : alias,
joinType, joinType,
nodeBuilder nodeBuilder
); );
this.fetched = fetched; this.fetchJoin = fetchJoin;
validateFetchAlias( alias ); 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 @Override
@ -89,7 +78,7 @@ public abstract class AbstractSqmAttributeJoin<L, R>
@Override @Override
public boolean isFetched() { public boolean isFetched() {
return fetched; return fetchJoin;
} }
@Override @Override
@ -100,11 +89,11 @@ public abstract class AbstractSqmAttributeJoin<L, R>
@Override @Override
public void clearFetched() { public void clearFetched() {
fetched = false; fetchJoin = false;
} }
private void validateFetchAlias(String alias) { private void validateFetchAlias(String alias) {
if ( fetched && alias != null && nodeBuilder().isJpaQueryComplianceEnabled() ) { if ( fetchJoin && alias != null && nodeBuilder().isJpaQueryComplianceEnabled() ) {
throw new IllegalStateException( throw new IllegalStateException(
"The JPA specification does not permit specifying an alias for fetch joins." "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.SetAttribute;
import jakarta.persistence.metamodel.SingularAttribute; import jakarta.persistence.metamodel.SingularAttribute;
import static org.hibernate.metamodel.AttributeClassification.EMBEDDED;
import static org.hibernate.query.sqm.internal.SqmUtil.findCompatibleFetchJoin; 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; SqmPath<?> resolvedPath = null;
for ( SqmJoin<?, ?> sqmJoin : getSqmJoins() ) { for ( SqmJoin<?, ?> sqmJoin : getSqmJoins() ) {
// We can only match singular joins here, as plural path parts are interpreted like sub-queries // 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() ) ) { && name.equals( sqmJoin.getReferencedPathSource().getPathName() ) ) {
final SqmAttributeJoin<?, ?> attributeJoin = (SqmAttributeJoin<?, ?>) sqmJoin;
if ( attributeJoin.getOn() == null ) { if ( attributeJoin.getOn() == null ) {
// todo (6.0): to match the expectation of the JPA spec I think we also have to check // 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, // 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() { public void removeLeftFetchJoins() {
if ( joins != null ) { if ( joins != null ) {
for ( SqmJoin<T, ?> join : new ArrayList<>(joins) ) { for ( SqmJoin<T, ?> join : new ArrayList<>(joins) ) {
if ( join instanceof SqmAttributeJoin ) { if ( join instanceof SqmAttributeJoin<T, ?> attributeJoin ) {
final SqmAttributeJoin<T, ?> attributeJoin = (SqmAttributeJoin<T, ?>) join;
if ( attributeJoin.isFetched() ) { if ( attributeJoin.isFetched() ) {
if ( join.getSqmJoinType() == SqmJoinType.LEFT ) { if ( join.getSqmJoinType() == SqmJoinType.LEFT ) {
joins.remove( join ); joins.remove( join );
@ -306,11 +303,10 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
} }
@Override @Override
public boolean hasTrueJoin() { public boolean hasImplicitlySelectableJoin() {
return getSqmJoins().stream() return getSqmJoins().stream()
.anyMatch( sqmJoin -> sqmJoin instanceof SqmAttributeJoin<?,?> attributeJoin .anyMatch( sqmJoin -> sqmJoin instanceof SqmAttributeJoin<?,?> attributeJoin
&& !attributeJoin.isFetched() && attributeJoin.isImplicitlySelectable() );
&& attributeJoin.getAttribute().getAttributeClassification()!=EMBEDDED );
} }
@Override @Override
@ -642,8 +638,7 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
@Override @Override
public <X> JpaCrossJoin<X> crossJoin(EntityDomainType<X> entity) { public <X> JpaCrossJoin<X> crossJoin(EntityDomainType<X> entity) {
//noinspection unchecked final SqmCrossJoin<X> crossJoin = new SqmCrossJoin<>( entity, null, findRoot() );
final SqmCrossJoin<X> crossJoin = new SqmCrossJoin<>( entity, null, (SqmRoot<X>) findRoot() );
// noinspection unchecked // noinspection unchecked
addSqmJoin( (SqmJoin<T, ?>) crossJoin ); addSqmJoin( (SqmJoin<T, ?>) crossJoin );
return crossJoin; return crossJoin;

View File

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

View File

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

View File

@ -30,7 +30,7 @@ public interface SqmAttributeJoin<O,T> extends SqmJoin<O,T>, JpaFetch<O,T>, JpaJ
@Override @Override
default boolean isImplicitlySelectable() { default boolean isImplicitlySelectable() {
return !isFetched(); return !isFetched() && !isImplicitJoin();
} }
@Override @Override
@ -39,8 +39,16 @@ public interface SqmAttributeJoin<O,T> extends SqmJoin<O,T>, JpaFetch<O,T>, JpaJ
@Override @Override
JavaType<T> getJavaTypeDescriptor(); JavaType<T> getJavaTypeDescriptor();
/**
* Is this a fetch join?
*/
boolean isFetched(); boolean isFetched();
/**
* Is this an implicit join inferred from a path expression?
*/
boolean isImplicitJoin();
@Internal @Internal
void clearFetched(); 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); <Y> SqmEntityJoin<R, Y> join(Class<Y> entityClass, JoinType joinType);
@Incubating @Incubating
boolean hasTrueJoin(); boolean hasImplicitlySelectableJoin();
@Override @Override
<A> SqmSingularJoin<R, A> join(SingularAttribute<? super R, A> attribute); <A> SqmSingularJoin<R, A> join(SingularAttribute<? super R, A> attribute);