HHH-17837 HHH-18202 Backport fk rendering-side logic for associations in order/group by
This commit is contained in:
parent
4772073a54
commit
d8c9f9dba9
|
@ -59,4 +59,9 @@ public interface BasicValuedModelPart extends BasicValuedMapping, ValuedModelPar
|
|||
default boolean hasPartitionedSelectionMapping() {
|
||||
return isPartitioned();
|
||||
}
|
||||
|
||||
@Override
|
||||
default BasicValuedModelPart asBasicValuedModelPart() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.metamodel.mapping;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
||||
|
||||
/**
|
||||
|
@ -21,6 +23,8 @@ public interface EntityAssociationMapping extends ModelPart, Association, TableG
|
|||
|
||||
EntityMappingType getAssociatedEntityMappingType();
|
||||
|
||||
Set<String> getTargetKeyPropertyNames();
|
||||
|
||||
/**
|
||||
* The model sub-part relative to the associated entity type that is the target
|
||||
* of this association's foreign-key
|
||||
|
|
|
@ -148,6 +148,11 @@ public interface ModelPart extends MappingModelExpressible {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
default BasicValuedModelPart asBasicValuedModelPart() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A short hand form of {@link #breakDownJdbcValues(Object, int, Object, Object, JdbcValueBiConsumer, SharedSessionContractImplementor)},
|
||||
* that passes 0 as offset and null for the two values {@code X} and {@code Y}.
|
||||
|
|
|
@ -60,7 +60,7 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa
|
|||
private final EntityMappingType associatedEntityTypeDescriptor;
|
||||
private final NotFoundAction notFoundAction;
|
||||
|
||||
private final Set<String> targetKeyPropertyNames;
|
||||
protected final Set<String> targetKeyPropertyNames;
|
||||
|
||||
public AbstractEntityCollectionPart(
|
||||
Nature nature,
|
||||
|
@ -110,10 +110,6 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa
|
|||
return getAssociatedEntityMappingType();
|
||||
}
|
||||
|
||||
protected Set<String> getTargetKeyPropertyNames() {
|
||||
return targetKeyPropertyNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableRole getNavigableRole() {
|
||||
return navigableRole;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.metamodel.mapping.internal;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
|
@ -135,6 +136,11 @@ public class ManyToManyCollectionPart extends AbstractEntityCollectionPart imple
|
|||
return super.findSubPart( name, targetType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTargetKeyPropertyNames() {
|
||||
return targetKeyPropertyNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X, Y> int breakDownJdbcValues(
|
||||
Object domainValue,
|
||||
|
|
|
@ -891,6 +891,7 @@ public class ToOneAttributeMapping
|
|||
return targetKeyPropertyName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getTargetKeyPropertyNames() {
|
||||
return targetKeyPropertyNames;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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.query.sqm.internal;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.metamodel.model.domain.DiscriminatorSqmPath;
|
||||
import org.hibernate.metamodel.model.domain.internal.EntityDiscriminatorSqmPath;
|
||||
import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker;
|
||||
import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
|
||||
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
|
||||
|
||||
/**
|
||||
* Generic {@link org.hibernate.query.sqm.SemanticQueryWalker} that applies the provided
|
||||
* {@link Consumer} to all {@link SqmPath paths} encountered during visitation.
|
||||
*
|
||||
* @author Marco Belladelli
|
||||
*/
|
||||
public class SqmPathVisitor extends BaseSemanticQueryWalker {
|
||||
private final Consumer<SqmPath<?>> pathConsumer;
|
||||
|
||||
public SqmPathVisitor(Consumer<SqmPath<?>> pathConsumer) {
|
||||
this.pathConsumer = pathConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitBasicValuedPath(SqmBasicValuedSimplePath<?> path) {
|
||||
pathConsumer.accept( path );
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath<?> path) {
|
||||
pathConsumer.accept( path );
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitEntityValuedPath(SqmEntityValuedSimplePath<?> path) {
|
||||
pathConsumer.accept( path );
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitAnyValuedValuedPath(SqmAnyValuedSimplePath<?> path) {
|
||||
pathConsumer.accept( path );
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitQualifiedAttributeJoin(SqmAttributeJoin<?, ?> path) {
|
||||
pathConsumer.accept( path );
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitTreatedPath(SqmTreatedPath<?, ?> path) {
|
||||
pathConsumer.accept( path );
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitDiscriminatorPath(EntityDiscriminatorSqmPath path) {
|
||||
pathConsumer.accept( path );
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitPluralValuedPath(SqmPluralValuedSimplePath<?> path) {
|
||||
pathConsumer.accept( path );
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitNonAggregatedCompositeValuedPath(NonAggregatedCompositeSimplePath<?> path) {
|
||||
pathConsumer.accept( path );
|
||||
return path;
|
||||
}
|
||||
}
|
|
@ -24,13 +24,14 @@ import org.hibernate.internal.util.collections.CollectionHelper;
|
|||
import org.hibernate.metamodel.MappingMetamodel;
|
||||
import org.hibernate.metamodel.mapping.BasicValuedMapping;
|
||||
import org.hibernate.metamodel.mapping.Bindable;
|
||||
import org.hibernate.metamodel.mapping.CollectionPart;
|
||||
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.metamodel.mapping.ManagedMappingType;
|
||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
|
@ -44,22 +45,28 @@ import org.hibernate.query.sqm.NodeBuilder;
|
|||
import org.hibernate.query.sqm.SqmQuerySource;
|
||||
import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess;
|
||||
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
||||
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||
import org.hibernate.query.sqm.tree.SqmDmlStatement;
|
||||
import org.hibernate.query.sqm.tree.SqmJoinType;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.from.SqmFrom;
|
||||
import org.hibernate.query.sqm.tree.from.SqmJoin;
|
||||
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.select.SqmOrderByClause;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
|
||||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
|
||||
import org.hibernate.spi.NavigablePath;
|
||||
import org.hibernate.sql.ast.Clause;
|
||||
import org.hibernate.sql.ast.SqlTreeCreationException;
|
||||
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
|
@ -73,6 +80,7 @@ import org.hibernate.type.internal.BasicTypeImpl;
|
|||
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
|
||||
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
|
||||
|
||||
/**
|
||||
|
@ -126,13 +134,57 @@ public class SqmUtil {
|
|||
}
|
||||
|
||||
/**
|
||||
* Utility that returns {@code true} if the specified {@link SqmPath sqmPath} should be
|
||||
* dereferenced using the target table mapping, i.e. when the path's lhs is an explicit join.
|
||||
* Utility that returns the entity association target's mapping type if the specified {@code sqmPath} should
|
||||
* be dereferenced using the target table, i.e. when the path's lhs is an explicit join that is used in the
|
||||
* group by clause, or defaults to the provided {@code modelPartContainer} otherwise.
|
||||
*/
|
||||
public static boolean needsTargetTableMapping(SqmPath<?> sqmPath, ModelPartContainer modelPartContainer) {
|
||||
return modelPartContainer.getPartMappingType() != modelPartContainer
|
||||
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
|
||||
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType;
|
||||
public static ModelPartContainer getTargetMappingIfNeeded(
|
||||
SqmPath<?> sqmPath,
|
||||
ModelPartContainer modelPartContainer,
|
||||
SqmToSqlAstConverter sqlAstCreationState) {
|
||||
final SqmQueryPart<?> queryPart = sqlAstCreationState.getCurrentSqmQueryPart();
|
||||
if ( queryPart != null ) {
|
||||
// We only need to do this for queries
|
||||
final Clause clause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
|
||||
if ( clause != Clause.FROM && modelPartContainer.getPartMappingType() != modelPartContainer && sqmPath.getLhs() instanceof SqmFrom<?, ?> ) {
|
||||
final ModelPart modelPart;
|
||||
if ( modelPartContainer instanceof PluralAttributeMapping ) {
|
||||
modelPart = getCollectionPart(
|
||||
(PluralAttributeMapping) modelPartContainer,
|
||||
castNonNull( sqmPath.getNavigablePath().getParent() )
|
||||
);
|
||||
}
|
||||
else {
|
||||
modelPart = modelPartContainer;
|
||||
}
|
||||
if ( modelPart instanceof EntityAssociationMapping ) {
|
||||
final EntityAssociationMapping association = (EntityAssociationMapping) modelPart;
|
||||
// If the path is one of the association's target key properties,
|
||||
// we need to render the target side if in group/order by
|
||||
if ( association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() )
|
||||
&& ( clause == Clause.GROUP || clause == Clause.ORDER
|
||||
|| !isFkOptimizationAllowed( sqmPath.getLhs() )
|
||||
|| queryPart.getFirstQuerySpec().groupByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState )
|
||||
|| queryPart.getFirstQuerySpec().orderByClauseContains( sqmPath.getNavigablePath(), sqlAstCreationState ) ) ) {
|
||||
return association.getAssociatedEntityMappingType();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return modelPartContainer;
|
||||
}
|
||||
|
||||
private static CollectionPart getCollectionPart(PluralAttributeMapping attribute, NavigablePath path) {
|
||||
final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( path.getLocalName() );
|
||||
if ( nature != null ) {
|
||||
switch ( nature ) {
|
||||
case ELEMENT:
|
||||
return attribute.getElementDescriptor();
|
||||
case INDEX:
|
||||
return attribute.getIndexDescriptor();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,6 +208,35 @@ public class SqmUtil {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static List<NavigablePath> getGroupByNavigablePaths(SqmQuerySpec<?> querySpec) {
|
||||
final List<SqmExpression<?>> expressions = querySpec.getGroupByClauseExpressions();
|
||||
if ( expressions.isEmpty() ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final List<NavigablePath> navigablePaths = new ArrayList<>( expressions.size() );
|
||||
final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
|
||||
for ( SqmExpression<?> expression : expressions ) {
|
||||
expression.accept( pathVisitor );
|
||||
}
|
||||
return navigablePaths;
|
||||
}
|
||||
|
||||
public static List<NavigablePath> getOrderByNavigablePaths(SqmQuerySpec<?> querySpec) {
|
||||
final SqmOrderByClause order = querySpec.getOrderByClause();
|
||||
if ( order == null || order.getSortSpecifications().isEmpty() ) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final List<SqmSortSpecification> sortSpecifications = order.getSortSpecifications();
|
||||
final List<NavigablePath> navigablePaths = new ArrayList<>( sortSpecifications.size() );
|
||||
final SqmPathVisitor pathVisitor = new SqmPathVisitor( path -> navigablePaths.add( path.getNavigablePath() ) );
|
||||
for ( SqmSortSpecification sortSpec : sortSpecifications ) {
|
||||
sortSpec.getSortExpression().accept( pathVisitor );
|
||||
}
|
||||
return navigablePaths;
|
||||
}
|
||||
|
||||
public static Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> generateJdbcParamsXref(
|
||||
DomainParameterXref domainParameterXref,
|
||||
JdbcParameterBySqmParameterAccess jdbcParameterBySqmParameterAccess) {
|
||||
|
|
|
@ -514,6 +514,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
private boolean negativeAdjustment;
|
||||
|
||||
private final Set<AssociationKey> visitedAssociationKeys = new HashSet<>();
|
||||
private final HashMap<MetadataKey<?, ?>, Object> metadata = new HashMap<>();
|
||||
private final MappingMetamodel domainModel;
|
||||
|
||||
public BaseSqmToSqlAstConverter(
|
||||
|
@ -8389,6 +8390,42 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
orderByFragments.add( new AbstractMap.SimpleEntry<>( orderByFragment, tableGroup ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S, M> M resolveMetadata(S source, Function<S, M> producer ) {
|
||||
//noinspection unchecked
|
||||
return (M) metadata.computeIfAbsent( new MetadataKey<>( source, producer ), k -> producer.apply( source ) );
|
||||
}
|
||||
|
||||
static class MetadataKey<S, M> {
|
||||
private final S source;
|
||||
private final Function<S, M> producer;
|
||||
|
||||
public MetadataKey(S source, Function<S, M> producer) {
|
||||
this.source = source;
|
||||
this.producer = producer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final MetadataKey<?, ?> that = (MetadataKey<?, ?>) o;
|
||||
return source.equals( that.source ) && producer.equals( that.producer );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = source.hashCode();
|
||||
result = 31 * result + producer.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResolvingCircularFetch() {
|
||||
return resolvingCircularFetch;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.query.sqm.sql;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
package org.hibernate.query.sqm.sql;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.hibernate.internal.util.collections.Stack;
|
||||
|
@ -23,6 +24,8 @@ import org.hibernate.sql.ast.tree.expression.Expression;
|
|||
import org.hibernate.sql.ast.tree.expression.QueryTransformer;
|
||||
import org.hibernate.sql.ast.tree.predicate.Predicate;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Specialized SemanticQueryWalker (SQM visitor) for producing SQL AST.
|
||||
*
|
||||
|
@ -52,4 +55,10 @@ public interface SqmToSqlAstConverter extends SemanticQueryWalker<Object>, SqlAs
|
|||
|
||||
Predicate visitNestedTopLevelPredicate(SqmPredicate predicate);
|
||||
|
||||
/**
|
||||
* Resolve a generic metadata object from the provided source, using the specified producer.
|
||||
*/
|
||||
default <S, M> M resolveMetadata(S source, Function<S, M> producer) {
|
||||
return producer.apply( source );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import java.util.function.Consumer;
|
|||
import org.hibernate.metamodel.MappingMetamodel;
|
||||
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.ManagedMappingType;
|
||||
import org.hibernate.metamodel.mapping.MappingType;
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
|
@ -34,7 +33,8 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
|
|||
import org.hibernate.sql.ast.tree.from.TableReference;
|
||||
import org.hibernate.sql.ast.tree.update.Assignable;
|
||||
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;
|
||||
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -81,22 +81,14 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
|
|||
}
|
||||
}
|
||||
|
||||
final BasicValuedModelPart mapping;
|
||||
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
|
||||
// We have to make sure we render the column of the target table
|
||||
mapping = (BasicValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
|
||||
sqmPath.getReferencedPathSource().getPathName(),
|
||||
treatTarget
|
||||
);
|
||||
}
|
||||
else {
|
||||
mapping = (BasicValuedModelPart) modelPartContainer.findSubPart(
|
||||
sqmPath.getReferencedPathSource().getPathName(),
|
||||
treatTarget
|
||||
);
|
||||
}
|
||||
// Use the target type to find the sub part if needed, otherwise just use the container
|
||||
final ModelPart modelPart = getTargetMappingIfNeeded(
|
||||
sqmPath,
|
||||
modelPartContainer,
|
||||
sqlAstCreationState
|
||||
).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget );
|
||||
|
||||
if ( mapping == null ) {
|
||||
if ( modelPart == null ) {
|
||||
if ( jpaQueryComplianceEnabled ) {
|
||||
// to get the better error, see if we got nothing because of treat handling
|
||||
final ModelPart subPart = tableGroup.getModelPart().findSubPart(
|
||||
|
@ -111,6 +103,7 @@ public class BasicValuedPathInterpretation<T> extends AbstractSqmPathInterpretat
|
|||
throw new UnknownPathException( "Path '" + sqmPath.getNavigablePath() + "' did not reference a known model part" );
|
||||
}
|
||||
|
||||
final BasicValuedModelPart mapping = castNonNull( modelPart.asBasicValuedModelPart() );
|
||||
final TableReference tableReference = tableGroup.resolveTableReference(
|
||||
sqmPath.getNavigablePath(),
|
||||
mapping,
|
||||
|
|
|
@ -13,7 +13,6 @@ import java.util.function.Consumer;
|
|||
import org.hibernate.metamodel.MappingMetamodel;
|
||||
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
import org.hibernate.metamodel.mapping.ManagedMappingType;
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||
|
@ -29,7 +28,7 @@ import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
|
|||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.update.Assignable;
|
||||
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -65,20 +64,12 @@ public class EmbeddableValuedPathInterpretation<T> extends AbstractSqmPathInterp
|
|||
}
|
||||
|
||||
final ModelPartContainer modelPartContainer = tableGroup.getModelPart();
|
||||
final EmbeddableValuedModelPart mapping;
|
||||
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
|
||||
// We have to make sure we render the column of the target table
|
||||
mapping = (EmbeddableValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
|
||||
sqmPath.getReferencedPathSource().getPathName(),
|
||||
treatTarget
|
||||
);
|
||||
}
|
||||
else {
|
||||
mapping = (EmbeddableValuedModelPart) modelPartContainer.findSubPart(
|
||||
sqmPath.getReferencedPathSource().getPathName(),
|
||||
treatTarget
|
||||
);
|
||||
}
|
||||
// Use the target type to find the sub part if needed, otherwise just use the container
|
||||
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) getTargetMappingIfNeeded(
|
||||
sqmPath,
|
||||
modelPartContainer,
|
||||
sqlAstCreationState
|
||||
).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget );
|
||||
|
||||
return new EmbeddableValuedPathInterpretation<>(
|
||||
mapping.toSqlExpression(
|
||||
|
|
|
@ -162,7 +162,9 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
|
|||
// we try to make use of it and the FK model part if possible based on the inferred mapping
|
||||
if ( mapping instanceof EntityAssociationMapping ) {
|
||||
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
|
||||
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
|
||||
final ModelPart keyTargetMatchPart = associationMapping.getForeignKeyDescriptor().getPart(
|
||||
associationMapping.getSideNature()
|
||||
);
|
||||
|
||||
if ( associationMapping.isFkOptimizationAllowed() ) {
|
||||
final boolean forceUsingForeignKeyAssociationSidePart;
|
||||
|
@ -265,7 +267,7 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
|
|||
if ( currentClause == Clause.GROUP || currentClause == Clause.ORDER ) {
|
||||
assert sqlAstCreationState.getCurrentSqmQueryPart().isSimpleQueryPart();
|
||||
final SqmQuerySpec<?> querySpec = sqlAstCreationState.getCurrentSqmQueryPart().getFirstQuerySpec();
|
||||
if ( currentClause == Clause.ORDER && !querySpec.groupByClauseContains( navigablePath ) ) {
|
||||
if ( currentClause == Clause.ORDER && !querySpec.groupByClauseContains( navigablePath, sqlAstCreationState ) ) {
|
||||
// We must ensure that the order by expression be expanded but only if the group by
|
||||
// contained the same expression, and that was expanded as well
|
||||
expandToAllColumns = false;
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.Internal;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.metamodel.mapping.CollectionPart;
|
||||
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
|
||||
|
@ -25,11 +26,12 @@ import org.hibernate.query.criteria.JpaSelection;
|
|||
import org.hibernate.query.sqm.FetchClauseType;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||
import org.hibernate.query.sqm.internal.SqmUtil;
|
||||
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.query.sqm.tree.SqmNode;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
|
@ -729,9 +731,32 @@ public class SqmQuerySpec<T> extends SqmQueryPart<T>
|
|||
}
|
||||
}
|
||||
|
||||
public boolean groupByClauseContains(NavigablePath path) {
|
||||
for ( SqmExpression<?> expression : groupByClauseExpressions ) {
|
||||
if ( expression instanceof SqmPath && ( (SqmPath<?>) expression ).getNavigablePath() == path ) {
|
||||
@Internal
|
||||
public boolean groupByClauseContains(NavigablePath navigablePath, SqmToSqlAstConverter sqlAstConverter) {
|
||||
if ( groupByClauseExpressions.isEmpty() ) {
|
||||
return false;
|
||||
}
|
||||
return navigablePathsContain( sqlAstConverter.resolveMetadata(
|
||||
this,
|
||||
SqmUtil::getGroupByNavigablePaths
|
||||
), navigablePath );
|
||||
}
|
||||
|
||||
@Internal
|
||||
public boolean orderByClauseContains(NavigablePath navigablePath, SqmToSqlAstConverter sqlAstConverter) {
|
||||
final SqmOrderByClause orderByClause = getOrderByClause();
|
||||
if ( orderByClause == null || orderByClause.getSortSpecifications().isEmpty() ) {
|
||||
return false;
|
||||
}
|
||||
return navigablePathsContain( sqlAstConverter.resolveMetadata(
|
||||
this,
|
||||
SqmUtil::getOrderByNavigablePaths
|
||||
), navigablePath );
|
||||
}
|
||||
|
||||
private boolean navigablePathsContain(List<NavigablePath> navigablePaths, NavigablePath navigablePath) {
|
||||
for ( NavigablePath path : navigablePaths ) {
|
||||
if ( path.isParentOrEqual( navigablePath ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,15 +52,15 @@ public class MapIssueTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testMapKeyJoinIsIncluded(SessionFactoryScope scope) {
|
||||
public void testMapKeyJoinIsOmitted(SessionFactoryScope scope) {
|
||||
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
scope.inTransaction(
|
||||
s -> {
|
||||
s.createQuery( "select c from MapOwner as o join o.contents c join c.relationship r where r.id is not null" ).list();
|
||||
statementInspector.assertExecutedCount( 1 );
|
||||
// Assert 3 joins, collection table, collection element and relationship
|
||||
statementInspector.assertNumberOfJoins( 0, 3 );
|
||||
// Assert 2 joins, collection table and collection element. No need to join the relationship because it is not nullable
|
||||
statementInspector.assertNumberOfJoins( 0, 2 );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -152,8 +152,9 @@ public class EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest {
|
|||
.getSingleResult();
|
||||
|
||||
statementInspector.assertExecutedCount( 2 );
|
||||
// The join to the target table PARENT for Male#parent is added since it's explicitly joined in HQL
|
||||
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 2 );
|
||||
// The join to the target table PARENT for Male#parent is avoided,
|
||||
// because the FK in the collection table is not-null and data from the target table is not needed
|
||||
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
|
||||
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 );
|
||||
assertThat( son.getParent(), CoreMatchers.notNullValue() );
|
||||
|
||||
|
|
Loading…
Reference in New Issue