Fix determining table groups for fetches and reuse joins for parsed paths. Fix determining correct table group for SqmFrom usages. Fix indexed access for plural paths

This commit is contained in:
Christian Beikov 2021-09-22 23:02:56 +02:00
parent 07f6d31d2b
commit aa7b5529e9
22 changed files with 377 additions and 142 deletions

View File

@ -176,7 +176,8 @@ public abstract class AbstractCompositeIdentifierMapping
final CompositeTableGroup compositeTableGroup = new CompositeTableGroup(
navigablePath,
this,
lhs
lhs,
fetched
);
final TableGroupJoin join = new TableGroupJoin( navigablePath, SqlAstJoinType.LEFT, compositeTableGroup, null );

View File

@ -275,7 +275,8 @@ public class EmbeddedAttributeMapping
final CompositeTableGroup compositeTableGroup = new CompositeTableGroup(
navigablePath,
this,
lhs
lhs,
fetched
);
TableGroupJoin tableGroupJoin = new TableGroupJoin(

View File

@ -206,7 +206,7 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
SqlAstCreationContext creationContext) {
assert lhs.getModelPart() instanceof PluralAttributeMapping;
final TableGroup tableGroup = new CompositeTableGroup( navigablePath, this, lhs );
final TableGroup tableGroup = new CompositeTableGroup( navigablePath, this, lhs, fetched );
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
navigablePath,

View File

@ -275,14 +275,29 @@ public class ToOneAttributeMapping
) );
}
if ( referencedPropertyName == null ) {
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
targetKeyPropertyNames.add( EntityIdentifierMapping.ROLE_LOCAL_NAME );
final IdentifierProperty identifierProperty = getEntityMappingType()
.getEntityPersister()
.getEntityMetamodel()
.getIdentifierProperty();
this.targetKeyPropertyName = identifierProperty.getName();
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
targetKeyPropertyNames.add( EntityIdentifierMapping.ROLE_LOCAL_NAME );
addPrefixedPropertyNames( targetKeyPropertyNames, targetKeyPropertyName, identifierProperty.getType() );
final Type propertyType = identifierProperty.getType();
if ( identifierProperty.getName() == null ) {
final CompositeType compositeType;
if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded()
&& compositeType.getPropertyNames().length == 1 ) {
this.targetKeyPropertyName = compositeType.getPropertyNames()[0];
addPrefixedPropertyNames( targetKeyPropertyNames, targetKeyPropertyName, compositeType.getSubtypes()[0] );
}
else {
this.targetKeyPropertyName = EntityIdentifierMapping.ROLE_LOCAL_NAME;
addPrefixedPropertyNames( targetKeyPropertyNames, null, propertyType );
}
}
else {
this.targetKeyPropertyName = identifierProperty.getName();
addPrefixedPropertyNames( targetKeyPropertyNames, targetKeyPropertyName, propertyType );
}
this.targetKeyPropertyNames = targetKeyPropertyNames;
}
else if ( bootValue.isReferenceToPrimaryKey() ) {
@ -334,22 +349,24 @@ public class ToOneAttributeMapping
Set<String> targetKeyPropertyNames,
String prefix,
Type type) {
if ( type.isComponentType() ) {
if ( prefix != null ) {
targetKeyPropertyNames.add( prefix );
}
if ( type.isComponentType() ) {
final ComponentType componentType = (ComponentType) type;
final String[] propertyNames = componentType.getPropertyNames();
final Type[] componentTypeSubtypes = componentType.getSubtypes();
for ( int i = 0, propertyNamesLength = propertyNames.length; i < propertyNamesLength; i++ ) {
addPrefixedPropertyNames(
targetKeyPropertyNames,
prefix + "." + propertyNames[i],
componentTypeSubtypes[i]
);
final String newPrefix;
if ( prefix == null ) {
newPrefix = propertyNames[i];
}
else {
newPrefix = prefix + "." + propertyNames[i];
}
addPrefixedPropertyNames( targetKeyPropertyNames, newPrefix, componentTypeSubtypes[i] );
}
}
else {
targetKeyPropertyNames.add( prefix );
}
}
public ToOneAttributeMapping copy() {
@ -849,6 +866,7 @@ public class ToOneAttributeMapping
);
}
@Override
public SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup) {
if ( isNullable ) {
return SqlAstJoinType.LEFT;
@ -903,6 +921,7 @@ public class ToOneAttributeMapping
final LazyTableGroup lazyTableGroup = new LazyTableGroup(
canUseInnerJoin,
navigablePath,
fetched,
() -> createTableGroupJoinInternal(
canUseInnerJoin,
navigablePath,
@ -916,10 +935,10 @@ public class ToOneAttributeMapping
if ( !canUseParentTableGroup ) {
return false;
}
// Special case for resolving the table group for entity valued paths
if ( np == navigablePath ) {
return true;
}
// // Special case for resolving the table group for entity valued paths
// if ( np == navigablePath ) {
// return true;
// }
NavigablePath path = np.getParent();
// Fast path
if ( path != null && navigablePath.equals( path ) ) {

View File

@ -144,7 +144,7 @@ public class BasicDotIdentifierConsumer implements DotIdentifierConsumer {
// identifier is an "unqualified attribute reference"
validateAsRoot( pathRootByExposedNavigable );
SqmPath<?> sqmPath = (SqmPath<?>) pathRootByExposedNavigable.get( identifier );
final SqmPath<?> sqmPath = pathRootByExposedNavigable.get( identifier );
if ( isTerminal ) {
return sqmPath;
}

View File

@ -7,12 +7,19 @@
package org.hibernate.query.hql.internal;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.hql.HqlLogging;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
import org.hibernate.query.sqm.tree.from.SqmEntityJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
/**
* Specialized "intermediate" SemanticPathPart for processing domain model paths
@ -42,22 +49,53 @@ public class DomainPathPart implements SemanticPathPart {
currentPath,
name
);
// if we want to allow re-use of matched unaliased SqmFrom nodes
//
// final SqmPathRegistry pathRegistry = creationState.getCurrentProcessingState().getPathRegistry();
// final NavigablePath possibleImplicitAliasPath = lhs.getNavigablePath().append( name );
// final SqmPath fromByImplicitAlias = pathRegistry.findPath( possibleImplicitAliasPath );
//
// if ( fromByImplicitAlias != null ) {
// if ( fromByImplicitAlias instanceof SqmFrom ) {
// final String explicitPathAlias = fromByImplicitAlias.getExplicitAlias();
// if ( explicitPathAlias == null || Objects.equals( possibleImplicitAliasPath.getFullPath(), explicitPathAlias ) ) {
// currentPath = fromByImplicitAlias;
// return isTerminal ? currentPath : this;
// }
// }
// }
currentPath = (SqmPath<?>) currentPath.get( name );
final SqmPath<?> reusablePath = currentPath.getReusablePath( name );
if ( reusablePath != null ) {
currentPath = reusablePath;
}
else {
// Try to resolve an existing attribute join without ON clause
SqmPath<?> resolvedPath = null;
if ( currentPath instanceof SqmFrom<?, ?> ) {
ModelPartContainer modelPartContainer = null;
for ( SqmJoin<?, ?> sqmJoin : ( (SqmFrom<?, ?>) currentPath ).getSqmJoins() ) {
if ( sqmJoin instanceof SqmAttributeJoin<?, ?>
&& 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,
// but as far as I understand, in 5.x we expect to ignore this behavior
// if ( attributeJoin.getSqmJoinType() != SqmJoinType.INNER ) {
// if ( attributeJoin.getAttribute().isCollection() ) {
// continue;
// }
// if ( modelPartContainer == null ) {
// modelPartContainer = findModelPartContainer( attributeJoin, creationState );
// }
// final TableGroupJoinProducer joinProducer = (TableGroupJoinProducer) modelPartContainer.findSubPart(
// name,
// null
// );
// if ( attributeJoin.getSqmJoinType().getCorrespondingSqlJoinType() != joinProducer.getDefaultSqlAstJoinType( null ) ) {
// continue;
// }
// }
resolvedPath = sqmJoin;
if ( attributeJoin.isFetched() ) {
break;
}
}
}
}
}
if ( resolvedPath == null ) {
currentPath = currentPath.get( name );
}
else {
currentPath = resolvedPath;
}
}
if ( isTerminal ) {
return currentPath;
}
@ -66,6 +104,45 @@ public class DomainPathPart implements SemanticPathPart {
}
}
private ModelPartContainer findModelPartContainer(SqmAttributeJoin<?, ?> attributeJoin, SqmCreationState creationState) {
final SqmFrom<?, ?> lhs = attributeJoin.getLhs();
if ( lhs instanceof SqmAttributeJoin<?, ?> ) {
final SqmAttributeJoin<?, ?> lhsAttributeJoin = (SqmAttributeJoin<?, ?>) lhs;
if ( lhsAttributeJoin.getReferencedPathSource() instanceof EntityDomainType<?> ) {
final String entityName = ( (EntityDomainType<?>) lhsAttributeJoin.getReferencedPathSource() ).getHibernateEntityName();
return (ModelPartContainer) creationState.getCreationContext().getQueryEngine()
.getTypeConfiguration()
.getSessionFactory()
.getMetamodel()
.entityPersister( entityName )
.findSubPart( attributeJoin.getAttribute().getName(), null );
}
else {
return (ModelPartContainer) findModelPartContainer( lhsAttributeJoin, creationState )
.findSubPart( attributeJoin.getAttribute().getName(), null );
}
}
else {
final String entityName;
if ( lhs instanceof SqmRoot<?> ) {
entityName = ( (SqmRoot<?>) lhs ).getEntityName();
}
else if ( lhs instanceof SqmEntityJoin<?> ) {
entityName = ( (SqmEntityJoin<?>) lhs ).getEntityName();
}
else {
assert lhs instanceof SqmCrossJoin<?>;
entityName = ( (SqmCrossJoin<?>) lhs ).getEntityName();
}
return (ModelPartContainer) creationState.getCreationContext().getQueryEngine()
.getTypeConfiguration()
.getSessionFactory()
.getMetamodel()
.entityPersister( entityName )
.findSubPart( attributeJoin.getAttribute().getName(), null );
}
}
@Override
public SqmPath resolveIndexedAccess(
SqmExpression selector,

View File

@ -55,7 +55,6 @@ import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
import org.hibernate.query.BinaryArithmeticOperator;
import org.hibernate.query.ComparisonOperator;
import org.hibernate.query.FetchClauseType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.NullPrecedence;
import org.hibernate.query.PathException;
import org.hibernate.query.SemanticException;
@ -97,8 +96,6 @@ import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom;
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmListJoin;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmMapJoin;
import org.hibernate.query.sqm.tree.domain.SqmMaxElementPath;
@ -3962,7 +3959,10 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override
public SemanticPathPart visitGeneralPathFragment(HqlParser.GeneralPathFragmentContext ctx) {
return visitDotIdentifierSequence( (HqlParser.DotIdentifierSequenceContext) ctx.getChild( 0 ) );
return visitIndexedPathAccessFragment(
(HqlParser.DotIdentifierSequenceContext) ctx.getChild( 0 ),
ctx.getChildCount() == 1 ? null : (HqlParser.IndexedPathAccessFragmentContext) ctx.getChild( 1 )
);
}
@Override
@ -3981,84 +3981,37 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return visitMapKeyNavigablePath( (HqlParser.MapKeyNavigablePathContext) firstChild );
}
else if ( firstChild instanceof HqlParser.DotIdentifierSequenceContext && ctx.getChildCount() == 2 ) {
dotIdentifierConsumerStack.push(
new QualifiedJoinPathConsumer(
(SqmRoot<?>) dotIdentifierConsumerStack.getCurrent().getConsumedPart(),
SqmJoinType.INNER,
false,
null,
this
)
return visitIndexedPathAccessFragment(
(HqlParser.DotIdentifierSequenceContext) firstChild,
(HqlParser.IndexedPathAccessFragmentContext) ctx.getChild( 1 )
);
final SqmAttributeJoin<?, ?> indexedJoinPath;
try {
indexedJoinPath = (SqmAttributeJoin<?, ?>) firstChild.accept( this );
}
finally {
dotIdentifierConsumerStack.pop();
}
dotIdentifierConsumerStack.push(
new BasicDotIdentifierConsumer( indexedJoinPath, this ) {
@Override
protected void reset() {
}
}
);
try {
return (SemanticPathPart) ctx.getChild( 1 ).accept( this );
}
finally {
dotIdentifierConsumerStack.pop();
}
}
throw new ParsingException( "Unsure how to process `syntacticDomainPath` over : " + ctx.getText() );
}
@Override
public SemanticPathPart visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext ctx) {
final DotIdentifierConsumer consumer = dotIdentifierConsumerStack.pop();
final SqmExpression<?> indexExpression = (SqmExpression<?>) ctx.getChild( 1 ).accept( this );
final SqmAttributeJoin<?, ?> attributeJoin = (SqmAttributeJoin<?, ?>) consumer.getConsumedPart();
final NavigablePath navigablePath = attributeJoin.getNavigablePath().getParent().append(
attributeJoin.getNavigablePath().getLocalName(),
indexExpression.toHqlString()
);
// Reuse an existing indexed path join if possible
for ( SqmJoin<?, ?> sqmJoin : attributeJoin.getSqmJoins() ) {
if ( sqmJoin.getNavigablePath().getLocalName().equals( navigablePath.getLocalName() ) ) {
return sqmJoin;
}
private SemanticPathPart visitIndexedPathAccessFragment(
HqlParser.DotIdentifierSequenceContext ctx,
HqlParser.IndexedPathAccessFragmentContext idxCtx) {
final SemanticPathPart pathPart = visitDotIdentifierSequence( ctx );
if ( idxCtx == null ) {
return pathPart;
}
final SqmExpression<?> index;
if ( attributeJoin instanceof SqmListJoin<?, ?> ) {
index = ( (SqmListJoin<?, ?>) attributeJoin ).index();
final SqmExpression<?> indexExpression = (SqmExpression<?>) idxCtx.getChild( 1 ).accept(this );
final boolean hasIndexContinuation = idxCtx.getChildCount() == 5;
final SqmPath<?> indexedPath = pathPart.resolveIndexedAccess( indexExpression, !hasIndexContinuation, this );
if ( hasIndexContinuation ) {
return (SemanticPathPart) idxCtx.getChild( 4 ).accept( this );
}
else if ( attributeJoin instanceof SqmMapJoin<?, ?, ?> ) {
index = ( (SqmMapJoin<?, ?, ?>) attributeJoin ).key();
}
else {
throw new SemanticException( "Index access is only supported on list or map attributes: " + attributeJoin.getNavigablePath() );
}
attributeJoin.setJoinPredicate( creationContext.getNodeBuilder().equal( index, indexExpression ) );
final SqmIndexedCollectionAccessPath<?> path = new SqmIndexedCollectionAccessPath<>(
navigablePath,
attributeJoin,
indexExpression
);
dotIdentifierConsumerStack.push(
new BasicDotIdentifierConsumer( path, this ) {
@Override
protected void reset() {
}
}
);
if ( ctx.getChildCount() == 5 ) {
return (SemanticPathPart) ctx.getChild( 4 ).accept( this );
}
return path;
return indexedPath;
}
@Override
public SemanticPathPart visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext idxCtx) {
throw new UnsupportedOperationException( "Should be handled by #visitIndexedPathAccessFragment" );
}
@Override

View File

@ -215,6 +215,38 @@ public class SqmPathRegistryImpl implements SqmPathRegistry {
return (X) found;
}
@Override
public <X extends SqmFrom<?, ?>> X resolveFrom(NavigablePath navigablePath, Function<NavigablePath, SqmFrom<?, ?>> creator) {
SqmTreeCreationLogger.LOGGER.tracef( "SqmProcessingIndex#resolvePath(NavigablePath) : %s", navigablePath );
final SqmFrom<?, ?> existing = sqmFromByPath.get( navigablePath );
if ( existing != null ) {
//noinspection unchecked
return (X) existing;
}
final SqmFrom<?, ?> sqmFrom = creator.apply( navigablePath );
register( sqmFrom );
//noinspection unchecked
return (X) sqmFrom;
}
@Override
public <X extends SqmFrom<?, ?>> X resolveFrom(SqmPath<?> path) {
SqmTreeCreationLogger.LOGGER.tracef( "SqmProcessingIndex#resolvePath(SqmPath) : %s", path );
final SqmFrom<?, ?> existing = sqmFromByPath.get( path.getNavigablePath() );
if ( existing != null ) {
//noinspection unchecked
return (X) existing;
}
final SqmFrom<?, ?> sqmFrom = resolveFrom( path.getLhs() ).join( path.getNavigablePath().getUnaliasedLocalName() );
register( sqmFrom );
//noinspection unchecked
return (X) sqmFrom;
}
@Override
public <X> SqmPath<X> resolvePath(NavigablePath navigablePath, Function<NavigablePath, SqmPath<X>> creator) {
SqmTreeCreationLogger.LOGGER.tracef( "SqmProcessingIndex#resolvePath(NavigablePath) : %s", navigablePath );

View File

@ -55,6 +55,22 @@ public interface SqmPathRegistry {
*/
<X extends SqmFrom<?, ?>> X findFromExposing(String navigableName);
/**
* Similar to {@link #findFromByPath}, but accepting a producer to be used
* to create and register a SqmFrom if none yet registered.
*
* @return The existing or just-created SqmFrom
*/
<X extends SqmFrom<?, ?>> X resolveFrom(NavigablePath path, Function<NavigablePath, SqmFrom<?, ?>> creator);
/**
* Similar to {@link #resolveFrom}, but accepting a SqmPath to be used
* to create and register a SqmFrom if none yet registered.
*
* @return The existing or just-created SqmFrom
*/
<X extends SqmFrom<?, ?>> X resolveFrom(SqmPath<?> path);
/**
* Find an SqmPath by its NavigablePath. Will return a SqmFrom if the NavigablePath
* has (yet) been resolved to a SqmFrom. Otherwise, it will be a non-SqmFrom SqmPath

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.query.sqm.spi;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
@ -29,8 +31,11 @@ public class SqmCreationHelper {
"`lhs` cannot be null for a sub-navigable reference - " + subNavigable
);
}
return buildSubNavigablePath( lhs.getNavigablePath(), subNavigable, alias );
NavigablePath navigablePath = lhs.getNavigablePath();
if ( lhs.getReferencedPathSource() instanceof PluralPersistentAttribute<?, ?, ?> ) {
navigablePath = navigablePath.append( CollectionPart.Nature.ELEMENT.getName() );
}
return buildSubNavigablePath( navigablePath, subNavigable, alias );
}
private SqmCreationHelper() {

View File

@ -2401,7 +2401,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
if ( modelPart instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) modelPart;
keyPart = toOneAttributeMapping.findSubPart( toOneAttributeMapping.getTargetKeyPropertyName() );
resultPart = toOneAttributeMapping;
resultPart = modelPart;
}
else if ( modelPart instanceof PluralAttributeMapping ) {
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) modelPart;
@ -2426,10 +2426,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
keyPart = modelPart;
resultPart = modelPart;
}
final NavigablePath navigablePath;
if ( resultPart == modelPart ) {
navigablePath = tableGroup.getNavigablePath();
}
else {
navigablePath = tableGroup.getNavigablePath().append( resultPart.getPartName() );
}
final Expression result;
if ( resultPart instanceof EntityValuedModelPart ) {
final EntityValuedModelPart mapping = (EntityValuedModelPart) tableGroup.getModelPart();
final EntityValuedModelPart mapping = (EntityValuedModelPart) resultPart;
final boolean expandToAllColumns;
if ( currentClauseStack.getCurrent() == Clause.GROUP ) {
// When the table group is known to be fetched i.e. a fetch join
@ -2440,9 +2447,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else {
expandToAllColumns = false;
}
final TableGroup parentGroupToUse = findTableGroup( navigablePath.getParent() );
result = EntityValuedPathInterpretation.from(
tableGroup.getNavigablePath(),
tableGroup,
navigablePath,
parentGroupToUse == null ? tableGroup : parentGroupToUse,
mapping,
expandToAllColumns,
this
@ -2457,7 +2465,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
this,
getSqlAstCreationState()
),
tableGroup.getNavigablePath(),
navigablePath,
(EmbeddableValuedModelPart) resultPart,
tableGroup
);
@ -2466,7 +2474,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
assert resultPart instanceof BasicValuedModelPart;
final BasicValuedModelPart mapping = (BasicValuedModelPart) keyPart;
final TableReference tableReference = tableGroup.resolveTableReference(
tableGroup.getNavigablePath().append( keyPart.getPartName() ),
navigablePath.append( keyPart.getPartName() ),
mapping.getContainingTableExpression()
);
@ -2495,7 +2503,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
result = new BasicValuedPathInterpretation<>(
columnReference,
tableGroup.getNavigablePath(),
navigablePath,
(BasicValuedModelPart) resultPart,
tableGroup
);
@ -5199,7 +5207,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final List<String> bagRoles = new ArrayList<>();
final BiConsumer<Fetchable, Boolean> fetchableBiConsumer = (fetchable, isKeyFetchable) -> {
final NavigablePath fetchablePath = fetchParent.resolveNavigablePath( fetchable );
final NavigablePath resolvedNavigablePath = fetchParent.resolveNavigablePath( fetchable );
final String alias;
FetchTiming fetchTiming = fetchable.getMappedFetchOptions().getTiming();
@ -5207,16 +5215,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
EntityGraphTraversalState.TraversalResult traversalResult = null;
final FromClauseIndex fromClauseIndex = getFromClauseIndex();
final SqmAttributeJoin<?, ?> fetchedJoin = fromClauseIndex.findFetchedJoinByPath( fetchablePath );
final SqmAttributeJoin<?, ?> fetchedJoin = fromClauseIndex.findFetchedJoinByPath( resolvedNavigablePath );
boolean explicitFetch = false;
final NavigablePath fetchablePath;
if ( fetchedJoin != null ) {
fetchablePath = fetchedJoin.getNavigablePath();
// there was an explicit fetch in the SQM
// there should be a TableGroupJoin registered for this `fetchablePath` already
// because it
assert fromClauseIndex.getTableGroup( fetchablePath ) != null;
assert fromClauseIndex.getTableGroup( fetchedJoin.getNavigablePath() ) != null;
//
if ( fetchedJoin.isFetched() ) {
fetchTiming = FetchTiming.IMMEDIATE;
}
@ -5225,6 +5233,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
explicitFetch = true;
}
else {
fetchablePath = resolvedNavigablePath;
// there was not an explicit fetch in the SQM
alias = null;

View File

@ -85,7 +85,7 @@ public class FromClauseIndex extends SimpleFromClauseAccessImpl {
}
public boolean isResolved(SqmFrom fromElement) {
return tableGroupMap.containsKey( fromElement.getNavigablePath().getIdentifierForTableGroup() )
return tableGroupMap.containsKey( fromElement.getNavigablePath() )
|| parent != null && ( (FromClauseIndex) parent ).isResolved( fromElement );
}

View File

@ -20,7 +20,9 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
@ -88,10 +90,12 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
entityMappingType.getJdbcTypeCount() + identifierMapping.getJdbcTypeCount()
+ ( discriminatorMapping == null ? 0 : 1 )
);
final TableGroup parentTableGroup = tableGroup;
final SelectableConsumer selectableConsumer = (selectionIndex, selectableMapping) -> {
final TableReference tableReference = tableGroup.resolveTableReference(
final TableReference tableReference = parentTableGroup.resolveTableReference(
navigablePath,
selectableMapping.getContainingTableExpression()
selectableMapping.getContainingTableExpression(),
false
);
expressions.add(
sqlExprResolver.resolveSqlExpression(
@ -119,7 +123,21 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
final ForeignKeyDescriptor fkDescriptor = associationMapping.getForeignKeyDescriptor();
final String lhsTable;
final ModelPart lhsPart;
if ( associationMapping.getSideNature() == ForeignKeyDescriptor.Nature.KEY ) {
boolean useKeyPart = associationMapping.getSideNature() == ForeignKeyDescriptor.Nature.KEY;
if ( mapping instanceof EntityCollectionPart ) {
// EntityCollectionPart always returns TARGET, but sometimes we still want to use the KEY (element) side.
// With a collection table, we can always use the TARGET for referring to the collection,
// but in case of a one-to-many without collection table, this would be problematic,
// as we can't use e.g. the primary key of the owner entity as substitution.
final TableGroup pluralTableGroup = sqlAstCreationState.getFromClauseAccess()
.findTableGroup( navigablePath.getParent() );
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) pluralTableGroup.getModelPart();
if ( pluralAttributeMapping.getSeparateCollectionTable() == null ) {
useKeyPart = true;
tableGroup = pluralTableGroup;
}
}
if ( useKeyPart ) {
lhsTable = fkDescriptor.getKeyTable();
lhsPart = fkDescriptor.getKeyPart();
}
@ -167,12 +185,13 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
else {
assert mapping instanceof EntityMappingType;
final TableGroup parentTableGroup = tableGroup;
final EntityMappingType entityMappingType = (EntityMappingType) mapping;
final EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping();
if ( identifierMapping instanceof BasicEntityIdentifierMapping ) {
final BasicEntityIdentifierMapping simpleIdMapping = (BasicEntityIdentifierMapping) identifierMapping;
final TableReference tableReference = tableGroup.resolveTableReference(
final TableReference tableReference = parentTableGroup.resolveTableReference(
navigablePath,
simpleIdMapping.getContainingTableExpression()
);
@ -191,7 +210,7 @@ public class EntityValuedPathInterpretation<T> extends AbstractSqmPathInterpreta
final List<Expression> expressions = new ArrayList<>();
identifierMapping.forEachSelectable(
(selectionIndex, selectableMapping) -> {
final TableReference tableReference = tableGroup.resolveTableReference(
final TableReference tableReference = parentTableGroup.resolveTableReference(
navigablePath, selectableMapping.getContainingTableExpression() );
expressions.add(

View File

@ -105,7 +105,7 @@ public abstract class AbstractSqmPath<T> extends AbstractSqmExpression<T> implem
reusablePaths = new HashMap<>();
}
final String relativeName = path.getNavigablePath().getLocalName();
final String relativeName = path.getNavigablePath().getUnaliasedLocalName();
final SqmPath<?> previous = reusablePaths.put( relativeName, path );
if ( previous != null && previous != path ) {

View File

@ -7,12 +7,20 @@
package org.hibernate.query.sqm.tree.domain;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.ListPersistentAttribute;
import org.hibernate.metamodel.model.domain.MapPersistentAttribute;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.PathException;
import org.hibernate.query.SemanticException;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
/**
* An SqmPath for plural attribute paths
@ -68,6 +76,61 @@ public class SqmPluralValuedSimplePath<E> extends AbstractSqmSimplePath<E> {
);
}
@Override
public SqmPath<?> resolveIndexedAccess(
SqmExpression<?> selector,
boolean isTerminal,
SqmCreationState creationState) {
final SqmPathRegistry pathRegistry = creationState.getCurrentProcessingState().getPathRegistry();
final String alias = selector.toHqlString();
final NavigablePath navigablePath = getNavigablePath().getParent().append(
getNavigablePath().getUnaliasedLocalName(),
alias
);
SqmFrom<?, ?> path = pathRegistry.findFromByPath( navigablePath );
if ( path == null ) {
final PluralPersistentAttribute<?, ?, E> referencedPathSource = getReferencedPathSource();
final SqmFrom<?, Object> parent = pathRegistry.resolveFrom( getLhs() );
final SqmQualifiedJoin<Object, ?> join;
final SqmExpression<?> index;
if ( referencedPathSource instanceof ListPersistentAttribute<?, ?> ) {
//noinspection unchecked
join = new SqmListJoin<>(
parent,
(ListPersistentAttribute<Object, ?>) referencedPathSource,
alias,
SqmJoinType.INNER,
false,
parent.nodeBuilder()
);
index = ( (SqmListJoin<?, ?>) join ).index();
}
else if ( referencedPathSource instanceof MapPersistentAttribute<?, ?, ?> ) {
//noinspection unchecked
join = new SqmMapJoin<>(
parent,
(MapPersistentAttribute<Object, ?, ?>) referencedPathSource,
alias,
SqmJoinType.INNER,
false,
parent.nodeBuilder()
);
index = ( (SqmMapJoin<?, ?, ?>) join ).key();
}
else {
throw new SemanticException( "Index access is only supported on list or map attributes: " + getNavigablePath() );
}
join.setJoinPredicate( creationState.getCreationContext().getNodeBuilder().equal( index, selector ) );
parent.addSqmJoin( join );
pathRegistry.register( path = join );
}
return new SqmIndexedCollectionAccessPath<>(
path.getNavigablePath(),
path,
selector
);
}
@Override
public <S extends E> SqmTreatedSimplePath<E,S> treatAs(Class<S> treatJavaType) throws PathException {
return (SqmTreatedSimplePath<E, S>) treatAs( nodeBuilder().getDomainModel().entity( treatJavaType ) );

View File

@ -21,7 +21,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
public class SimpleFromClauseAccessImpl implements FromClauseAccess {
protected final FromClauseAccess parent;
protected final Map<String, TableGroup> tableGroupMap = new HashMap<>();
protected final Map<NavigablePath, TableGroup> tableGroupMap = new HashMap<>();
public SimpleFromClauseAccessImpl() {
this( null );
@ -33,7 +33,7 @@ public class SimpleFromClauseAccessImpl implements FromClauseAccess {
@Override
public TableGroup findTableGroup(NavigablePath navigablePath) {
final TableGroup tableGroup = tableGroupMap.get( navigablePath.getIdentifierForTableGroup() );
final TableGroup tableGroup = tableGroupMap.get( navigablePath );
if ( tableGroup == null && parent != null ) {
return parent.findTableGroup( navigablePath );
}
@ -48,7 +48,7 @@ public class SimpleFromClauseAccessImpl implements FromClauseAccess {
tableGroup.getNavigablePath().getIdentifierForTableGroup(),
navigablePath.getIdentifierForTableGroup()
);
final TableGroup previous = tableGroupMap.put( navigablePath.getIdentifierForTableGroup(), tableGroup );
final TableGroup previous = tableGroupMap.put( navigablePath, tableGroup );
if ( previous != null ) {
SqlTreeCreationLogger.LOGGER.debugf(
"Registration of TableGroup [%s] for NavigablePath [%s] overrode previous registration : %s",

View File

@ -22,16 +22,19 @@ public class CompositeTableGroup implements VirtualTableGroup {
private final EmbeddableValuedModelPart compositionMapping;
private final TableGroup underlyingTableGroup;
private final boolean fetched;
private List<TableGroupJoin> tableGroupJoins;
public CompositeTableGroup(
NavigablePath navigablePath,
EmbeddableValuedModelPart compositionMapping,
TableGroup underlyingTableGroup) {
TableGroup underlyingTableGroup,
boolean fetched) {
this.navigablePath = navigablePath;
this.compositionMapping = compositionMapping;
this.underlyingTableGroup = underlyingTableGroup;
this.fetched = fetched;
}
@Override
@ -50,6 +53,23 @@ public class CompositeTableGroup implements VirtualTableGroup {
return null;
}
@Override
public boolean isFetched() {
// if ( fetched ) {
// return true;
// }
// // We also consider it "fetched" if it contains fetched joins
// if ( tableGroupJoins != null ) {
// for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) {
// if ( tableGroupJoin.getJoinedGroup().isFetched() ) {
// return true;
// }
// }
// }
// return false;
return fetched;
}
@Override
public EmbeddableValuedModelPart getModelPart() {
return compositionMapping;

View File

@ -24,6 +24,7 @@ public class LazyTableGroup extends AbstractColumnReferenceQualifier implements
private final boolean canUseInnerJoins;
private final NavigablePath navigablePath;
private final boolean fetched;
private final TableGroupProducer producer;
private final String sourceAlias;
private final SqlAliasBase sqlAliasBase;
@ -37,6 +38,7 @@ public class LazyTableGroup extends AbstractColumnReferenceQualifier implements
public LazyTableGroup(
boolean canUseInnerJoins,
NavigablePath navigablePath,
boolean fetched,
Supplier<TableGroup> tableGroupSupplier,
BiPredicate<NavigablePath, String> navigablePathChecker,
TableGroupProducer tableGroupProducer,
@ -46,6 +48,7 @@ public class LazyTableGroup extends AbstractColumnReferenceQualifier implements
TableGroup parentTableGroup) {
this.canUseInnerJoins = canUseInnerJoins;
this.navigablePath = navigablePath;
this.fetched = fetched;
this.producer = tableGroupProducer;
this.sourceAlias = sourceAlias;
this.sqlAliasBase = sqlAliasBase;
@ -155,6 +158,11 @@ public class LazyTableGroup extends AbstractColumnReferenceQualifier implements
return false;
}
@Override
public boolean isFetched() {
return fetched;
}
@Override
public TableReference getTableReferenceInternal(
NavigablePath navigablePath,

View File

@ -14,6 +14,7 @@ import org.hibernate.query.results.ResultsHelper;
import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupProducer;
import org.hibernate.sql.results.graph.AbstractFetchParent;
import org.hibernate.sql.results.graph.AssemblerCreationState;
import org.hibernate.sql.results.graph.DomainResult;
@ -35,6 +36,7 @@ public class EmbeddableFetchImpl extends AbstractFetchParent implements Embeddab
private final FetchParent fetchParent;
private final FetchTiming fetchTiming;
private final TableGroup tableGroup;
private final boolean hasTableGroup;
private final boolean nullable;
@ -54,7 +56,7 @@ public class EmbeddableFetchImpl extends AbstractFetchParent implements Embeddab
this.hasTableGroup = hasTableGroup;
this.nullable = nullable;
creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup(
this.tableGroup = creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup(
getNavigablePath(),
np -> {
final TableGroup lhsTableGroup = creationState.getSqlAstCreationState()
@ -106,6 +108,19 @@ public class EmbeddableFetchImpl extends AbstractFetchParent implements Embeddab
return getReferencedMappingContainer();
}
@Override
public NavigablePath resolveNavigablePath(Fetchable fetchable) {
if ( fetchable instanceof TableGroupProducer ) {
for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) {
if ( tableGroupJoin.getJoinedGroup().isFetched() && tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) {
return tableGroupJoin.getNavigablePath();
}
}
}
return super.resolveNavigablePath( fetchable );
}
@Override
public DomainResult<?> asResult(DomainResultCreationState creationState) {
return embeddedPartDescriptor.createDomainResult(

View File

@ -61,8 +61,6 @@ public class EntityResultImpl extends AbstractEntityResultGraphNode implements E
@Override
public NavigablePath resolveNavigablePath(Fetchable fetchable) {
// todo: this is not ideal yet as we could potentially resolve a path that we did not intend
// to fix this, we'd need to know if the table group is for a fetch
if ( fetchable instanceof TableGroupProducer &&
!getNavigablePath().getUnaliasedLocalName().equals( getNavigablePath().getLocalName() ) ) {
for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) {

View File

@ -17,7 +17,7 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
* @author Archie Cobbs
* @author Nathan Xu
*/
@RequiresDialect( MySQLDialect.class )
@RequiresDialect( value = MySQLDialect.class, strictMatching = true )
public class HHH13908Test extends BaseEntityManagerFunctionalTestCase {
@Override

View File

@ -533,8 +533,7 @@ public class OneToManySizeTest2 {
"select distinct student from Student student join student.teacher t join fetch student.teacher tfetch join fetch tfetch.students where size(t.students) > -1",
Student.class
).getResultList();
// Since the join for "student.teacher" is never used and is a non-optional association we don't generate a SQL join for it
assertEquals( 2, countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ) );
assertEquals( 3, countNumberOfJoins( statementInspector.getSqlQueries().get( 0 ) ) );
assertEquals( 3L, students.size() );
assertTrue( Hibernate.isInitialized( students.get( 0 ).getTeacher().getStudents() ) );
assertTrue( Hibernate.isInitialized( students.get( 1 ).getTeacher().getStudents() ) );