HHH-16494 Reworked pruning for joined inheritance persister

Added handling of subclasses with the same table name and discriminator condition.
Also avoid useless left-joins when not finding table references instead of throwing an exception.
This commit is contained in:
Marco Belladelli 2023-05-16 11:20:50 +02:00
parent fed020e110
commit 22091b2254
4 changed files with 129 additions and 70 deletions

View File

@ -78,6 +78,8 @@ import org.hibernate.engine.internal.MutableEntityEntryFactory;
import org.hibernate.engine.internal.StatefulPersistenceContext;
import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.profile.internal.FetchProfileAffectee;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CollectionKey;
@ -121,7 +123,6 @@ import org.hibernate.internal.util.collections.LockModeEnumMap;
import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.TooManyRowsAffectedException;
import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper;
import org.hibernate.engine.profile.internal.FetchProfileAffectee;
import org.hibernate.loader.ast.internal.LoaderSelectBuilder;
import org.hibernate.loader.ast.internal.LoaderSqlAstCreationState;
import org.hibernate.loader.ast.internal.MultiIdEntityLoaderArrayParam;
@ -198,6 +199,7 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EntityInstantiator;
import org.hibernate.metamodel.spi.EntityRepresentationStrategy;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.mutation.DeleteCoordinator;
@ -225,6 +227,7 @@ import org.hibernate.service.ServiceRegistry;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.Alias;
import org.hibernate.sql.Delete;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.SimpleSelect;
import org.hibernate.sql.Template;
import org.hibernate.sql.ast.spi.SimpleFromClauseAccessImpl;
@ -296,6 +299,7 @@ import static org.hibernate.generator.EventType.INSERT;
import static org.hibernate.generator.EventType.UPDATE;
import static org.hibernate.internal.util.ReflectHelper.isAbstractClass;
import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.StringHelper.qualifyConditionally;
import static org.hibernate.internal.util.collections.ArrayHelper.contains;
import static org.hibernate.internal.util.collections.ArrayHelper.to2DStringArray;
import static org.hibernate.internal.util.collections.ArrayHelper.toBooleanArray;
@ -3161,6 +3165,63 @@ public abstract class AbstractEntityPersister
return predicate;
}
protected String getPrunedDiscriminatorPredicate(
Map<String, EntityNameUse> entityNameUses,
MappingMetamodelImplementor mappingMetamodel,
String alias) {
final InFragment frag = new InFragment();
if ( isDiscriminatorFormula() ) {
frag.setFormula( alias, getDiscriminatorFormulaTemplate() );
}
else {
frag.setColumn( alias, getDiscriminatorColumnName() );
}
boolean containsNotNull = false;
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
final EntityNameUse.UseKind useKind = entry.getValue().getKind();
if ( useKind == EntityNameUse.UseKind.PROJECTION || useKind == EntityNameUse.UseKind.EXPRESSION ) {
// We only care about treat and filter uses which allow to reduce the amount of rows to select
continue;
}
final EntityPersister persister = mappingMetamodel.getEntityDescriptor( entry.getKey() );
// Filtering for abstract entities makes no sense, so ignore that
// Also, it makes no sense to filter for any of the super types,
// as the query will contain a filter for that already anyway
if ( !persister.isAbstract() && ( this == persister || !isTypeOrSuperType( persister ) ) ) {
containsNotNull = containsNotNull || InFragment.NOT_NULL.equals( persister.getDiscriminatorSQLValue() );
frag.addValue( persister.getDiscriminatorSQLValue() );
}
}
final List<String> discriminatorSQLValues = Arrays.asList( ( (AbstractEntityPersister) getRootEntityDescriptor() ).fullDiscriminatorSQLValues );
if ( frag.getValues().size() == discriminatorSQLValues.size() ) {
// Nothing to prune if we filter for all subtypes
return null;
}
if ( containsNotNull ) {
final StringBuilder sb = new StringBuilder();
String lhs;
if ( isDiscriminatorFormula() ) {
lhs = StringHelper.replace( getDiscriminatorFormulaTemplate(), Template.TEMPLATE, alias );
}
else {
lhs = qualifyConditionally( alias, getDiscriminatorColumnName() );
}
sb.append( " or " ).append( lhs ).append( " is not in (" );
for ( Object discriminatorSQLValue : discriminatorSQLValues ) {
if ( !frag.getValues().contains( discriminatorSQLValue ) ) {
sb.append( lhs ).append( discriminatorSQLValue );
}
}
sb.append( ") and " ).append( lhs ).append( " is not null" );
frag.getValues().remove( InFragment.NOT_NULL );
return frag.toFragmentString() + sb;
}
else {
return frag.toFragmentString();
}
}
@Override
public void applyFilterRestrictions(
Consumer<Predicate> predicateConsumer,

View File

@ -51,6 +51,7 @@ import org.hibernate.metamodel.mapping.internal.CaseStatementDiscriminatorMappin
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.internal.SqlFragmentPredicate;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.spi.NavigablePath;
@ -75,6 +76,7 @@ import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger;
import static java.util.Collections.emptyMap;
import static org.hibernate.internal.util.collections.ArrayHelper.indexOf;
import static org.hibernate.internal.util.collections.ArrayHelper.to2DStringArray;
import static org.hibernate.internal.util.collections.ArrayHelper.toIntArray;
import static org.hibernate.internal.util.collections.ArrayHelper.toStringArray;
@ -1314,6 +1316,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
// i.e. with parenthesis around, as that means the table reference joins will be isolated
final boolean innerJoinOptimization = tableGroup.canUseInnerJoins() || tableGroup.isRealTableGroup();
final Set<String> tablesToInnerJoin = innerJoinOptimization ? new HashSet<>() : null;
boolean needsTreatDiscriminator = false;
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
final EntityNameUse.UseKind useKind = entry.getValue().getKind();
final JoinedSubclassEntityPersister persister =
@ -1355,15 +1358,22 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
}
}
}
final String tableName = persister.getTableName();
final TableReference mainTableReference = tableGroup.getTableReference(
null,
persister.getTableName(),
tableName,
false
);
if ( mainTableReference == null ) {
throw new UnknownTableReferenceException( persister.getTableName(), "Couldn't find table reference" );
if ( mainTableReference != null ) {
retainedTableReferences.add( mainTableReference );
}
if ( needsDiscriminator() ) {
// We allow multiple joined subclasses to use the same table if they define a discriminator column.
// In this case, we might need to add a discriminator condition to make sure we filter the correct subtype,
// see SingleTableEntityPersister#pruneForSubclasses for more details on this condition
needsTreatDiscriminator = needsTreatDiscriminator || !persister.isAbstract() &&
!isTypeOrSuperType( persister ) && useKind == EntityNameUse.UseKind.TREAT;
}
retainedTableReferences.add( mainTableReference );
}
// If no tables to inner join have been found, we add at least the super class tables of this persister
if ( innerJoinOptimization && tablesToInnerJoin.isEmpty() ) {
@ -1376,6 +1386,30 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
}
final List<TableReferenceJoin> tableReferenceJoins = tableGroup.getTableReferenceJoins();
if ( needsTreatDiscriminator ) {
if ( tableReferenceJoins.isEmpty() ) {
// We need to apply the discriminator predicate to the primary table reference itself
final String discriminatorPredicate = getPrunedDiscriminatorPredicate( entityNameUses, metamodel, "t" );
if ( discriminatorPredicate != null ) {
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getPrimaryTableReference();
tableReference.setPrunedTableExpression( "(select * from " + getRootTableName() + " t where " + discriminatorPredicate + ")" );
}
}
else {
// We have to apply the discriminator condition to the root table reference join
boolean applied = applyDiscriminatorPredicate(
tableReferenceJoins.get( 0 ),
(NamedTableReference) tableGroup.getPrimaryTableReference(),
entityNameUses,
metamodel
);
for ( int i = 0; !applied && i < tableReferenceJoins.size(); i++ ) {
final TableReferenceJoin join = tableReferenceJoins.get( i );
applied = applyDiscriminatorPredicate( join, join.getJoinedTableReference(), entityNameUses, metamodel );
}
assert applied : "Could not apply treat discriminator predicate to root table join";
}
}
if ( tableReferenceJoins.isEmpty() ) {
return;
}
@ -1394,9 +1428,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
tableReferenceJoins.add( join );
}
else {
final String tableExpression = oldJoin.getJoinedTableReference().getTableExpression();
for ( int i = subclassCoreTableSpan; i < subclassTableNameClosure.length; i++ ) {
if ( tableExpression.equals( subclassTableNameClosure[i] ) ) {
if ( joinedTableReference.getTableExpression().equals( subclassTableNameClosure[i] ) ) {
// Retain joins to secondary tables
tableReferenceJoins.add( oldJoin );
break;
@ -1410,6 +1443,24 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
}
}
private boolean applyDiscriminatorPredicate(
TableReferenceJoin join,
NamedTableReference tableReference,
Map<String, EntityNameUse> entityNameUses,
MappingMetamodelImplementor metamodel) {
if ( tableReference.getTableExpression().equals( getRootTableName() ) ) {
assert join.getJoinType() == SqlAstJoinType.INNER : "Found table reference join with root table of non-INNER type: " + join.getJoinType();
final String discriminatorPredicate = getPrunedDiscriminatorPredicate(
entityNameUses,
metamodel,
tableReference.getIdentificationVariable()
);
join.applyPredicate( new SqlFragmentPredicate( discriminatorPredicate ) );
return true;
}
return false;
}
@Override
public void visitConstraintOrderedTables(ConstraintOrderedTableConsumer consumer) {
for ( int i = 0; i < constraintOrderedTableNames.length; i++ ) {

View File

@ -8,7 +8,6 @@ package org.hibernate.persister.entity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -23,7 +22,6 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
import org.hibernate.internal.DynamicFilterAliasGenerator;
import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jdbc.Expectation;
import org.hibernate.mapping.Column;
@ -40,8 +38,6 @@ import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.Template;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.model.ast.builder.MutationGroupBuilder;
@ -692,61 +688,10 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
return;
}
final InFragment frag = new InFragment();
if ( isDiscriminatorFormula() ) {
frag.setFormula( "t", getDiscriminatorFormulaTemplate() );
}
else {
frag.setColumn( "t", getDiscriminatorColumnName() );
}
boolean containsNotNull = false;
for ( Map.Entry<String, EntityNameUse> entry : entityNameUses.entrySet() ) {
final EntityNameUse.UseKind useKind = entry.getValue().getKind();
if ( useKind == EntityNameUse.UseKind.PROJECTION || useKind == EntityNameUse.UseKind.EXPRESSION ) {
// We only care about treat and filter uses which allow to reduce the amount of rows to select
continue;
}
final EntityPersister persister = mappingMetamodel.getEntityDescriptor( entry.getKey() );
// Filtering for abstract entities makes no sense, so ignore that
// Also, it makes no sense to filter for any of the super types,
// as the query will contain a filter for that already anyway
if ( !persister.isAbstract() && ( this == persister || !isTypeOrSuperType( persister ) ) ) {
containsNotNull = containsNotNull || InFragment.NOT_NULL.equals( persister.getDiscriminatorSQLValue() );
frag.addValue( persister.getDiscriminatorSQLValue() );
}
}
final List<String> discriminatorSQLValues = Arrays.asList( ( (SingleTableEntityPersister) getRootEntityDescriptor() ).fullDiscriminatorSQLValues );
if ( frag.getValues().size() == discriminatorSQLValues.size() ) {
// Nothing to prune if we filter for all subtypes
return;
}
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getPrimaryTableReference();
if ( containsNotNull ) {
StringBuilder sb = new StringBuilder();
String lhs;
if ( isDiscriminatorFormula() ) {
lhs = StringHelper.replace( getDiscriminatorFormulaTemplate(), Template.TEMPLATE, "t" );
}
else {
lhs = "t." + getDiscriminatorColumnName();
}
sb.append( " or " ).append( lhs ).append( " is not in (" );
for ( Object discriminatorSQLValue : discriminatorSQLValues ) {
if ( !frag.getValues().contains( discriminatorSQLValue ) ) {
sb.append( lhs ).append( discriminatorSQLValue );
}
}
sb.append( ") and " ).append( lhs ).append( " is not null" );
frag.getValues().remove( InFragment.NOT_NULL );
tableReference.setPrunedTableExpression(
"(select * from " + getTableName() + " t where " + frag.toFragmentString() + sb + ")"
);
}
else {
tableReference.setPrunedTableExpression(
"(select * from " + getTableName() + " t where " + frag.toFragmentString() + ")"
);
final String discriminatorPredicate = getPrunedDiscriminatorPredicate( entityNameUses, mappingMetamodel, "t" );
if ( discriminatorPredicate != null ) {
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getPrimaryTableReference();
tableReference.setPrunedTableExpression( "(select * from " + getTableName() + " t where " + discriminatorPredicate + ")" );
}
}

View File

@ -3111,10 +3111,12 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
subType.getEntityName(),
(s, existingUse) -> finalEntityNameUse.stronger( existingUse )
);
actualTableGroup.resolveTableReference(
null,
subType.getEntityPersister().getMappedTableDetails().getTableName()
);
if ( useKind == EntityNameUse.UseKind.PROJECTION ) {
actualTableGroup.resolveTableReference(
null,
subType.getEntityPersister().getMappedTableDetails().getTableName()
);
}
}
}
}