HHH-16658 Propagate entity name uses from predicates and subqueries properly to the upper context
This commit is contained in:
parent
cf09a8aa99
commit
33d601f146
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,68 +2906,85 @@ 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;
|
||||
}
|
||||
}
|
||||
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
|
||||
registerEntityNameUsage(
|
||||
tableGroup,
|
||||
entityNameUse,
|
||||
treatTarget.getHibernateEntityName()
|
||||
);
|
||||
}
|
||||
else {
|
||||
// A simple attribute usage e.g. `alias.attribute = 1`
|
||||
parentType = entityType;
|
||||
}
|
||||
final AttributeMapping attributeMapping = parentType.findAttributeMapping( attributeName );
|
||||
if ( attributeMapping == null ) {
|
||||
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.EXPRESSION,
|
||||
parentType.getEntityName()
|
||||
);
|
||||
}
|
||||
else {
|
||||
// If the attribute mapping can't be found on the declaring type and it is not the identifier,
|
||||
// this signals that we are working with an arbitrarily chosen attribute from a subclass.
|
||||
// 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 {
|
||||
if ( tableGroup instanceof PluralTableGroup ) {
|
||||
actualTableGroup = ( (PluralTableGroup) tableGroup ).getElementTableGroup();
|
||||
}
|
||||
else {
|
||||
actualTableGroup = tableGroup;
|
||||
finalEntityNameUse = entityNameUse;
|
||||
}
|
||||
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
|
||||
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() ) {
|
||||
final Map<String, EntityNameUse> entityNameUses = conjunctTreatUsagesUnion.computeIfAbsent(
|
||||
entry.getKey(),
|
||||
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 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;
|
||||
}
|
||||
conjunctTreatUsagesArray[i] = new IdentityHashMap<>( tableGroupEntityNameUses );
|
||||
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 );
|
||||
}
|
||||
}
|
||||
if ( conjunctTreatUsagesArray == null ) {
|
||||
if ( originalConjunctTableGroupTreatUsages != null ) {
|
||||
tableGroupEntityNameUses.putAll( originalConjunctTableGroupTreatUsages );
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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() ) {
|
||||
// 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 currentUseKind = entityNameUses.get( useEntry.getKey() );
|
||||
if ( currentUseKind == null ) {
|
||||
final EntityNameUse currentEntityNameUse = entityNameUses.get( useEntry.getKey() );
|
||||
if ( currentEntityNameUse == null ) {
|
||||
entityNameUses.put( useEntry.getKey(), useEntry.getValue() );
|
||||
}
|
||||
else {
|
||||
entityNameUses.put( useEntry.getKey(), useEntry.getValue().stronger( currentUseKind ) );
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -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 ) {
|
||||
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 ).registerTreatUsage( tableGroup, treatType );
|
||||
( (SqlAstQueryPartProcessingState) parentState ).registerFromUsage( sqmFrom, downgradeTreatUses );
|
||||
}
|
||||
}
|
||||
else {
|
||||
treatUses.put( treatType, Boolean.TRUE );
|
||||
// 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<TableGroup, Map<EntityDomainType<?>, Boolean>> getTreatRegistrations() {
|
||||
return treatRegistrations;
|
||||
public Map<SqmFrom<?, ?>, Boolean> getFromRegistrations() {
|
||||
return sqmFromRegistrations;
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 )
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,28 +195,27 @@ 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.save( greyhound );
|
||||
s.save( dachshund );
|
||||
s.persist( greyhound );
|
||||
s.persist( dachshund );
|
||||
|
||||
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();
|
||||
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);
|
||||
|
@ -223,7 +224,10 @@ public class TreatKeywordTest extends BaseCoreFunctionalTestCase {
|
|||
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
|
||||
|
@ -232,9 +236,87 @@ public class TreatKeywordTest extends BaseCoreFunctionalTestCase {
|
|||
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();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
s.close();
|
||||
@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();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@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() {
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue