Skip to content

Commit

Permalink
HHH-17837 HHH-18202 Backport fk rendering-side logic for associations…
Browse files Browse the repository at this point in the history
… in order/group by
  • Loading branch information
mbladel committed Jun 7, 2024
1 parent be7f17f commit c0d4694
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,9 @@ default int forEachSelectable(SelectableConsumer consumer) {
default boolean hasPartitionedSelectionMapping() {
return isPartitioned();
}

@Override
default BasicValuedModelPart asBasicValuedModelPart() {
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/
package org.hibernate.metamodel.mapping;

import java.util.Set;

import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;

/**
Expand All @@ -21,6 +23,8 @@ default String getFetchableName() {

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ default EntityMappingType asEntityMappingType(){
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}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -110,10 +110,6 @@ public EntityMappingType getMappedType() {
return getAssociatedEntityMappingType();
}

protected Set<String> getTargetKeyPropertyNames() {
return targetKeyPropertyNames;
}

@Override
public NavigableRole getNavigableRole() {
return navigableRole;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -135,6 +136,11 @@ public ModelPart findSubPart(String name, EntityMappingType targetType) {
return super.findSubPart( name, targetType );
}

@Override
public Set<String> getTargetKeyPropertyNames() {
return targetKeyPropertyNames;
}

@Override
public <X, Y> int breakDownJdbcValues(
Object domainValue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,7 @@ public String getTargetKeyPropertyName() {
return targetKeyPropertyName;
}

@Override
public Set<String> getTargetKeyPropertyNames() {
return targetKeyPropertyNames;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
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;
Expand All @@ -44,22 +45,28 @@
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;
Expand All @@ -73,6 +80,7 @@
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;

/**
Expand Down Expand Up @@ -126,13 +134,57 @@ public static IllegalQueryOperationException expectingNonSelect(SqmStatement<?>
}

/**
* 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;
}

/**
Expand All @@ -156,6 +208,35 @@ public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath) {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -8389,6 +8390,42 @@ private void applyOrdering(TableGroup tableGroup, OrderByFragment orderByFragmen
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit c0d4694

Please sign in to comment.