HHH-16658 Propagate entity name uses from predicates and subqueries properly to the upper context

This commit is contained in:
Christian Beikov 2023-05-23 17:02:27 +02:00
parent cf09a8aa99
commit 33d601f146
16 changed files with 917 additions and 416 deletions

View File

@ -22,8 +22,8 @@ import org.hibernate.graph.spi.AppliedGraph;
import org.hibernate.metamodel.mapping.AssociationKey;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.spi.Limit;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.spi.NavigablePath;
import org.hibernate.query.ResultListTransformer;
import org.hibernate.query.TupleTransformer;
@ -38,10 +38,8 @@ import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlAstQueryPartProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.internal.ImmutableFetchList;
@ -108,17 +106,17 @@ public class LoaderSqlAstCreationState
}
@Override
public void registerTreat(TableGroup tableGroup, EntityDomainType<?> treatType) {
public void registerTreatedFrom(SqmFrom<?, ?> sqmFrom) {
throw new UnsupportedOperationException();
}
@Override
public void registerTreatUsage(TableGroup tableGroup, EntityDomainType<?> treatType) {
public void registerFromUsage(SqmFrom<?, ?> sqmFrom, boolean downgradeTreatUses) {
throw new UnsupportedOperationException();
}
@Override
public Map<TableGroup, Map<EntityDomainType<?>, Boolean>> getTreatRegistrations() {
public Map<SqmFrom<?, ?>, Boolean> getFromRegistrations() {
return Collections.emptyMap();
}

View File

@ -175,6 +175,11 @@ public interface ForeignKeyDescriptor extends VirtualModelPart, ValuedModelPart
IntFunction<SelectableMapping> selectableMappingAccess,
MappingModelCreationProcess creationProcess);
/**
* Return a copy of this foreign key descriptor with the target part as given by the argument.
*/
ForeignKeyDescriptor withTargetPart(ValuedModelPart targetPart);
AssociationKey getAssociationKey();
boolean hasConstraint();

View File

@ -156,6 +156,20 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
this.hasConstraint = original.hasConstraint;
}
private EmbeddedForeignKeyDescriptor(EmbeddedForeignKeyDescriptor original, EmbeddableValuedModelPart targetPart) {
this.keyTable = original.keyTable;
this.keySelectableMappings = original.keySelectableMappings;
this.keySide = original.keySide;
this.targetTable = targetPart.getContainingTableExpression();
this.targetSelectableMappings = targetPart;
this.targetSide = new EmbeddedForeignKeyDescriptorSide(
Nature.TARGET,
targetPart
);
this.associationKey = original.associationKey;
this.hasConstraint = original.hasConstraint;
}
@Override
public String getKeyTable() {
return keyTable;
@ -232,6 +246,11 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
);
}
@Override
public ForeignKeyDescriptor withTargetPart(ValuedModelPart targetPart) {
return new EmbeddedForeignKeyDescriptor( this, (EmbeddableValuedModelPart) targetPart );
}
@Override
public DomainResult<?> createKeyDomainResult(
NavigablePath navigablePath,

View File

@ -59,6 +59,7 @@ import org.hibernate.metamodel.mapping.CompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
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;
@ -1244,7 +1245,19 @@ public class MappingModelCreationHelper {
dialect,
creationProcess
);
attributeMapping.setForeignKeyDescriptor( referencedAttributeMapping.getForeignKeyDescriptor() );
foreignKeyDescriptor = referencedAttributeMapping.getForeignKeyDescriptor();
}
final EntityMappingType declaringEntityMapping = attributeMapping.findContainingEntityMapping();
if ( foreignKeyDescriptor.getTargetPart() instanceof EntityIdentifierMapping
&& foreignKeyDescriptor.getTargetPart() != declaringEntityMapping.getIdentifierMapping() ) {
// If the many-to-one refers to the super type, but the one-to-many is defined in a subtype,
// it would be wasteful to reuse the FK descriptor of the many-to-one,
// because that refers to the PK column in the root table.
// Joining such an association then requires that we join the root table
attributeMapping.setForeignKeyDescriptor(
foreignKeyDescriptor.withTargetPart( declaringEntityMapping.getIdentifierMapping() )
);
}
else {
attributeMapping.setForeignKeyDescriptor( foreignKeyDescriptor );

View File

@ -223,6 +223,17 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
);
}
@Override
public ForeignKeyDescriptor withTargetPart(ValuedModelPart targetPart) {
return new SimpleForeignKeyDescriptor(
keySide.getModelPart(),
(BasicValuedModelPart) targetPart,
refersToPrimaryKey,
hasConstraint,
false
);
}
@Override
public DomainResult<?> createKeyDomainResult(
NavigablePath navigablePath,

View File

@ -17,13 +17,14 @@ public final class EntityNameUse {
public static final EntityNameUse PROJECTION = new EntityNameUse( UseKind.PROJECTION, true );
public static final EntityNameUse EXPRESSION = new EntityNameUse( UseKind.EXPRESSION, true );
public static final EntityNameUse TREAT = new EntityNameUse( UseKind.TREAT, true );
public static final EntityNameUse BASE_TREAT = new EntityNameUse( UseKind.TREAT, null );
public static final EntityNameUse OPTIONAL_TREAT = new EntityNameUse( UseKind.TREAT, false );
public static final EntityNameUse FILTER = new EntityNameUse( UseKind.FILTER, true );
private final UseKind kind;
private final boolean requiresRestriction;
private final Boolean requiresRestriction;
private EntityNameUse(UseKind kind, boolean requiresRestriction) {
private EntityNameUse(UseKind kind, Boolean requiresRestriction) {
this.kind = kind;
this.requiresRestriction = requiresRestriction;
}
@ -47,15 +48,27 @@ public final class EntityNameUse {
}
public boolean requiresRestriction() {
return requiresRestriction;
return requiresRestriction != Boolean.FALSE;
}
public EntityNameUse stronger(EntityNameUse other) {
return other == null || kind.isStrongerThan( other.kind ) ? this : get( other.kind );
if ( other == null || kind.isStrongerThan( other.kind ) ) {
return this;
}
if ( kind == other.kind && kind == UseKind.TREAT ) {
return requiresRestriction == null ? other : this;
}
return other.kind.isStrongerThan( kind ) ? other : get( other.kind );
}
public EntityNameUse weaker(EntityNameUse other) {
return other == null || kind.isWeakerThan( other.kind ) ? this : get( other.kind );
if ( other == null || kind.isWeakerThan( other.kind ) ) {
return this;
}
if ( kind == other.kind && kind == UseKind.TREAT ) {
return requiresRestriction == null ? other : this;
}
return other.kind.isWeakerThan( kind ) ? other : get( other.kind );
}
public enum UseKind {

View File

@ -616,14 +616,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
}
private void collectSelectableOwners(LinkedHashMap<String, Map<String, SelectableMapping>> selectables) {
if ( isAbstract() ) {
for ( EntityMappingType subMappingType : getSubMappingTypes() ) {
if ( !subMappingType.isAbstract() ) {
( (UnionSubclassEntityPersister) subMappingType ).collectSelectableOwners( selectables );
}
}
}
else {
if ( !isAbstract() ) {
final SelectableConsumer selectableConsumer = (i, selectable) -> {
Map<String, SelectableMapping> selectableMapping = selectables.computeIfAbsent(
selectable.getSelectionExpression(),
@ -647,11 +640,6 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
for ( int i = 0; i < size; i++ ) {
attributeMappings.get( i ).forEachSelectable( selectableConsumer );
}
for ( EntityMappingType subMappingType : getSubMappingTypes() ) {
if ( !subMappingType.isAbstract() ) {
( (UnionSubclassEntityPersister) subMappingType ).collectSelectableOwners( selectables );
}
}
}
}

View File

@ -65,6 +65,9 @@ import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.Bindable;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.DiscriminatorConverter;
import org.hibernate.metamodel.mapping.DiscriminatorMapping;
import org.hibernate.metamodel.mapping.DiscriminatorValueDetails;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
@ -106,6 +109,7 @@ import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EntityDiscriminatorSqmPath;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.metamodel.model.domain.internal.EntityTypeImpl;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityNameUse;
import org.hibernate.persister.entity.EntityPersister;
@ -496,6 +500,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private final Stack<List> queryTransformers = new StandardStack<>( List.class );
private boolean inTypeInference;
private boolean inImpliedResultTypeInference;
private boolean inNestedContext;
private Supplier<MappingModelExpressible<?>> functionImpliedResultTypeAccess;
private SqmByUnit appliedByUnit;
@ -1971,6 +1976,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final Predicate originalAdditionalRestrictions = additionalRestrictions;
additionalRestrictions = null;
final boolean oldInNestedContext = inNestedContext;
inNestedContext = false;
final boolean trackAliasedNodePositions;
if ( trackSelectionsForGroup ) {
@ -2054,24 +2061,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
applyCollectionFilterPredicates( sqlQuerySpec );
}
// Look for treat registrations that have never been used in the query.
// These treats become "global" i.e. we need to apply filtering for all subtypes.
// This tracking is necessary to differentiate between the HQLs
// - from Root r join treat(r.attribute as Subtype) a where a.id = 1 or 1=1
// - from Root r join r.attribute a where treat(a as Subtype).id = 1 or 1=1
for ( Map.Entry<TableGroup, Map<EntityDomainType<?>, Boolean>> entry : processingState.getTreatRegistrations().entrySet() ) {
final TableGroup actualTableGroup = entry.getKey();
final Map<EntityDomainType<?>, Boolean> treatUses = entry.getValue();
for ( Map.Entry<EntityDomainType<?>, Boolean> treatUseEntry : treatUses.entrySet() ) {
final EntityDomainType<?> treatTargetType = treatUseEntry.getKey();
if ( !treatUseEntry.getValue() ) {
// The treat registration was not used in the query
registerEntityNameUsage(
actualTableGroup,
EntityNameUse.TREAT,
treatTargetType.getHibernateEntityName()
);
}
// Look for treated SqmFrom registrations that have uses of the untreated SqmFrom.
// These SqmFrom nodes are then not treat-joined but rather treated only in expressions
// Consider the following two queries. The latter also uses the untreated SqmFrom
// and hence has different semantics i.e. the treat is not filtering, but just applies where it is used
// - select a.id from Root r join treat(r.attribute as Subtype) a where a.id = 1
// - select a.id from Root r join r.attribute a where treat(a as Subtype).id = 1
for ( Map.Entry<SqmFrom<?, ?>, Boolean> entry : processingState.getFromRegistrations().entrySet() ) {
if ( entry.getValue() == Boolean.TRUE ) {
downgradeTreatUses( getFromClauseIndex().getTableGroup( entry.getKey().getNavigablePath() ) );
}
}
@ -2090,6 +2088,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqlQuerySpec.applyPredicate( additionalRestrictions );
}
additionalRestrictions = originalAdditionalRestrictions;
inNestedContext = oldInNestedContext;
popProcessingStateStack();
queryTransformers.pop();
currentSqmQueryPart = sqmQueryPart;
@ -2097,6 +2096,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
}
private void downgradeTreatUses(TableGroup tableGroup) {
final Map<String, EntityNameUse> entityNameUses = tableGroupEntityNameUses.get( tableGroup );
if ( entityNameUses != null ) {
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
if ( entry.getValue().getKind() == EntityNameUse.UseKind.TREAT ) {
entry.setValue( EntityNameUse.EXPRESSION );
}
}
}
}
protected void visitOrderByOffsetAndFetch(SqmQueryPart<?> sqmQueryPart, QueryPart sqlQueryPart) {
if ( sqmQueryPart.getOrderByClause() != null ) {
currentClauseStack.push( Clause.ORDER );
@ -2865,14 +2875,27 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final EntityDomainType<?> treatedType;
if ( projectedPath instanceof SqmTreatedPath<?, ?> ) {
treatedType = ( (SqmTreatedPath<?, ?>) projectedPath ).getTreatTarget();
registerEntityNameUsage( tableGroup, EntityNameUse.TREAT, treatedType.getHibernateEntityName() );
registerEntityNameUsage( tableGroup, EntityNameUse.TREAT, treatedType.getHibernateEntityName(), true );
// Register that this treat was used somewhere
((SqlAstQueryPartProcessingState) getCurrentProcessingState()).registerTreatUsage( tableGroup, treatedType );
if ( projectedPath instanceof SqmFrom<?, ?> ) {
// Register that the TREAT uses for the SqmFrom node may not be downgraded
( (SqlAstQueryPartProcessingState) getCurrentProcessingState() ).registerFromUsage(
(SqmFrom<?, ?>) ( (SqmTreatedPath<?, ?>) projectedPath ).getWrappedPath(),
false
);
}
}
else if ( projectedPath.getNodeType().getSqmPathType() instanceof EntityDomainType<?> ) {
treatedType = (EntityDomainType<?>) projectedPath.getNodeType().getSqmPathType();
registerEntityNameUsage( tableGroup, EntityNameUse.PROJECTION, treatedType.getHibernateEntityName() );
registerEntityNameUsage( tableGroup, EntityNameUse.PROJECTION, treatedType.getHibernateEntityName(), true );
if ( projectedPath instanceof SqmFrom<?, ?> ) {
// Register that the TREAT uses for the SqmFrom node may not be downgraded
( (SqlAstQueryPartProcessingState) getCurrentProcessingState() ).registerFromUsage(
(SqmFrom<?, ?>) projectedPath,
true
);
}
}
}
@ -2883,59 +2906,76 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
* it will instead register a {@link EntityNameUse#TREAT} for the treated type.
*/
private void registerPathAttributeEntityNameUsage(SqmPath<?> sqmPath, TableGroup tableGroup) {
final SqmPath<?> parentPath = sqmPath.getLhs();
final SqlAstProcessingState processingState = getCurrentProcessingState();
if ( processingState instanceof SqlAstQueryPartProcessingState ) {
if ( parentPath instanceof SqmFrom<?, ?> ) {
( (SqlAstQueryPartProcessingState) processingState ).registerFromUsage(
(SqmFrom<?, ?>) parentPath,
true
);
}
if ( sqmPath instanceof SqmFrom<?, ?> ) {
( (SqlAstQueryPartProcessingState) processingState ).registerFromUsage(
(SqmFrom<?, ?>) sqmPath,
true
);
}
}
final SqmPathSource<?> resolvedModel;
if ( !( sqmPath instanceof SqmTreatedPath<?, ?> )
&& tableGroup.getModelPart().getPartMappingType() instanceof EntityMappingType
&& ( resolvedModel = sqmPath.getResolvedModel() ) instanceof PersistentAttribute<?, ?> ) {
final SqmPath<?> parentPath = sqmPath.getLhs();
final String attributeName = resolvedModel.getPathName();
final EntityMappingType entityType = (EntityMappingType) tableGroup.getModelPart().getPartMappingType();
final EntityMappingType parentType;
final EntityNameUse entityNameUse;
final String treatedEntityName;
if ( parentPath instanceof SqmTreatedPath<?, ?> ) {
// A treated attribute usage i.e. `treat(alias as Subtype).attribute = 1`
final EntityDomainType<?> treatTarget = ( (SqmTreatedPath<?, ?>) parentPath ).getTreatTarget();
final AbstractEntityPersister persister = (AbstractEntityPersister) creationContext.getMappingMetamodel()
.getEntityDescriptor( treatTarget.getHibernateEntityName() );
( (SqlAstQueryPartProcessingState) getCurrentProcessingState() ).registerTreatUsage(
tableGroup,
treatTarget
);
treatedEntityName = treatTarget.getHibernateEntityName();
parentType = persister;
parentType = creationContext.getMappingMetamodel()
.getEntityDescriptor( treatTarget.getHibernateEntityName() );
// The following is an optimization to avoid rendering treat conditions into predicates.
// Imagine an HQL predicate like `treat(alias as Subtype).attribute is null or alias.name = '...'`.
// If the column for `attribute` is not "shared", meaning that the column is valid only for one subtype,
// then we can safely skip adding the `type(alias) = Subtype and ...` condition to the SQL.
// If the `attribute` is basic, we will render a case wrapper around the column expression
// and hence we can safely skip adding the `type(alias) = Subtype and ...` condition to the SQL.
final ModelPart subPart = parentType.findSubPart( attributeName );
final EntityNameUse entityNameUse;
// We only apply this optimization for basic valued model parts for now
if ( subPart instanceof BasicValuedModelPart
&& !persister.isSharedColumn( ( (BasicValuedModelPart) subPart ).getSelectionExpression() ) ) {
if ( subPart instanceof BasicValuedModelPart ) {
entityNameUse = EntityNameUse.OPTIONAL_TREAT;
}
else {
entityNameUse = EntityNameUse.TREAT;
}
registerEntityNameUsage(
tableGroup,
entityNameUse,
treatTarget.getHibernateEntityName()
);
}
else {
// A simple attribute usage e.g. `alias.attribute = 1`
treatedEntityName = null;
parentType = entityType;
entityNameUse = EntityNameUse.EXPRESSION;
}
final AttributeMapping attributeMapping = parentType.findAttributeMapping( attributeName );
if ( attributeMapping == null ) {
if ( attributeName.equals( parentType.getIdentifierMapping().getPartName() ) ) {
// Until HHH-16571 is fixed, we must register an entity name use for the root entity descriptor name
if ( attributeName.equals( parentType.getIdentifierMapping().getAttributeName() ) ) {
if ( parentType.getIdentifierMapping() instanceof EmbeddableValuedModelPart ) {
// Until HHH-16571 is fixed, we must also register an entity name use for the root entity descriptor name
registerEntityNameUsage(
tableGroup,
EntityNameUse.EXPRESSION,
parentType.getRootEntityDescriptor().getEntityName()
);
}
registerEntityNameUsage(
tableGroup,
entityNameUse,
parentType.getRootEntityDescriptor().getEntityName()
EntityNameUse.EXPRESSION,
parentType.getEntityName()
);
}
else {
@ -2944,7 +2984,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// Register entity name usages for all subtypes that declare the attribute with the same name then
for ( EntityMappingType subMappingType : parentType.getSubMappingTypes() ) {
if ( subMappingType.findDeclaredAttributeMapping( attributeName ) != null ) {
registerEntityNameUsage( tableGroup, entityNameUse, subMappingType.getEntityName() );
registerEntityNameUsage( tableGroup, EntityNameUse.EXPRESSION, subMappingType.getEntityName() );
}
}
}
@ -2952,10 +2992,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else {
registerEntityNameUsage(
tableGroup,
entityNameUse,
treatedEntityName == null
? attributeMapping.findContainingEntityMapping().getEntityName()
: treatedEntityName
EntityNameUse.EXPRESSION,
attributeMapping.findContainingEntityMapping().getEntityName()
);
}
}
@ -2966,6 +3004,14 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
TableGroup tableGroup,
EntityNameUse entityNameUse,
String hibernateEntityName) {
registerEntityNameUsage( tableGroup, entityNameUse, hibernateEntityName, false );
}
private void registerEntityNameUsage(
TableGroup tableGroup,
EntityNameUse entityNameUse,
String hibernateEntityName,
boolean projection) {
final AbstractEntityPersister persister = (AbstractEntityPersister) creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel()
@ -2975,19 +3021,24 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
final TableGroup actualTableGroup;
final EntityNameUse finalEntityNameUse;
if ( tableGroup instanceof PluralTableGroup ) {
actualTableGroup = ( (PluralTableGroup) tableGroup ).getElementTableGroup();
finalEntityNameUse = entityNameUse;
}
else if ( tableGroup instanceof CorrelatedTableGroup ) {
if ( tableGroup instanceof CorrelatedTableGroup ) {
actualTableGroup = ( (CorrelatedTableGroup) tableGroup ).getCorrelatedTableGroup();
// For correlated table groups we can't apply filters,
// as the context is in which the use happens may only affect the result of the subquery
finalEntityNameUse = entityNameUse == EntityNameUse.EXPRESSION ? entityNameUse : EntityNameUse.PROJECTION;
}
else {
actualTableGroup = tableGroup;
finalEntityNameUse = entityNameUse;
if ( tableGroup instanceof PluralTableGroup ) {
actualTableGroup = ( (PluralTableGroup) tableGroup ).getElementTableGroup();
}
else {
actualTableGroup = tableGroup;
}
finalEntityNameUse = entityNameUse == EntityNameUse.EXPRESSION
|| entityNameUse == EntityNameUse.PROJECTION
|| contextAllowsTreatOrFilterEntityNameUse()
? entityNameUse
: EntityNameUse.EXPRESSION;
}
final Map<String, EntityNameUse> entityNameUses = tableGroupEntityNameUses.computeIfAbsent(
actualTableGroup,
@ -2999,11 +3050,12 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
// Resolve the table reference for all types which we register an entity name use for
actualTableGroup.resolveTableReference( null, persister.getTableName() );
if ( actualTableGroup.isInitialized() ) {
actualTableGroup.resolveTableReference( null, persister.getTableName() );
}
if ( finalEntityNameUse == EntityNameUse.PROJECTION ) {
// For projections also register uses of all super and subtypes,
// as well as resolve the respective table references
final EntityNameUse.UseKind useKind = finalEntityNameUse.getKind();
if ( projection ) {
EntityMappingType superMappingType = persister;
while ( ( superMappingType = superMappingType.getSuperMappingType() ) != null ) {
entityNameUses.putIfAbsent( superMappingType.getEntityName(), EntityNameUse.PROJECTION );
@ -3012,22 +3064,14 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
( (AbstractEntityPersister) superMappingType.getEntityPersister() ).getTableName()
);
}
for ( String subclassEntityName : persister.getSubclassEntityNames() ) {
entityNameUses.putIfAbsent( subclassEntityName, EntityNameUse.PROJECTION );
}
final int subclassTableSpan = persister.getSubclassTableSpan();
for ( int i = 0; i < subclassTableSpan; i++ ) {
actualTableGroup.resolveTableReference( null, persister.getSubclassTableName( i ) );
}
}
else if ( finalEntityNameUse == EntityNameUse.TREAT ) {
if ( useKind == EntityNameUse.UseKind.TREAT || useKind == EntityNameUse.UseKind.PROJECTION ) {
// If we encounter a treat use, we also want register the use for all subtypes.
// We do this here to not have to expand entity name uses during pruning later on
for ( EntityMappingType subType : persister.getSubMappingTypes() ) {
entityNameUses.compute(
subType.getEntityName(),
(s, existingUse) -> entityNameUse.stronger( existingUse )
(s, existingUse) -> finalEntityNameUse.stronger( existingUse )
);
actualTableGroup.resolveTableReference(
null,
@ -3037,6 +3081,21 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
}
private boolean contextAllowsTreatOrFilterEntityNameUse() {
final Clause currentClause = getCurrentClauseStack().getCurrent();
switch ( currentClause ) {
case SET:
case FROM:
case GROUP:
case HAVING:
case WHERE:
// A TREAT or FILTER EntityNameUse is only allowed in these clauses,
// but only if it's not in a nested context
return !inNestedContext;
}
return false;
}
protected void registerTypeUsage(EntityDiscriminatorSqmPath path) {
registerTypeUsage( getFromClauseAccess().getTableGroup( path.getNavigablePath().getParent() ) );
}
@ -3100,10 +3159,18 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final List<SqmFrom<?, ?>> sqmTreats = sqmFrom.getSqmTreats();
if ( !sqmTreats.isEmpty() ) {
final SqlAstQueryPartProcessingState queryPartProcessingState = (SqlAstQueryPartProcessingState) getCurrentProcessingState();
queryPartProcessingState.registerTreatedFrom( sqmFrom );
// If a SqmFrom is used anywhere even though treats exists,
// the treats are context dependent and hence we need to downgrade TREAT entity uses to EXPRESSION.
// Treat expressions will be protected via predicates or case when expressions,
// but we may not filter rows based on the TREAT entity uses.
if ( lhsTableGroup.hasRealJoins() ) {//|| sqmFrom instanceof SqmRoot<?> ) {
queryPartProcessingState.registerFromUsage( sqmFrom, true );
}
for ( SqmFrom<?, ?> sqmTreat : sqmTreats ) {
final TableGroup actualTableGroup = getActualTableGroup( lhsTableGroup, sqmTreat );
// We don't know the context yet in which a treat is used, so we have to register them first and track the usage
queryPartProcessingState.registerTreat( actualTableGroup, ( (SqmTreatedPath<?, ?>) sqmTreat ).getTreatTarget() );
// We don't know the context yet in which a treat is used, so we have to register base treats and track the usage
registerEntityNameUsage( actualTableGroup, EntityNameUse.BASE_TREAT, ( (SqmTreatedPath<?, ?>) sqmTreat ).getTreatTarget().getHibernateEntityName() );
consumeExplicitJoins( sqmTreat, actualTableGroup );
}
}
@ -3266,6 +3333,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
joinedTableGroupJoin.applyPredicate( visitNestedTopLevelPredicate( sqmJoin.getJoinPredicate() ) );
currentlyProcessingJoin = oldJoin;
}
// Since joins on treated paths will never cause table pruning, we need to add a join condition for the treat
if ( sqmJoin.getLhs() instanceof SqmTreatedPath<?, ?> ) {
final SqmTreatedPath<?, ?> treatedPath = (SqmTreatedPath<?, ?>) sqmJoin.getLhs();
joinedTableGroupJoin.applyPredicate(
createTreatTypeRestriction(
treatedPath.getWrappedPath(),
treatedPath.getTreatTarget()
)
);
}
if ( transitive ) {
consumeExplicitJoins( sqmJoin, joinedTableGroup );
@ -3593,24 +3670,15 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
if ( !( path instanceof SqmEntityValuedSimplePath<?>
|| path instanceof SqmEmbeddedValuedSimplePath<?>
|| path instanceof SqmAnyValuedSimplePath<?> ) ) {
|| path instanceof SqmAnyValuedSimplePath<?>
|| path instanceof SqmTreatedPath<?, ?> ) ) {
// Since this is a selection, we must create a table group for the path as a DomainResult will be created
// But only create it for paths that are not handled by #prepareReusablePath anyway
final NavigablePath navigablePath;
if ( path instanceof SqmTreatedRoot<?, ?> ) {
navigablePath = ( (SqmTreatedRoot<?, ?>) path ).getWrappedPath().getNavigablePath();
}
else {
navigablePath = path.getLhs().getNavigablePath();
}
final TableGroup createdTableGroup = createTableGroup(
getActualTableGroup( fromClauseIndex.getTableGroup( navigablePath ), path ),
getActualTableGroup( fromClauseIndex.getTableGroup( path.getLhs().getNavigablePath() ), path ),
path
);
if ( createdTableGroup != null ) {
if ( path instanceof SqmTreatedPath<?, ?> ) {
fromClauseIndex.register( path, createdTableGroup );
}
registerEntityNameProjectionUsage( path, createdTableGroup );
}
}
@ -4915,30 +4983,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// so we instead add the type restriction predicate as conjunct
// by registering the treat into tableGroupEntityNameUses
final String treatedName = treatedPath.getTreatTarget().getHibernateEntityName();
final EntityPersister entityDescriptor = domainModel.findEntityDescriptor( treatedName );
final TableGroup tableGroup = getFromClauseIndex().findTableGroup( wrappedPath.getNavigablePath() );
final TableGroup actualTableGroup;
if ( tableGroup instanceof PluralTableGroup ) {
final CollectionPart.Nature nature = CollectionPart.Nature.fromName( path.getNavigablePath().getLocalName() );
actualTableGroup = ( (PluralTableGroup) tableGroup ).getTableGroup(
nature == null
? CollectionPart.Nature.ELEMENT
: nature
);
}
else {
actualTableGroup = tableGroup;
}
final Map<String, EntityNameUse> entityNameUses = tableGroupEntityNameUses.computeIfAbsent(
actualTableGroup,
p -> new HashMap<>( 1 )
);
for ( String subclassEntityName : entityDescriptor.getSubclassEntityNames() ) {
entityNameUses.compute(
subclassEntityName,
(s, existingUse) -> EntityNameUse.TREAT.stronger( existingUse )
);
}
registerEntityNameUsage( tableGroup, EntityNameUse.TREAT, treatedName );
return expression;
}
final BasicValuedPathInterpretation<?> basicPath = (BasicValuedPathInterpretation<?>) expression;
@ -5041,7 +5087,14 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private Set<String> determineEntityNamesForTreatTypeRestriction(
EntityMappingType partMappingType,
Map<String, EntityNameUse> entityNameUses) {
final Set<String> entityNameUsesSet = entityNameUses.keySet();
final Set<String> entityNameUsesSet = new HashSet<>( entityNameUses.size() );
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
if ( entry.getValue() == EntityNameUse.PROJECTION ) {
continue;
}
entityNameUsesSet.add( entry.getKey() );
}
if ( entityNameUsesSet.containsAll( partMappingType.getSubclassEntityNames() ) ) {
// No need to create a restriction if all subclasses are used
return Collections.emptySet();
@ -5051,7 +5104,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return Collections.emptySet();
}
final String baseEntityNameToAdd;
if ( entityNameUses.containsKey( partMappingType.getEntityName() ) ) {
if ( entityNameUsesSet.contains( partMappingType.getEntityName() ) ) {
if ( !partMappingType.isAbstract() ) {
baseEntityNameToAdd = partMappingType.getEntityName();
}
@ -5206,7 +5259,39 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final MappingModelExpressible<?> inferableExpressible = getInferredValueMapping();
if ( inferableExpressible instanceof BasicValuedMapping ) {
if ( inferableExpressible instanceof DiscriminatorMapping ) {
final MappingMetamodelImplementor mappingMetamodel = creationContext.getSessionFactory().getMappingMetamodel();
final Object literalValue = literal.getLiteralValue();
final EntityPersister entityDescriptor;
if ( literalValue instanceof Class<?> ) {
entityDescriptor = mappingMetamodel.findEntityDescriptor( (Class<?>) literalValue );
}
else {
final DiscriminatorMapping discriminatorMapping = (DiscriminatorMapping) inferableExpressible;
//noinspection unchecked
final DiscriminatorConverter<?, Object> valueConverter = (DiscriminatorConverter<?, Object>) discriminatorMapping.getValueConverter();
final DiscriminatorValueDetails discriminatorDetails;
if ( valueConverter.getDomainJavaType().isInstance( literalValue ) ) {
discriminatorDetails = valueConverter.getDetailsForDiscriminatorValue( literalValue );
}
else if ( valueConverter.getRelationalJavaType().isInstance( literalValue ) ) {
discriminatorDetails = valueConverter.getDetailsForRelationalForm( literalValue );
}
else {
// Special case when passing the discriminator value as e.g. string literal,
// but the expected relational type is Character.
// In this case, we use wrap to transform the value to the correct type
final Object relationalForm = valueConverter.getRelationalJavaType().wrap(
literalValue,
creationContext.getSessionFactory().getWrapperOptions()
);
discriminatorDetails = valueConverter.getDetailsForRelationalForm( relationalForm );
}
entityDescriptor = discriminatorDetails.getIndicatedEntity().getEntityPersister();
}
return new EntityTypeLiteral( entityDescriptor );
}
else if ( inferableExpressible instanceof BasicValuedMapping ) {
final BasicValuedMapping basicValuedMapping = (BasicValuedMapping) inferableExpressible;
final BasicValueConverter valueConverter = basicValuedMapping.getJdbcMapping().getValueConverter();
if ( valueConverter != null ) {
@ -5219,15 +5304,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else if ( valueConverter.getRelationalJavaType().isInstance( value ) ) {
sqlLiteralValue = value;
}
else if ( basicValuedMapping instanceof EntityDiscriminatorMapping ) {
// Special case when passing the discriminator value as e.g. string literal,
// but the expected relational type is Character.
// In this case, we use wrap to transform the value to the correct type
sqlLiteralValue = valueConverter.getRelationalJavaType().wrap(
value,
creationContext.getSessionFactory().getWrapperOptions()
);
}
// In HQL, number literals might not match the relational java type exactly,
// so we allow coercion between the number types
else if ( Number.class.isAssignableFrom( valueConverter.getRelationalJavaType().getJavaTypeClass() )
@ -5927,6 +6003,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public Expression visitFunction(SqmFunction<?> sqmFunction) {
final boolean oldInNestedContext = inNestedContext;
inNestedContext = true;
final Supplier<MappingModelExpressible<?>> oldFunctionImpliedResultTypeAccess = functionImpliedResultTypeAccess;
functionImpliedResultTypeAccess = inferrableTypeAccessStack.getCurrent();
inferrableTypeAccessStack.push( () -> null );
@ -5936,6 +6014,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
finally {
inferrableTypeAccessStack.pop();
functionImpliedResultTypeAccess = oldFunctionImpliedResultTypeAccess;
inNestedContext = oldInNestedContext;
}
}
@ -6577,6 +6656,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
public CaseSimpleExpression visitSimpleCaseExpression(SqmCaseSimple<?, ?> expression) {
final List<CaseSimpleExpression.WhenFragment> whenFragments = new ArrayList<>( expression.getWhenFragments().size() );
final Supplier<MappingModelExpressible<?>> inferenceSupplier = inferrableTypeAccessStack.getCurrent();
final boolean oldInNestedContext = inNestedContext;
inNestedContext = true;
inferrableTypeAccessStack.push(
() -> {
for ( SqmCaseSimple.WhenFragment<?, ?> whenFragment : expression.getWhenFragments() ) {
@ -6623,6 +6705,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
resolved = (MappingModelExpressible<?>) highestPrecedence( resolved, otherwise.getExpressionType() );
}
inNestedContext = oldInNestedContext;
return new CaseSimpleExpression(
resolved,
fixture,
@ -6635,6 +6718,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
public CaseSearchedExpression visitSearchedCaseExpression(SqmCaseSearched<?> expression) {
final List<CaseSearchedExpression.WhenFragment> whenFragments = new ArrayList<>( expression.getWhenFragments().size() );
final Supplier<MappingModelExpressible<?>> inferenceSupplier = inferrableTypeAccessStack.getCurrent();
final boolean oldInNestedContext = inNestedContext;
inNestedContext = true;
MappingModelExpressible<?> resolved = determineCurrentExpressible( expression );
Expression otherwise = null;
@ -6663,6 +6749,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
resolved = (MappingModelExpressible<?>) highestPrecedence( resolved, otherwise.getExpressionType() );
}
inNestedContext = oldInNestedContext;
return new CaseSearchedExpression( resolved, whenFragments, otherwise );
}
@ -6874,37 +6961,164 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
new ArrayList<>( predicate.getPredicates().size() ),
getBooleanType()
);
final Map<TableGroup, Map<String, EntityNameUse>> originalConjunctTableGroupTreatUsages;
final Map<TableGroup, Map<String, EntityNameUse>> previousTableGroupEntityNameUses;
if ( tableGroupEntityNameUses.isEmpty() ) {
originalConjunctTableGroupTreatUsages = null;
previousTableGroupEntityNameUses = null;
}
else {
originalConjunctTableGroupTreatUsages = new IdentityHashMap<>( tableGroupEntityNameUses );
previousTableGroupEntityNameUses = new IdentityHashMap<>( tableGroupEntityNameUses );
}
Map<TableGroup, Map<String, EntityNameUse>>[] conjunctTreatUsagesArray = null;
Map<TableGroup, Map<String, EntityNameUse>> conjunctTreatUsagesUnion = null;
Map<TableGroup, Map<String, EntityNameUse>>[] disjunctEntityNameUsesArray = null;
Map<TableGroup, Map<String, EntityNameUse>> entityNameUsesToPropagate = null;
List<TableGroup> treatedTableGroups = null;
List<TableGroup> filteredTableGroups = null;
List<SqmPredicate> predicates = predicate.getPredicates();
for ( int i = 0; i < predicates.size(); i++ ) {
tableGroupEntityNameUses.clear();
disjunction.add( (Predicate) predicates.get( i ).accept( this ) );
if ( !tableGroupEntityNameUses.isEmpty() ) {
if ( conjunctTreatUsagesArray == null ) {
conjunctTreatUsagesArray = new Map[predicate.getPredicates().size()];
conjunctTreatUsagesUnion = new IdentityHashMap<>();
if ( disjunctEntityNameUsesArray == null ) {
disjunctEntityNameUsesArray = new Map[predicate.getPredicates().size()];
entityNameUsesToPropagate = previousTableGroupEntityNameUses == null
? new IdentityHashMap<>()
: previousTableGroupEntityNameUses;
}
if ( i == 0 ) {
// Collect the table groups for which filters are registered
for ( Map.Entry<TableGroup, Map<String, EntityNameUse>> entry : tableGroupEntityNameUses.entrySet() ) {
if ( entry.getValue().containsValue( EntityNameUse.TREAT ) || entry.getValue().containsValue( EntityNameUse.OPTIONAL_TREAT ) ) {
if ( treatedTableGroups == null ) {
treatedTableGroups = new ArrayList<>( 1 );
}
treatedTableGroups.add( entry.getKey() );
}
if ( entry.getValue().containsValue( EntityNameUse.FILTER ) ) {
if ( filteredTableGroups == null ) {
filteredTableGroups = new ArrayList<>( 1 );
}
filteredTableGroups.add( entry.getKey() );
}
}
}
// Create a copy of the filtered table groups from which we remove
final List<TableGroup> missingTableGroupFilters;
if ( filteredTableGroups == null || i == 0 ) {
missingTableGroupFilters = Collections.emptyList();
}
else {
missingTableGroupFilters = new ArrayList<>( filteredTableGroups );
}
final List<TableGroup> missingTableGroupTreats;
if ( treatedTableGroups == null || i == 0 ) {
missingTableGroupTreats = Collections.emptyList();
}
else {
missingTableGroupTreats = new ArrayList<>( treatedTableGroups );
}
// Compute the entity name uses to propagate to the parent context
// If every disjunct contains a FILTER, we can merge the filters
// If every disjunct contains a TREAT, we can merge the treats
// Otherwise, we downgrade the entity name uses to expression uses
for ( Map.Entry<TableGroup, Map<String, EntityNameUse>> entry : tableGroupEntityNameUses.entrySet() ) {
final Map<String, EntityNameUse> entityNameUses = conjunctTreatUsagesUnion.computeIfAbsent(
entry.getKey(),
final TableGroup tableGroup = entry.getKey();
final Map<String, EntityNameUse> entityNameUses = entityNameUsesToPropagate.computeIfAbsent(
tableGroup,
k -> new HashMap<>()
);
entityNameUses.putAll( entry.getValue() );
final boolean downgradeTreatUses;
final boolean downgradeFilterUses;
if ( i == 0 ) {
// Never downgrade the treat uses of the first disjunct
downgradeTreatUses = false;
// Never downgrade the filter uses of the first disjunct
downgradeFilterUses = false;
}
else {
// If the table group is not part of the missingTableGroupTreats, we must downgrade treat uses
downgradeTreatUses = !missingTableGroupTreats.contains( tableGroup );
// If the table group is not part of the missingTableGroupFilters, we must downgrade filter uses
downgradeFilterUses = !missingTableGroupFilters.contains( tableGroup );
}
for ( Map.Entry<String, EntityNameUse> useEntry : entry.getValue().entrySet() ) {
final EntityNameUse.UseKind useKind = useEntry.getValue().getKind();
final EntityNameUse currentUseKind = entityNameUses.get( useEntry.getKey() );
final EntityNameUse unionEntityNameUse;
if ( useKind == EntityNameUse.UseKind.TREAT ) {
if ( downgradeTreatUses ) {
unionEntityNameUse = EntityNameUse.EXPRESSION;
}
else {
unionEntityNameUse = useEntry.getValue();
missingTableGroupTreats.remove( tableGroup );
}
}
else if ( useKind == EntityNameUse.UseKind.FILTER ) {
if ( downgradeFilterUses ) {
unionEntityNameUse = EntityNameUse.EXPRESSION;
}
else {
unionEntityNameUse = useEntry.getValue();
missingTableGroupFilters.remove( tableGroup );
}
}
else {
unionEntityNameUse = useEntry.getValue();
}
if ( currentUseKind == null ) {
entityNameUses.put( useEntry.getKey(), unionEntityNameUse );
}
else {
entityNameUses.put( useEntry.getKey(), unionEntityNameUse.stronger( currentUseKind ) );
}
}
}
// Downgrade entity name uses for table groups that haven't been filtered in this disjunct
for ( TableGroup missingTableGroupTreat : missingTableGroupTreats ) {
treatedTableGroups.remove( missingTableGroupTreat );
final Map<String, EntityNameUse> entityNameUses = entityNameUsesToPropagate.get( missingTableGroupTreat );
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
if ( entry.getValue().getKind() == EntityNameUse.UseKind.TREAT ) {
entry.setValue( EntityNameUse.EXPRESSION );
}
}
}
for ( TableGroup missingTableGroupFilter : missingTableGroupFilters ) {
filteredTableGroups.remove( missingTableGroupFilter );
final Map<String, EntityNameUse> entityNameUses = entityNameUsesToPropagate.get( missingTableGroupFilter );
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
if ( entry.getValue() == EntityNameUse.FILTER ) {
entry.setValue( EntityNameUse.EXPRESSION );
}
}
}
disjunctEntityNameUsesArray[i] = new IdentityHashMap<>( tableGroupEntityNameUses );
}
else {
if ( treatedTableGroups != null ) {
treatedTableGroups = null;
for ( Map<String, EntityNameUse> entityNameUses : entityNameUsesToPropagate.values() ) {
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
if ( entry.getValue().getKind() == EntityNameUse.UseKind.TREAT ) {
entry.setValue( EntityNameUse.EXPRESSION );
}
}
}
}
if ( filteredTableGroups != null ) {
filteredTableGroups = null;
for ( Map<String, EntityNameUse> entityNameUses : entityNameUsesToPropagate.values() ) {
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
if ( entry.getValue() == EntityNameUse.FILTER ) {
entry.setValue( EntityNameUse.EXPRESSION );
}
}
}
}
conjunctTreatUsagesArray[i] = new IdentityHashMap<>( tableGroupEntityNameUses );
}
}
if ( conjunctTreatUsagesArray == null ) {
if ( originalConjunctTableGroupTreatUsages != null ) {
tableGroupEntityNameUses.putAll( originalConjunctTableGroupTreatUsages );
if ( disjunctEntityNameUsesArray == null ) {
if ( previousTableGroupEntityNameUses != null ) {
tableGroupEntityNameUses.putAll( previousTableGroupEntityNameUses );
}
return disjunction;
}
@ -6917,7 +7131,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final Map<String, EntityNameUse> intersected = new HashMap<>( entry.getValue() );
entry.setValue( intersected );
boolean remove = false;
for ( Map<TableGroup, Map<String, EntityNameUse>> conjunctTreatUsages : conjunctTreatUsagesArray ) {
for ( Map<TableGroup, Map<String, EntityNameUse>> conjunctTreatUsages : disjunctEntityNameUsesArray ) {
final Map<String, EntityNameUse> entityNames;
if ( conjunctTreatUsages == null || ( entityNames = conjunctTreatUsages.get( entry.getKey() ) ) == null ) {
remove = true;
@ -6953,8 +7167,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
// Prepend the treat type usages to the respective conjuncts
for ( int i = 0; i < conjunctTreatUsagesArray.length; i++ ) {
final Map<TableGroup, Map<String, EntityNameUse>> conjunctTreatUsages = conjunctTreatUsagesArray[i];
for ( int i = 0; i < disjunctEntityNameUsesArray.length; i++ ) {
final Map<TableGroup, Map<String, EntityNameUse>> conjunctTreatUsages = disjunctEntityNameUsesArray[i];
if ( conjunctTreatUsages != null && !conjunctTreatUsages.isEmpty() ) {
disjunction.getPredicates().set(
i,
@ -6965,21 +7179,20 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
}
}
if ( originalConjunctTableGroupTreatUsages != null ) {
for ( Map.Entry<TableGroup, Map<String, EntityNameUse>> entry : originalConjunctTableGroupTreatUsages.entrySet() ) {
final Map<String, EntityNameUse> entityNameUses = tableGroupEntityNameUses.putIfAbsent(
entry.getKey(),
entry.getValue()
);
if ( entityNameUses != null && entityNameUses != entry.getValue() ) {
for ( Map.Entry<String, EntityNameUse> useEntry : entry.getValue().entrySet() ) {
final EntityNameUse currentUseKind = entityNameUses.get( useEntry.getKey() );
if ( currentUseKind == null ) {
entityNameUses.put( useEntry.getKey(), useEntry.getValue() );
}
else {
entityNameUses.put( useEntry.getKey(), useEntry.getValue().stronger( currentUseKind ) );
}
// Propagate the union of the entity name uses upwards
for ( Map.Entry<TableGroup, Map<String, EntityNameUse>> entry : entityNameUsesToPropagate.entrySet() ) {
final Map<String, EntityNameUse> entityNameUses = tableGroupEntityNameUses.putIfAbsent(
entry.getKey(),
entry.getValue()
);
if ( entityNameUses != null && entityNameUses != entry.getValue() ) {
for ( Map.Entry<String, EntityNameUse> useEntry : entry.getValue().entrySet() ) {
final EntityNameUse currentEntityNameUse = entityNameUses.get( useEntry.getKey() );
if ( currentEntityNameUse == null ) {
entityNameUses.put( useEntry.getKey(), useEntry.getValue() );
}
else {
entityNameUses.put( useEntry.getKey(), useEntry.getValue().stronger( currentEntityNameUse ) );
}
}
}
@ -7103,14 +7316,14 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
private void handleTypeComparison(Expression lhs, Expression rhs, boolean inclusive) {
final DiscriminatorPathInterpretation typeExpression;
final DiscriminatorPathInterpretation<?> typeExpression;
final EntityTypeLiteral literalExpression;
if ( lhs instanceof DiscriminatorPathInterpretation ) {
typeExpression = (DiscriminatorPathInterpretation) lhs;
typeExpression = (DiscriminatorPathInterpretation<?>) lhs;
literalExpression = rhs instanceof EntityTypeLiteral ? (EntityTypeLiteral) rhs : null;
}
else if ( rhs instanceof DiscriminatorPathInterpretation ) {
typeExpression = (DiscriminatorPathInterpretation) rhs;
typeExpression = (DiscriminatorPathInterpretation<?>) rhs;
literalExpression = lhs instanceof EntityTypeLiteral ? (EntityTypeLiteral) lhs : null;
}
else {

View File

@ -11,7 +11,8 @@ import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.tree.domain.SqmTreatedPath;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
@ -20,7 +21,6 @@ import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.select.SelectClause;
@ -36,7 +36,7 @@ public class SqlAstQueryPartProcessingStateImpl
implements SqlAstQueryPartProcessingState {
private final QueryPart queryPart;
private final Map<TableGroup, Map<EntityDomainType<?>, Boolean>> treatRegistrations = new HashMap<>();
private final Map<SqmFrom<?, ?>, Boolean> sqmFromRegistrations = new HashMap<>();
private final boolean deduplicateSelectionItems;
private FetchParent nestingFetchParent;
@ -77,27 +77,32 @@ public class SqlAstQueryPartProcessingStateImpl
}
@Override
public void registerTreat(TableGroup tableGroup, EntityDomainType<?> treatType) {
treatRegistrations.computeIfAbsent( tableGroup, tg -> new HashMap<>() ).put( treatType, Boolean.FALSE );
public void registerTreatedFrom(SqmFrom<?, ?> sqmFrom) {
sqmFromRegistrations.put( sqmFrom, null );
}
@Override
public void registerTreatUsage(TableGroup tableGroup, EntityDomainType<?> treatType) {
final Map<EntityDomainType<?>, Boolean> treatUses = treatRegistrations.get( tableGroup );
if ( treatUses == null ) {
final SqlAstProcessingState parentState = getParentState();
if ( parentState instanceof SqlAstQueryPartProcessingState ) {
( (SqlAstQueryPartProcessingState) parentState ).registerTreatUsage( tableGroup, treatType );
public void registerFromUsage(SqmFrom<?, ?> sqmFrom, boolean downgradeTreatUses) {
if ( !( sqmFrom instanceof SqmTreatedPath<?, ?> ) ) {
if ( !sqmFromRegistrations.containsKey( sqmFrom ) ) {
final SqlAstProcessingState parentState = getParentState();
if ( parentState instanceof SqlAstQueryPartProcessingState ) {
( (SqlAstQueryPartProcessingState) parentState ).registerFromUsage( sqmFrom, downgradeTreatUses );
}
}
else {
// If downgrading was once forcibly disabled, don't overwrite that anymore
final Boolean currentValue = sqmFromRegistrations.get( sqmFrom );
if ( currentValue != Boolean.FALSE ) {
sqmFromRegistrations.put( sqmFrom, downgradeTreatUses );
}
}
}
else {
treatUses.put( treatType, Boolean.TRUE );
}
}
@Override
public Map<TableGroup, Map<EntityDomainType<?>, Boolean>> getTreatRegistrations() {
return treatRegistrations;
public Map<SqmFrom<?, ?>, Boolean> getFromRegistrations() {
return sqmFromRegistrations;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -9,6 +9,7 @@ package org.hibernate.sql.ast.spi;
import java.util.Map;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
@ -24,12 +25,19 @@ public interface SqlAstQueryPartProcessingState extends SqlAstProcessingState {
*/
QueryPart getInflightQueryPart();
void registerTreat(TableGroup tableGroup, EntityDomainType<?> treatType);
void registerTreatUsage(TableGroup tableGroup, EntityDomainType<?> treatType);
/**
* Registers that the given SqmFrom is treated.
*/
void registerTreatedFrom(SqmFrom<?, ?> sqmFrom);
/**
* The treat registrations. The boolean indicates whether the treat is used in the query part.
* Registers that the given SqmFrom was used in an expression and whether to downgrade {@link org.hibernate.persister.entity.EntityNameUse#TREAT} of it.
*/
Map<TableGroup, Map<EntityDomainType<?>, Boolean>> getTreatRegistrations();
void registerFromUsage(SqmFrom<?, ?> sqmFrom, boolean downgradeTreatUses);
/**
* Returns the treated SqmFroms and whether their {@link org.hibernate.persister.entity.EntityNameUse#TREAT}
* should be downgraded to {@link org.hibernate.persister.entity.EntityNameUse#EXPRESSION}.
*/
Map<SqmFrom<?, ?>, Boolean> getFromRegistrations();
}

View File

@ -193,4 +193,20 @@ public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, SqmPat
}
return null;
}
default boolean hasRealJoins() {
for ( TableGroupJoin join : getTableGroupJoins() ) {
final TableGroup joinedGroup = join.getJoinedGroup();
if ( !( joinedGroup instanceof VirtualTableGroup ) || joinedGroup.hasRealJoins() ) {
return true;
}
}
for ( TableGroupJoin join : getNestedTableGroupJoins() ) {
final TableGroup joinedGroup = join.getJoinedGroup();
if ( !( joinedGroup instanceof VirtualTableGroup ) || joinedGroup.hasRealJoins() ) {
return true;
}
}
return false;
}
}

View File

@ -10,6 +10,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.Embeddable;
@ -273,6 +274,7 @@ public class EmbeddableWithGenericAndMappedSuperClassTest {
@Entity(name = "PopularBook")
@AttributeOverride( name = "edition.code", column = @Column(name = "code_str"))
public static class PopularBook extends Book<String> {
@ -285,6 +287,7 @@ public class EmbeddableWithGenericAndMappedSuperClassTest {
}
@Entity(name = "RareBook")
@AttributeOverride( name = "edition.code", column = @Column(name = "code_nr"))
public static class RareBook extends Book<Integer> {
public RareBook() {

View File

@ -55,35 +55,35 @@ public class EntityUseJoinedSubclassOptimizationTest {
"select " +
"t1_0.id," +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end," +
"t1_6.seats," +
"t1_1.nr," +
"t1_5.doors," +
"t1_2.familyName," +
"t1_3.architectName," +
"t1_1.seats," +
"t1_2.nr," +
"t1_3.doors," +
"t1_4.name " +
"t1_4.familyName," +
"t1_5.architectName," +
"t1_5.doors," +
"t1_6.name " +
"from Thing t1_0 " +
"join Building t1_1 on t1_0.id=t1_1.id " +
"join House t1_2 on t1_0.id=t1_2.id " +
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
"left join Car t1_5 on t1_0.id=t1_5.id " +
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
"left join Airplane t1_1 on t1_0.id=t1_1.id " +
"join Building t1_2 on t1_0.id=t1_2.id " +
"left join Car t1_3 on t1_0.id=t1_3.id " +
"join House t1_4 on t1_0.id=t1_4.id " +
"left join Skyscraper t1_5 on t1_0.id=t1_5.id " +
"left join Vehicle t1_6 on t1_0.id=t1_6.id " +
"where " +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end=2",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
@ -160,35 +160,35 @@ public class EntityUseJoinedSubclassOptimizationTest {
"select " +
"t1_0.id," +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end," +
"t1_6.seats," +
"t1_1.nr," +
"t1_5.doors," +
"t1_2.familyName," +
"t1_3.architectName," +
"t1_1.seats," +
"t1_2.nr," +
"t1_3.doors," +
"t1_4.name " +
"t1_4.familyName," +
"t1_5.architectName," +
"t1_5.doors," +
"t1_6.name " +
"from Thing t1_0 " +
"left join Building t1_1 on t1_0.id=t1_1.id " +
"left join House t1_2 on t1_0.id=t1_2.id " +
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
"left join Car t1_5 on t1_0.id=t1_5.id " +
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
"left join Airplane t1_1 on t1_0.id=t1_1.id " +
"left join Building t1_2 on t1_0.id=t1_2.id " +
"left join Car t1_3 on t1_0.id=t1_3.id " +
"left join House t1_4 on t1_0.id=t1_4.id " +
"left join Skyscraper t1_5 on t1_0.id=t1_5.id " +
"left join Vehicle t1_6 on t1_0.id=t1_6.id " +
"where " +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end!=2",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
@ -209,35 +209,35 @@ public class EntityUseJoinedSubclassOptimizationTest {
"select " +
"t1_0.id," +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end," +
"t1_6.seats," +
"t1_1.nr," +
"t1_5.doors," +
"t1_2.familyName," +
"t1_3.architectName," +
"t1_1.seats," +
"t1_2.nr," +
"t1_3.doors," +
"t1_4.name " +
"t1_4.familyName," +
"t1_5.architectName," +
"t1_5.doors," +
"t1_6.name " +
"from Thing t1_0 " +
"left join Building t1_1 on t1_0.id=t1_1.id " +
"left join House t1_2 on t1_0.id=t1_2.id " +
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
"left join Car t1_5 on t1_0.id=t1_5.id " +
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
"left join Airplane t1_1 on t1_0.id=t1_1.id " +
"left join Building t1_2 on t1_0.id=t1_2.id " +
"left join Car t1_3 on t1_0.id=t1_3.id " +
"left join House t1_4 on t1_0.id=t1_4.id " +
"left join Skyscraper t1_5 on t1_0.id=t1_5.id " +
"left join Vehicle t1_6 on t1_0.id=t1_6.id " +
"where " +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end in (2,5)",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
@ -258,35 +258,35 @@ public class EntityUseJoinedSubclassOptimizationTest {
"select " +
"t1_0.id," +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end," +
"t1_6.seats," +
"t1_1.nr," +
"t1_5.doors," +
"t1_2.familyName," +
"t1_3.architectName," +
"t1_1.seats," +
"t1_2.nr," +
"t1_3.doors," +
"t1_4.name " +
"t1_4.familyName," +
"t1_5.architectName," +
"t1_5.doors," +
"t1_6.name " +
"from Thing t1_0 " +
"join Building t1_1 on t1_0.id=t1_1.id " +
"left join House t1_2 on t1_0.id=t1_2.id " +
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
"left join Car t1_5 on t1_0.id=t1_5.id " +
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
"left join Airplane t1_1 on t1_0.id=t1_1.id " +
"join Building t1_2 on t1_0.id=t1_2.id " +
"left join Car t1_3 on t1_0.id=t1_3.id " +
"left join House t1_4 on t1_0.id=t1_4.id " +
"left join Skyscraper t1_5 on t1_0.id=t1_5.id " +
"left join Vehicle t1_6 on t1_0.id=t1_6.id " +
"where " +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end in (2,3)",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
@ -307,35 +307,35 @@ public class EntityUseJoinedSubclassOptimizationTest {
"select " +
"t1_0.id," +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end," +
"t1_6.seats," +
"t1_1.nr," +
"t1_5.doors," +
"t1_2.familyName," +
"t1_3.architectName," +
"t1_1.seats," +
"t1_2.nr," +
"t1_3.doors," +
"t1_4.name " +
"t1_4.familyName," +
"t1_5.architectName," +
"t1_5.doors," +
"t1_6.name " +
"from Thing t1_0 " +
"left join Building t1_1 on t1_0.id=t1_1.id " +
"left join House t1_2 on t1_0.id=t1_2.id " +
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
"left join Car t1_5 on t1_0.id=t1_5.id " +
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
"left join Airplane t1_1 on t1_0.id=t1_1.id " +
"left join Building t1_2 on t1_0.id=t1_2.id " +
"left join Car t1_3 on t1_0.id=t1_3.id " +
"left join House t1_4 on t1_0.id=t1_4.id " +
"left join Skyscraper t1_5 on t1_0.id=t1_5.id " +
"left join Vehicle t1_6 on t1_0.id=t1_6.id " +
"where " +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_4.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_3.id is not null then 5 " +
"when t1_1.id is not null then 6 " +
"when t1_2.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end not in (2,5)",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
@ -358,29 +358,54 @@ public class EntityUseJoinedSubclassOptimizationTest {
"select " +
"t1_0.id," +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_1.id is not null then 2 " +
"when t1_5.id is not null then 3 " +
"when t1_4.id is not null then 5 " +
"when t1_2.id is not null then 6 " +
"when t1_3.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end," +
"t1_6.seats," +
"t1_1.nr," +
"t1_5.doors," +
"t1_2.familyName," +
"t1_3.architectName," +
"t1_3.doors," +
"t1_4.name " +
"t1_2.seats," +
"t1_3.nr," +
"t1_4.doors," +
"t1_1.familyName," +
"t1_5.architectName," +
"t1_5.doors,t1_6.name " +
"from Thing t1_0 " +
"join Building t1_1 on t1_0.id=t1_1.id " +
"join House t1_2 on t1_0.id=t1_2.id " +
"left join Skyscraper t1_3 on t1_0.id=t1_3.id " +
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
"left join Car t1_5 on t1_0.id=t1_5.id " +
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
"left join House t1_1 on t1_0.id=t1_1.id " +
"left join Airplane t1_2 on t1_0.id=t1_2.id " +
"left join Building t1_3 on t1_0.id=t1_3.id " +
"left join Car t1_4 on t1_0.id=t1_4.id " +
"left join Skyscraper t1_5 on t1_0.id=t1_5.id " +
"left join Vehicle t1_6 on t1_0.id=t1_6.id " +
"where t1_1.familyName is not null",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
}
);
}
@Test
public void testTreatPathEverywhere(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
scope.inTransaction(
entityManager -> {
sqlStatementInterceptor.clear();
entityManager.createSelectionQuery( "select treat(t as House) from Thing t where treat(t as House).familyName is not null" )
.getResultList();
sqlStatementInterceptor.assertExecutedCount( 1 );
// We need to join all tables because the EntityResult will create fetches for all subtypes.
// See #testEqTypeRestriction() for further explanation
assertEquals(
"select " +
"t1_1.id," +
"t1_2.nr," +
"t1_1.familyName " +
"from Thing t1_0 " +
"join House t1_1 on t1_0.id=t1_1.id " +
"join Building t1_2 on t1_0.id=t1_2.id " +
"where " +
"t1_2.familyName is not null",
"t1_1.familyName is not null",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
}
@ -400,36 +425,65 @@ public class EntityUseJoinedSubclassOptimizationTest {
"select " +
"t1_0.id," +
"case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"when t1_5.id is not null then 2 " +
"when t1_1.id is not null then 3 " +
"when t1_4.id is not null then 5 " +
"when t1_2.id is not null then 6 " +
"when t1_3.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end," +
"t1_6.seats," +
"t1_1.nr," +
"t1_5.doors," +
"t1_2.familyName," +
"t1_3.architectName," +
"t1_3.doors," +
"t1_4.name " +
"t1_2.seats," +
"t1_3.nr," +
"t1_4.doors," +
"t1_5.familyName," +
"t1_1.architectName," +
"t1_1.doors," +
"t1_6.name " +
"from Thing t1_0 " +
"join Building t1_1 on t1_0.id=t1_1.id " +
"left join House t1_2 on t1_0.id=t1_2.id " +
"join Skyscraper t1_3 on t1_0.id=t1_3.id " +
"left join Vehicle t1_4 on t1_0.id=t1_4.id " +
"left join Car t1_5 on t1_0.id=t1_5.id " +
"left join Airplane t1_6 on t1_0.id=t1_6.id " +
"left join Skyscraper t1_1 on t1_0.id=t1_1.id " +
"left join Airplane t1_2 on t1_0.id=t1_2.id " +
"left join Building t1_3 on t1_0.id=t1_3.id " +
"left join Car t1_4 on t1_0.id=t1_4.id " +
"left join House t1_5 on t1_0.id=t1_5.id " +
"left join Vehicle t1_6 on t1_0.id=t1_6.id " +
"where " +
"case when case " +
"when t1_2.id is not null then 2 " +
"when t1_3.id is not null then 3 " +
"when t1_5.id is not null then 5 " +
"when t1_6.id is not null then 6 " +
"when t1_1.id is not null then 1 " +
"when t1_4.id is not null then 4 " +
"end=3 then t1_3.doors end is not null",
"when t1_5.id is not null then 2 " +
"when t1_1.id is not null then 3 " +
"when t1_4.id is not null then 5 " +
"when t1_2.id is not null then 6 " +
"when t1_3.id is not null then 1 " +
"when t1_6.id is not null then 4 " +
"end=3 then t1_1.doors end is not null",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
}
);
}
@Test
public void testTreatPathEverywhereSharedColumn(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
scope.inTransaction(
entityManager -> {
sqlStatementInterceptor.clear();
entityManager.createSelectionQuery( "select treat(t as Skyscraper) from Thing t where treat(t as Skyscraper).doors is not null" )
.getResultList();
sqlStatementInterceptor.assertExecutedCount( 1 );
assertEquals(
"select " +
"t1_1.id," +
"t1_2.nr," +
"t1_1.architectName," +
"t1_1.doors " +
"from Thing t1_0 " +
"join Skyscraper t1_1 on t1_0.id=t1_1.id " +
"join Building t1_2 on t1_0.id=t1_2.id " +
"where " +
"case when case " +
"when t1_1.id is not null then 3 " +
"when t1_2.id is not null then 1 " +
"end=3 then t1_1.doors end is not null",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
}

View File

@ -239,7 +239,7 @@ public class EntityUseSingleTableOptimizationTest {
"t1_0.familyName," +
"t1_0.architectName," +
"t1_0.name " +
"from (select * from Thing t where t.DTYPE='House') t1_0 " +
"from Thing t1_0 " +
"where " +
"t1_0.familyName is not null",
sqlStatementInterceptor.getSqlQueries().get( 0 )
@ -267,7 +267,7 @@ public class EntityUseSingleTableOptimizationTest {
"t1_0.familyName," +
"t1_0.architectName," +
"t1_0.name " +
"from (select * from Thing t where t.DTYPE='Skyscraper') t1_0 " +
"from Thing t1_0 " +
"where " +
"case when t1_0.DTYPE='Skyscraper' then t1_0.doors end is not null",
sqlStatementInterceptor.getSqlQueries().get( 0 )

View File

@ -58,7 +58,7 @@ public class EntityUseUnionSubclassOptimizationTest {
"t1_0.architectName," +
"t1_0.name " +
"from (" +
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House" +
"select id, nr, null as name, null as seats, null as architectName, null as doors, familyName, 2 as clazz_ from House" +
") t1_0 " +
"where " +
"t1_0.clazz_=2",
@ -81,7 +81,7 @@ public class EntityUseUnionSubclassOptimizationTest {
"select " +
"1 " +
"from (" +
"select id, nr, null as familyName, null as architectName, null as doors, 1 as clazz_ from Building" +
"select id, nr, 1 as clazz_ from Building" +
") t1_0 " +
"where " +
"t1_0.clazz_=1",
@ -134,15 +134,15 @@ public class EntityUseUnionSubclassOptimizationTest {
"t1_0.architectName," +
"t1_0.name " +
"from (" +
"select id, null as nr, null as familyName, null as architectName, null as doors, name, seats, 6 as clazz_ from Airplane " +
"select id, null as nr, name, seats, null as architectName, null as doors, null as familyName, 6 as clazz_ from Airplane " +
"union all " +
"select id, nr, null as familyName, null as architectName, null as doors, null as name, null as seats, 1 as clazz_ from Building " +
"select id, nr, null as name, null as seats, null as architectName, null as doors, null as familyName, 1 as clazz_ from Building " +
"union all " +
"select id, null as nr, null as familyName, null as architectName, doors, name, null as seats, 5 as clazz_ from Car " +
"select id, null as nr, name, null as seats, null as architectName, doors, null as familyName, 5 as clazz_ from Car " +
"union all " +
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper " +
"select id, nr, null as name, null as seats, architectName, doors, null as familyName, 3 as clazz_ from Skyscraper " +
"union all " +
"select id, null as nr, null as familyName, null as architectName, null as doors, name, null as seats, 4 as clazz_ from Vehicle" +
"select id, null as nr, name, null as seats, null as architectName, null as doors, null as familyName, 4 as clazz_ from Vehicle" +
") t1_0 " +
"where " +
"t1_0.clazz_!=2",
@ -172,9 +172,9 @@ public class EntityUseUnionSubclassOptimizationTest {
"t1_0.architectName," +
"t1_0.name " +
"from (" +
"select id, null as nr, null as familyName, null as architectName, doors, name, null as seats, 5 as clazz_ from Car " +
"select id, null as nr, name, null as seats, null as architectName, doors, null as familyName, 5 as clazz_ from Car " +
"union all " +
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House" +
"select id, nr, null as name, null as seats, null as architectName, null as doors, familyName, 2 as clazz_ from House" +
") t1_0 " +
"where " +
"t1_0.clazz_ in (2,5)",
@ -204,9 +204,9 @@ public class EntityUseUnionSubclassOptimizationTest {
"t1_0.architectName," +
"t1_0.name " +
"from (" +
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House " +
"select id, nr, null as name, null as seats, null as architectName, null as doors, familyName, 2 as clazz_ from House " +
"union all " +
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper" +
"select id, nr, null as name, null as seats, architectName, doors, null as familyName, 3 as clazz_ from Skyscraper" +
") t1_0 " +
"where " +
"t1_0.clazz_ in (2,3)",
@ -236,13 +236,13 @@ public class EntityUseUnionSubclassOptimizationTest {
"t1_0.architectName," +
"t1_0.name " +
"from (" +
"select id, null as nr, null as familyName, null as architectName, null as doors, name, seats, 6 as clazz_ from Airplane " +
"select id, null as nr, name, seats, null as architectName, null as doors, null as familyName, 6 as clazz_ from Airplane " +
"union all " +
"select id, nr, null as familyName, null as architectName, null as doors, null as name, null as seats, 1 as clazz_ from Building " +
"select id, nr, null as name, null as seats, null as architectName, null as doors, null as familyName, 1 as clazz_ from Building " +
"union all " +
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper " +
"select id, nr, null as name, null as seats, architectName, doors, null as familyName, 3 as clazz_ from Skyscraper " +
"union all " +
"select id, null as nr, null as familyName, null as architectName, null as doors, name, null as seats, 4 as clazz_ from Vehicle" +
"select id, null as nr, name, null as seats, null as architectName, null as doors, null as familyName, 4 as clazz_ from Vehicle" +
") t1_0 " +
"where " +
"t1_0.clazz_ not in (2,5)",
@ -272,7 +272,42 @@ public class EntityUseUnionSubclassOptimizationTest {
"t1_0.architectName," +
"t1_0.name " +
"from (" +
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House" +
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House " +
"union all " +
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper " +
"union all " +
"select id, null as nr, null as familyName, null as architectName, doors, name, null as seats, 5 as clazz_ from Car " +
"union all " +
"select id, null as nr, null as familyName, null as architectName, null as doors, name, seats, 6 as clazz_ from Airplane " +
"union all " +
"select id, nr, null as familyName, null as architectName, null as doors, null as name, null as seats, 1 as clazz_ from Building " +
"union all " +
"select id, null as nr, null as familyName, null as architectName, null as doors, name, null as seats, 4 as clazz_ from Vehicle" +
") t1_0 " +
"where " +
"t1_0.familyName is not null",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
}
);
}
@Test
public void testTreatPathEverywhere(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
scope.inTransaction(
entityManager -> {
sqlStatementInterceptor.clear();
entityManager.createSelectionQuery( "select treat(t as House) from Thing t where treat(t as House).familyName is not null" )
.getResultList();
sqlStatementInterceptor.assertExecutedCount( 1 );
assertEquals(
"select " +
"t1_0.id," +
"t1_0.nr," +
"t1_0.familyName " +
"from (" +
"select id, nr, familyName, 2 as clazz_ from House" +
") t1_0 " +
"where " +
"t1_0.familyName is not null",
@ -302,7 +337,43 @@ public class EntityUseUnionSubclassOptimizationTest {
"t1_0.architectName," +
"t1_0.name " +
"from (" +
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper" +
"select id, nr, familyName, null as architectName, null as doors, null as name, null as seats, 2 as clazz_ from House " +
"union all " +
"select id, nr, null as familyName, architectName, doors, null as name, null as seats, 3 as clazz_ from Skyscraper " +
"union all " +
"select id, null as nr, null as familyName, null as architectName, doors, name, null as seats, 5 as clazz_ from Car " +
"union all " +
"select id, null as nr, null as familyName, null as architectName, null as doors, name, seats, 6 as clazz_ from Airplane " +
"union all " +
"select id, nr, null as familyName, null as architectName, null as doors, null as name, null as seats, 1 as clazz_ from Building " +
"union all " +
"select id, null as nr, null as familyName, null as architectName, null as doors, name, null as seats, 4 as clazz_ from Vehicle" +
") t1_0 " +
"where " +
"case when t1_0.clazz_=3 then t1_0.doors end is not null",
sqlStatementInterceptor.getSqlQueries().get( 0 )
);
}
);
}
@Test
public void testTreatPathEverywhereSharedColumn(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = scope.getCollectingStatementInspector();
scope.inTransaction(
entityManager -> {
sqlStatementInterceptor.clear();
entityManager.createSelectionQuery( "select treat(t as Skyscraper) from Thing t where treat(t as Skyscraper).doors is not null" )
.getResultList();
sqlStatementInterceptor.assertExecutedCount( 1 );
assertEquals(
"select " +
"t1_0.id," +
"t1_0.nr," +
"t1_0.architectName," +
"t1_0.doors " +
"from (" +
"select id, nr, architectName, doors, 3 as clazz_ from Skyscraper" +
") t1_0 " +
"where " +
"case when t1_0.clazz_=3 then t1_0.doors end is not null",

View File

@ -16,9 +16,11 @@ import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.Transaction;
@ -193,48 +195,128 @@ public class TreatKeywordTest extends BaseCoreFunctionalTestCase {
@Test
@TestForIssue(jiraKey = "HHH-9411")
public void testTreatWithRestrictionOnAbstractClass() {
Session s = openSession();
Transaction tx = s.beginTransaction();
inTransaction(
s -> {
Greyhound greyhound = new Greyhound();
Dachshund dachshund = new Dachshund();
s.persist( greyhound );
s.persist( dachshund );
Greyhound greyhound = new Greyhound();
Dachshund dachshund = new Dachshund();
s.save( greyhound );
s.save( dachshund );
List results = s.createQuery( "select treat (a as Dog) from Animal a where a.fast = TRUE" ).list();
List results = s.createQuery( "select treat (a as Dog) from Animal a where a.fast = TRUE" ).list();
assertEquals( Arrays.asList( greyhound ), results );
tx.commit();
s.close();
assertEquals( Arrays.asList( greyhound ), results );
s.remove( greyhound );
s.remove( dachshund );
}
);
}
@Test
@TestForIssue(jiraKey = "HHH-16657")
public void testTypeFilterInSubquery() {
Session s = openSession();
Transaction tx = s.beginTransaction();
inTransaction(
s -> {
JoinedEntitySubclass2 child1 = new JoinedEntitySubclass2(3, "child1");
JoinedEntitySubSubclass2 child2 = new JoinedEntitySubSubclass2(4, "child2");
JoinedEntitySubclass root1 = new JoinedEntitySubclass(1, "root1", child1);
JoinedEntitySubSubclass root2 = new JoinedEntitySubSubclass(2, "root2", child2);
s.persist( child1 );
s.persist( child2 );
s.persist( root1 );
s.persist( root2 );
}
);
inSession(
s -> {
List<String> results = s.createSelectionQuery(
"select (select o.name from j.other o where type(j) = JoinedEntitySubSubclass) from JoinedEntitySubclass j order by j.id",
String.class
).list();
JoinedEntitySubclass2 child1 = new JoinedEntitySubclass2(3, "child1");
JoinedEntitySubSubclass2 child2 = new JoinedEntitySubSubclass2(4, "child2");
JoinedEntitySubclass root1 = new JoinedEntitySubclass(1, "root1", child1);
JoinedEntitySubSubclass root2 = new JoinedEntitySubSubclass(2, "root2", child2);
s.persist( child1 );
s.persist( child2 );
s.persist( root1 );
s.persist( root2 );
assertEquals( 2, results.size() );
assertNull( results.get( 0 ) );
assertEquals( "child2", results.get( 1 ) );
}
);
inTransaction(
s -> {
s.createMutationQuery( "update JoinedEntity j set j.other = null" ).executeUpdate();
s.createMutationQuery( "delete from JoinedEntity" ).executeUpdate();
}
);
}
List<String> results = s.createSelectionQuery(
"select (select o.name from j.other o where type(j) = JoinedEntitySubSubclass) from JoinedEntitySubclass j order by j.id",
String.class
).list();
@Test
@TestForIssue(jiraKey = "HHH-16658")
public void testPropagateEntityNameUsesFromDisjunction() {
inSession(
s -> {
s.createSelectionQuery(
"select 1 from Animal a where (type(a) <> Dachshund or treat(a as Dachshund).fast) and (type(a) <> Greyhound or treat(a as Greyhound).fast)",
Integer.class
).list();
}
);
}
assertEquals( 2, results.size() );
assertNull( results.get( 0 ) );
assertEquals( "child2", results.get( 1 ) );
@Test
@TestForIssue(jiraKey = "HHH-16658")
public void testPropagateEntityNameUsesFromDisjunction2() {
inSession(
s -> {
s.createSelectionQuery(
"select 1 from JoinedEntity j where type(j) <> JoinedEntitySubclass or length(coalesce(treat(j as JoinedEntitySubclass).name,'')) > 1",
Integer.class
).list();
}
);
}
tx.commit();
s.close();
@Test
@TestForIssue(jiraKey = "HHH-16657")
public void testTreatInSelect() {
inTransaction(
s -> {
JoinedEntitySubclass root1 = new JoinedEntitySubclass(1, "root1");
JoinedEntitySubSubclass root2 = new JoinedEntitySubSubclass(2, "root2");
s.persist( root1 );
s.persist( root2 );
}
);
inSession(
s -> {
List<String> results = s.createSelectionQuery(
"select treat(j as JoinedEntitySubSubclass).name from JoinedEntitySubclass j order by j.id",
String.class
).list();
assertEquals( 2, results.size() );
assertNull( results.get( 0 ) );
assertEquals( "root2", results.get( 1 ) );
}
);
inTransaction(
s -> {
s.createMutationQuery( "delete from JoinedEntity" ).executeUpdate();
}
);
}
@Test
@TestForIssue(jiraKey = "HHH-16571") // Sort of related to that issue
public void testJoinSubclassOneToMany() {
// Originally, the FK for "others" used the primary key of the root table JoinedEntity
// Since we didn't register an entity use, we wrongly pruned that table before.
// This was fixed by letting the FK descriptor point to the primary key of JoinedEntitySubclass2,
// i.e. the plural attribute declaring type, which has the nice benefit of saving us a join
inSession(
s -> {
s.createSelectionQuery(
"select 1 from JoinedEntitySubclass2 s left join s.others o",
Integer.class
).list();
}
);
}
@Entity( name = "JoinedEntity" )
@ -295,6 +377,8 @@ public class TreatKeywordTest extends BaseCoreFunctionalTestCase {
@Entity( name = "JoinedEntitySubclass2" )
@Table( name = "JoinedEntitySubclass2" )
public static class JoinedEntitySubclass2 extends JoinedEntity {
@OneToMany(mappedBy = "other")
Set<JoinedEntity> others;
public JoinedEntitySubclass2() {
}