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:
parent
fed020e110
commit
22091b2254
|
@ -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,
|
||||
|
|
|
@ -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++ ) {
|
||||
|
|
|
@ -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 + ")" );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue