Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ private static boolean hasCustomEventListeners(EventSource source) {
// Bean Validation adds a PRE_DELETE listener
// and Envers adds a POST_DELETE listener
return fss.eventListenerGroup_PRE_DELETE.count() > 0
|| fss.eventListenerGroup_POST_COMMIT_DELETE.count() > 0
|| fss.eventListenerGroup_POST_DELETE.count() > 1
|| fss.eventListenerGroup_POST_DELETE.count() == 1
&& !(fss.eventListenerGroup_POST_DELETE.listeners().iterator().next()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,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 @@ -19,6 +19,8 @@
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.type.descriptor.java.JavaType;

import org.checkerframework.checker.nullness.qual.Nullable;

/**
* Base descriptor, within the mapping model, for any part of the
* application's domain model: an attribute, an entity identifier,
Expand Down Expand Up @@ -146,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 @@ -62,7 +62,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 @@ -112,10 +112,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 @@ -137,6 +138,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 @@ -873,6 +873,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
Expand Up @@ -75,4 +75,8 @@ public boolean hasQueryExecutionToBeAddedToStatistics() {
return true;
}

@Override
public boolean upgradeLocks() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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) {
super( null );
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 @@ -47,13 +48,17 @@
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.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.select.SqmOrderByClause;
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
import org.hibernate.query.sqm.tree.jpa.ParameterCollector;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
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;
Expand All @@ -69,6 +74,9 @@
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;

/**
* Helper utilities for dealing with SQM
*
Expand Down Expand Up @@ -119,31 +127,110 @@ public static IllegalQueryOperationException expectingNonSelect(SqmStatement<?>
);
}

public static boolean needsTargetTableMapping(
/**
* 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 ModelPartContainer getTargetMappingIfNeeded(
SqmPath<?> sqmPath,
ModelPartContainer modelPartContainer,
SqmToSqlAstConverter sqlAstCreationState) {
final Clause currentClause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
return ( currentClause == Clause.GROUP || currentClause == Clause.SELECT || currentClause == Clause.ORDER || currentClause == Clause.HAVING )
&& modelPartContainer.getPartMappingType() != modelPartContainer
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType
&& ( groupByClauseContains( sqlAstCreationState.getCurrentSqmQueryPart(), sqmPath.getNavigablePath() )
|| isNonOptimizableJoin( sqmPath.getLhs() ) );
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 boolean groupByClauseContains(SqmQueryPart<?> sqmQueryPart, NavigablePath path) {
return sqmQueryPart.isSimpleQueryPart() && sqmQueryPart.getFirstQuerySpec().groupByClauseContains( path );
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;
}

private static boolean isNonOptimizableJoin(SqmPath<?> sqmPath) {
/**
* Utility that returns {@code false} when the provided {@link SqmPath sqmPath} is
* a join that cannot be dereferenced through the foreign key on the associated table,
* i.e. a join that's neither {@linkplain SqmJoinType#INNER} nor {@linkplain SqmJoinType#LEFT}
* or one that has an explicit on clause predicate.
*/
public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath) {
if ( sqmPath instanceof SqmJoin<?, ?> ) {
final SqmJoinType sqmJoinType = ( (SqmJoin<?, ?>) sqmPath ).getSqmJoinType();
return sqmJoinType != SqmJoinType.INNER && sqmJoinType != SqmJoinType.LEFT;
final SqmJoin<?, ?> sqmJoin = (SqmJoin<?, ?>) sqmPath;
switch ( sqmJoin.getSqmJoinType() ) {
case INNER:
case LEFT:
return !( sqmJoin instanceof SqmQualifiedJoin<?, ?>)
|| ( (SqmQualifiedJoin<?, ?>) sqmJoin ).getJoinPredicate() == null;
default:
return false;
}
}
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 Expand Up @@ -523,7 +610,7 @@ public static SqmStatement.ParameterResolutions resolveParameters(SqmStatement<?
if ( statement.getQuerySource() == SqmQuerySource.CRITERIA ) {
final CriteriaParameterCollector parameterCollector = new CriteriaParameterCollector();

ParameterCollector.collectParameters(
collectParameters(
statement,
parameterCollector::process,
statement.nodeBuilder().getServiceRegistry()
Expand Down
Loading