Fix treat join issue with one-to-many and make sure table pruning always works. Also fix some issues with union subclass pruning

This commit is contained in:
Christian Beikov 2022-02-07 17:48:47 +01:00
parent 65e282766c
commit 0a5b62421e
12 changed files with 268 additions and 94 deletions

View File

@ -8,7 +8,9 @@ package org.hibernate.dialect;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
@ -21,6 +23,11 @@ import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.DerivedTableReference;
import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate;
import org.hibernate.sql.ast.tree.select.QueryPart;
@ -156,6 +163,23 @@ public class H2SqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstT
appendSql( CLOSE_PARENTHESIS );
}
@Override
protected boolean renderPrimaryTableReference(TableGroup tableGroup, LockMode lockMode) {
final TableReference tableRef = tableGroup.getPrimaryTableReference();
// The H2 parser can't handle a sub-query as first element in a nested join
// i.e. `join ( (select ...) alias join ... )`, so we have to introduce a dummy table reference
if ( tableRef instanceof QueryPartTableReference || tableRef.getTableId().startsWith( "(select" ) ) {
final boolean realTableGroup = tableGroup.isRealTableGroup()
&& ( CollectionHelper.isNotEmpty( tableGroup.getTableReferenceJoins() )
|| hasNestedTableGroupsToRender( tableGroup.getNestedTableGroupJoins() ) );
if ( realTableGroup ) {
appendSql( "dual cross join " );
}
}
return super.renderPrimaryTableReference( tableGroup, lockMode );
}
@Override
protected boolean supportsRowValueConstructorSyntax() {
// Just a guess

View File

@ -6497,11 +6497,6 @@ public abstract class AbstractEntityPersister
}
}
protected EntityMappingType getSubclassMappingType(String subclassName) {
return subclassMappingTypes != null ? subclassMappingTypes.get( subclassName ) : null;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// EntityDefinition impl (walking model - deprecated)

View File

@ -48,6 +48,7 @@ import org.hibernate.metamodel.mapping.internal.BasicEntityIdentifierMappingImpl
import org.hibernate.metamodel.mapping.internal.CaseStatementDiscriminatorMappingImpl;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.query.spi.NavigablePath;
@ -1214,16 +1215,13 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
@Override
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
// If the base type is part of the treatedEntityNames this means we can't optimize this,
// as the table group is e.g. returned through a select
if ( treatedEntityNames.contains( getEntityName() ) ) {
return;
}
final Set<TableReference> retainedTableReferences = new HashSet<>( treatedEntityNames.size() );
final Set<String> sharedSuperclassTables = new HashSet<>();
final MappingMetamodelImplementor metamodel = getFactory().getRuntimeMetamodels().getMappingMetamodel();
for ( String treatedEntityName : treatedEntityNames ) {
final JoinedSubclassEntityPersister subPersister = (JoinedSubclassEntityPersister) getSubclassMappingType( treatedEntityName );
final JoinedSubclassEntityPersister subPersister =
(JoinedSubclassEntityPersister) metamodel.findEntityDescriptor( treatedEntityName );
final String[] subclassTableNames = subPersister.getSubclassTableNames();
// For every treated entity name, we collect table names that are needed by all treated entity names
// In mathematical terms, sharedSuperclassTables will be the "intersection" of the table names of all treated entities

View File

@ -880,11 +880,6 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
@Override
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
// If the base type is part of the treatedEntityNames this means we can't optimize this,
// as the table group is e.g. returned through a select
if ( treatedEntityNames.contains( getEntityName() ) ) {
return;
}
// The optimization is to simply add the discriminator filter fragment for all treated entity names
final NamedTableReference tableReference = (NamedTableReference) tableGroup.getPrimaryTableReference();
tableReference.setPrunedTableExpression(

View File

@ -40,8 +40,10 @@ import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Subclass;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.query.spi.NavigablePath;
@ -382,11 +384,6 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
@Override
public void pruneForSubclasses(TableGroup tableGroup, Set<String> treatedEntityNames) {
// If the base type is part of the treatedEntityNames this means we can't optimize this,
// as the table group is e.g. returned through a select
if ( treatedEntityNames.contains( getEntityName() ) ) {
return;
}
final NamedTableReference tableReference = (NamedTableReference) tableGroup.resolveTableReference( getRootTableName() );
// Replace the default union sub-query with a specially created one that only selects the tables for the treated entity names
tableReference.setPrunedTableExpression( generateSubquery( treatedEntityNames ) );
@ -496,33 +493,40 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
}
final Dialect dialect = getFactory().getJdbcServices().getDialect();
final MappingMetamodelImplementor metamodel = getFactory().getRuntimeMetamodels().getMappingMetamodel();
// Collect all selectables of every entity subtype and group by selection expression as well as table name
final LinkedHashMap<String, Map<String, SelectableMapping>> selectables = new LinkedHashMap<>();
visitSubTypeAttributeMappings(
attributeMapping -> attributeMapping.forEachSelectable(
(i, selectable) -> selectables.computeIfAbsent( selectable.getSelectionExpression(), k -> new HashMap<>() )
.put( selectable.getContainingTableExpression(), selectable )
)
);
final SelectableConsumer selectableConsumer = (i, selectable) -> {
selectables.computeIfAbsent( selectable.getSelectionExpression(), k -> new HashMap<>() )
.put( selectable.getContainingTableExpression(), selectable );
};
// Collect the concrete subclass table names for the treated entity names
final Set<String> treatedTableNames = new HashSet<>( treated.size() );
for ( String subclassName : treated ) {
final UnionSubclassEntityPersister subPersister =
(UnionSubclassEntityPersister) getSubclassMappingType( subclassName );
(UnionSubclassEntityPersister) metamodel.getEntityDescriptor( subclassName );
for ( String subclassTableName : subPersister.getSubclassTableNames() ) {
if ( ArrayHelper.indexOf( subclassSpaces, subclassTableName ) != -1 ) {
treatedTableNames.add( subclassTableName );
}
}
subPersister.getIdentifierMapping().forEachSelectable( selectableConsumer );
if ( subPersister.getVersionMapping() != null ) {
subPersister.getVersionMapping().forEachSelectable( selectableConsumer );
}
subPersister.visitSubTypeAttributeMappings(
attributeMapping -> attributeMapping.forEachSelectable( selectableConsumer )
);
}
// Create a union sub-query for the table names, like generateSubquery(PersistentClass model, Mapping mapping)
final StringBuilder buf = new StringBuilder( subquery.length() )
.append( "( " );
for ( int i = 0; i < subclassTableNames.length; i++ ) {
final String subclassTableName = subclassTableNames[i];
for ( String name : getEntityMetamodel().getSubclassEntityNames() ) {
final AbstractEntityPersister persister = (AbstractEntityPersister) metamodel.findEntityDescriptor( name );
final String subclassTableName = persister.getTableName();
if ( treatedTableNames.contains( subclassTableName ) ) {
buf.append( "select " );
for ( Map<String, SelectableMapping> selectableMappings : selectables.values() ) {
@ -540,7 +544,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister {
);
buf.append( ", " );
}
buf.append( i ).append( " as clazz_" );
buf.append( persister.getDiscriminatorSQLValue() ).append( " as clazz_" );
buf.append( " from " ).append( subclassTableName );
buf.append( " union " );
if ( dialect.supportsUnionAll() ) {

View File

@ -21,7 +21,6 @@ import org.hibernate.query.sqm.tree.expression.SqmDistinct;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.sql.ast.tree.predicate.Predicate;
/**
* @author Christian Beikov
@ -88,7 +87,7 @@ public class SelfRenderingSqmAggregateFunction<T> extends SelfRenderingSqmFuncti
getFunctionName(),
getRenderingSupport(),
resolveSqlAstArguments( getArguments(), walker ),
filter == null ? null : (Predicate) filter.accept( walker ),
filter == null ? null : walker.visitNestedTopLevelPredicate( filter ),
resultType,
getMappingModelExpressible( walker, resultType )
);

View File

@ -408,9 +408,21 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
private final SqlAliasBaseManager sqlAliasBaseManager = new SqlAliasBaseManager();
private final Stack<SqlAstProcessingState> processingStateStack = new StandardStack<>();
private final Stack<FromClauseIndex> fromClauseIndexStack = new StandardStack<>();
/*
* Captures all entity names as which a table group was treated.
* This information is used to prune tables from the table group.
*/
private final Map<TableGroup, Set<String>> tableGroupTreatUsages = new IdentityHashMap<>();
/*
* Used to capture the treat usages within the current conjunct.
* The top level conjunct contexts like visitWhereClause, visitHavingClause, visitOrPredicate and
* visitNestedTopLevelPredicate consume these treat usages by rendering a type restriction
*/
private final Map<SqmPath<?>, Set<String>> conjunctTreatUsages = new IdentityHashMap<>();
private SqlAstProcessingState lastPoppedProcessingState;
private FromClauseIndex lastPoppedFromClauseIndex;
private SqmJoin<?, ?> currentlyProcessingJoin;
protected Predicate additionalRestrictions;
private final Stack<Clause> currentClauseStack = new StandardStack<>();
private final Stack<Supplier<MappingModelExpressible<?>>> inferrableTypeAccessStack = new StandardStack<>(
@ -700,14 +712,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
Predicate suppliedPredicate = null;
final SqmWhereClause whereClause = sqmStatement.getWhereClause();
if ( whereClause != null && whereClause.getPredicate() != null ) {
getCurrentClauseStack().push( Clause.WHERE );
try {
suppliedPredicate = (Predicate) whereClause.getPredicate().accept( this );
}
finally {
getCurrentClauseStack().pop();
}
if ( whereClause != null ) {
suppliedPredicate = visitWhereClause( whereClause.getPredicate() );
}
return new UpdateStatement(
@ -934,14 +940,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
Predicate suppliedPredicate = null;
final SqmWhereClause whereClause = statement.getWhereClause();
if ( whereClause != null && whereClause.getPredicate() != null ) {
getCurrentClauseStack().push( Clause.WHERE );
try {
suppliedPredicate = (Predicate) whereClause.getPredicate().accept( this );
}
finally {
getCurrentClauseStack().pop();
}
if ( whereClause != null ) {
suppliedPredicate = visitWhereClause( whereClause.getPredicate() );
}
return new DeleteStatement(
@ -1635,7 +1635,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
final SqmSelectClause selectClause = sqmQuerySpec.getSelectClause();
Predicate originalAdditionalRestrictions = additionalRestrictions;
final Predicate originalAdditionalRestrictions = additionalRestrictions;
additionalRestrictions = null;
final boolean trackAliasedNodePositions;
@ -1694,14 +1694,8 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
visitSelectClause( selectClause );
final SqmWhereClause whereClause = sqmQuerySpec.getWhereClause();
if ( whereClause != null && whereClause.getPredicate() != null ) {
currentClauseStack.push( Clause.WHERE );
try {
sqlQuerySpec.applyPredicate( (Predicate) whereClause.getPredicate().accept( this ) );
}
finally {
currentClauseStack.pop();
}
if ( whereClause != null ) {
sqlQuerySpec.applyPredicate( visitWhereClause( whereClause.getPredicate() ) );
}
sqlQuerySpec.setGroupByClauseExpressions( visitGroupByClause( sqmQuerySpec.getGroupByClauseExpressions() ) );
@ -2067,6 +2061,22 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return Collections.emptyList();
}
private Predicate visitWhereClause(SqmPredicate sqmPredicate) {
if ( sqmPredicate == null ) {
return null;
}
currentClauseStack.push( Clause.WHERE );
try {
return SqlAstTreeHelper.combinePredicates(
(Predicate) sqmPredicate.accept( this ),
consumeConjunctTreatTypeRestrictions()
);
}
finally {
currentClauseStack.pop();
}
}
@Override
public Predicate visitHavingClause(SqmPredicate sqmPredicate) {
if ( sqmPredicate == null ) {
@ -2074,7 +2084,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
currentClauseStack.push( Clause.HAVING );
try {
return (Predicate) sqmPredicate.accept( this );
return SqlAstTreeHelper.combinePredicates(
(Predicate) sqmPredicate.accept( this ),
consumeConjunctTreatTypeRestrictions()
);
}
finally {
currentClauseStack.pop();
@ -2100,14 +2113,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
}
// public QuerySpec visitOffsetAndFetchExpressions(QuerySpec sqlQuerySpec, SqmQuerySpec<?> sqmQuerySpec) {
// final Expression offsetExpression = visitOffsetExpression( sqmQuerySpec.getOffsetExpression() );
// final Expression fetchExpression = visitFetchExpression( sqmQuerySpec.getFetchExpression() );
// sqlQuerySpec.setOffsetClauseExpression( offsetExpression );
// sqlQuerySpec.setFetchClauseExpression( fetchExpression, sqmQuerySpec.getFetchClauseType() );
// return sqlQuerySpec;
// }
@Override
public Expression visitOffsetExpression(SqmExpression<?> expression) {
if ( expression == null ) {
@ -2156,8 +2161,6 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return null;
}
protected Predicate additionalRestrictions;
protected void consumeFromClauseRoot(SqmRoot<?> sqmRoot) {
log.tracef( "Resolving SqmRoot [%s] to TableGroup", sqmRoot );
final FromClauseIndex fromClauseIndex = getFromClauseIndex();
@ -2392,16 +2395,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
.getEntityDescriptor( entityDomainType.getHibernateEntityName() );
}
private final Map<TableGroup, Set<String>> tableGroupTreatUsages = new IdentityHashMap<>();
protected void registerUsage(SqmFrom<?, ?> sqmFrom, TableGroup tableGroup) {
protected void registerTreatUsage(SqmFrom<?, ?> sqmFrom, TableGroup tableGroup) {
final EntityDomainType<?> treatedType;
if ( sqmFrom instanceof SqmTreatedPath<?, ?> ) {
treatedType = ( (SqmTreatedPath<?, ?>) sqmFrom ).getTreatTarget();
}
else if ( sqmFrom.getReferencedPathSource().getSqmPathType() instanceof EntityDomainType<?> ) {
treatedType = (EntityDomainType<?>) sqmFrom.getReferencedPathSource().getSqmPathType();
}
else {
return;
}
@ -2458,13 +2456,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
sqmFrom.visitSqmJoins(
sqmJoin -> {
final TableGroup actualTableGroup = findActualTableGroup( lhsTableGroup, sqmJoin );
registerUsage( (SqmFrom<?, ?>) sqmJoin.getLhs(), actualTableGroup );
registerTreatUsage( (SqmFrom<?, ?>) sqmJoin.getLhs(), actualTableGroup );
consumeExplicitJoin( sqmJoin, actualTableGroup, actualTableGroup, true );
}
);
for ( SqmFrom<?, ?> sqmTreat : sqmFrom.getSqmTreats() ) {
final TableGroup actualTableGroup = findActualTableGroup( lhsTableGroup, sqmTreat );
registerUsage( sqmTreat, actualTableGroup );
registerTreatUsage( sqmTreat, actualTableGroup );
consumeExplicitJoins( sqmTreat, actualTableGroup );
}
}
@ -2633,7 +2631,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
final SqmJoin<?, ?> oldJoin = currentlyProcessingJoin;
currentlyProcessingJoin = sqmJoin;
joinedTableGroupJoin.applyPredicate( (Predicate) sqmJoin.getJoinPredicate().accept( this ) );
joinedTableGroupJoin.applyPredicate( visitNestedTopLevelPredicate( sqmJoin.getJoinPredicate() ) );
currentlyProcessingJoin = oldJoin;
}
@ -2702,7 +2700,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
if ( sqmJoin.getJoinPredicate() != null ) {
final SqmJoin<?, ?> oldJoin = currentlyProcessingJoin;
currentlyProcessingJoin = sqmJoin;
tableGroupJoin.applyPredicate( (Predicate) sqmJoin.getJoinPredicate().accept( this ) );
tableGroupJoin.applyPredicate( visitNestedTopLevelPredicate( sqmJoin.getJoinPredicate() ) );
currentlyProcessingJoin = oldJoin;
}
@ -2782,7 +2780,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
if ( newTableGroup != null ) {
implicitJoinChecker.accept( newTableGroup );
if ( sqmPath instanceof SqmFrom<?, ?> ) {
registerUsage( (SqmFrom<?, ?>) sqmPath, newTableGroup );
registerTreatUsage( (SqmFrom<?, ?>) sqmPath, newTableGroup );
}
}
return newTableGroup;
@ -2790,11 +2788,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
else if ( sqmPath instanceof SqmTreatedPath<?, ?> ) {
fromClauseIndex.register( (SqmPath<?>) sqmPath, tableGroup );
if ( sqmPath instanceof SqmFrom<?, ?> ) {
registerUsage( (SqmFrom<?, ?>) sqmPath, tableGroup );
registerTreatUsage( (SqmFrom<?, ?>) sqmPath, tableGroup );
}
}
else if ( parentPath instanceof SqmFrom<?, ?> ) {
registerUsage( (SqmFrom<?, ?>) parentPath, tableGroup );
registerTreatUsage( (SqmFrom<?, ?>) parentPath, tableGroup );
}
return tableGroup;
}
@ -2819,12 +2817,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
fromClauseIndex.getTableGroup( navigablePath ),
joinedPath
);
if ( createdTableGroup != null && joinedPath instanceof SqmTreatedPath<?, ?> ) {
fromClauseIndex.register( joinedPath, createdTableGroup );
if ( createdTableGroup != null ) {
if ( joinedPath instanceof SqmTreatedPath<?, ?> ) {
fromClauseIndex.register( joinedPath, createdTableGroup );
}
if ( joinedPath instanceof SqmFrom<?, ?> ) {
registerTreatUsage( (SqmFrom<?, ?>) joinedPath, createdTableGroup );
}
}
}
else if ( joinedPath instanceof SqmFrom<?, ?> ) {
registerUsage( (SqmFrom<?, ?>) joinedPath, tableGroup );
registerTreatUsage( (SqmFrom<?, ?>) joinedPath, tableGroup );
}
}
@ -3356,6 +3359,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public Expression visitTreatedPath(SqmTreatedPath<?, ?> sqmTreatedPath) {
prepareReusablePath( sqmTreatedPath, () -> null );
final TableGroup resolved = getFromClauseAccess().findTableGroup( sqmTreatedPath.getNavigablePath() );
if ( resolved != null ) {
log.tracef( "SqmTreatedPath [%s] resolved to existing TableGroup [%s]", sqmTreatedPath, resolved );
@ -3864,7 +3868,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
}
private Expression withTreatRestriction(Expression expression, SqmPath<?> path) {
final SqmPath<?> lhs = path.getLhs();
final SqmPath<?> lhs;
if ( path instanceof SqmTreatedPath<?, ?> ) {
lhs = path;
}
else {
lhs = path.getLhs();
}
if ( lhs instanceof SqmTreatedPath<?, ?> ) {
final SqmTreatedPath<?, ?> treatedPath = (SqmTreatedPath<?, ?>) lhs;
final Class<?> treatTargetJavaType = treatedPath.getTreatTarget().getJavaType();
@ -3873,6 +3883,21 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// Treating a node to a super type can be ignored
return expression;
}
if ( !( expression.getExpressionType() instanceof BasicValuedMapping ) ) {
// A case wrapper for non-basic paths is not possible,
// because a case expression must return a scalar value,
// so we instead add the type restriction predicate as conjunct
final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = domainModel.findEntityDescriptor(
treatedPath.getTreatTarget().getHibernateEntityName()
);
conjunctTreatUsages.computeIfAbsent( treatedPath.getWrappedPath(), p -> new HashSet<>( 1 ) )
.addAll( entityDescriptor.getEntityMetamodel().getSubclassEntityNames() );
return expression;
}
// Note: If the columns that are accessed are not shared with other entities, we could avoid this wrapping
return createCaseExpression( treatedPath.getWrappedPath(), treatedPath.getTreatTarget(), expression );
}
return expression;
@ -3894,18 +3919,49 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
);
}
private Predicate consumeConjunctTreatTypeRestrictions() {
return consumeConjunctTreatTypeRestrictions( conjunctTreatUsages );
}
private Predicate consumeConjunctTreatTypeRestrictions(Map<SqmPath<?>, Set<String>> conjunctTreatUsages) {
if ( conjunctTreatUsages == null || conjunctTreatUsages.isEmpty() ) {
return null;
}
Predicate predicate = null;
for ( Map.Entry<SqmPath<?>, Set<String>> entry : conjunctTreatUsages.entrySet() ) {
predicate = SqlAstTreeHelper.combinePredicates(
predicate,
createTreatTypeRestriction( entry.getKey(), entry.getValue() )
);
}
conjunctTreatUsages.clear();
return predicate;
}
private Predicate createTreatTypeRestriction(SqmPath<?> lhs, EntityDomainType<?> treatTarget) {
final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
final EntityPersister entityDescriptor = domainModel.findEntityDescriptor( treatTarget.getHibernateEntityName() );
final Set<String> subclassEntityNames = entityDescriptor.getEntityMetamodel().getSubclassEntityNames();
final Expression typeExpression = (Expression) lhs.type().accept( this );
return createTreatTypeRestriction( lhs, subclassEntityNames );
}
private Predicate createTreatTypeRestriction(SqmPath<?> lhs, Set<String> subclassEntityNames) {
final MappingMetamodel domainModel = creationContext.getSessionFactory()
.getRuntimeMetamodels()
.getMappingMetamodel();
// Do what visitSelfInterpretingSqmPath does, except for calling preparingReusablePath
// as that would register a type usage for the table group that we don't want here
final DiscriminatorSqmPath discriminatorSqmPath = (DiscriminatorSqmPath) lhs.type();
registerTypeUsage( discriminatorSqmPath );
final Expression typeExpression = discriminatorSqmPath.interpret( this, this, jpaQueryComplianceEnabled );
if ( subclassEntityNames.size() == 1 ) {
return new ComparisonPredicate(
typeExpression,
ComparisonOperator.EQUAL,
new EntityTypeLiteral( entityDescriptor )
new EntityTypeLiteral( domainModel.findEntityDescriptor( subclassEntityNames.iterator().next() ) )
);
}
else {
@ -5200,7 +5256,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
Expression otherwise = null;
for ( SqmCaseSearched.WhenFragment<?> whenFragment : expression.getWhenFragments() ) {
inferrableTypeAccessStack.push( () -> null );
final Predicate whenPredicate = (Predicate) whenFragment.getPredicate().accept( this );
final Predicate whenPredicate = visitNestedTopLevelPredicate( whenFragment.getPredicate() );
inferrableTypeAccessStack.pop();
final MappingModelExpressible<?> alreadyKnown = resolved;
inferrableTypeAccessStack.push(
@ -5366,6 +5422,26 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Predicates
@Override
public Predicate visitNestedTopLevelPredicate(SqmPredicate predicate) {
final Map<SqmPath<?>, Set<String>> originalConjunctTableGroupTreatUsages;
if ( conjunctTreatUsages.isEmpty() ) {
originalConjunctTableGroupTreatUsages = null;
}
else {
originalConjunctTableGroupTreatUsages = new IdentityHashMap<>( conjunctTreatUsages );
}
conjunctTreatUsages.clear();
final Predicate result = (Predicate) predicate.accept( this );
final Predicate finalPredicate = SqlAstTreeHelper.combinePredicates(
result,
consumeConjunctTreatTypeRestrictions()
);
if ( originalConjunctTableGroupTreatUsages != null ) {
conjunctTreatUsages.putAll( originalConjunctTableGroupTreatUsages );
}
return finalPredicate;
}
@Override
public GroupedPredicate visitGroupedPredicate(SqmGroupedPredicate predicate) {
@ -5383,8 +5459,68 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public Junction visitOrPredicate(SqmOrPredicate predicate) {
final Junction disjunction = new Junction( Junction.Nature.DISJUNCTION, getBooleanType() );
disjunction.add( (Predicate) predicate.getLeftHandPredicate().accept( this ) );
disjunction.add( (Predicate) predicate.getRightHandPredicate().accept( this ) );
final Predicate predicate1 = (Predicate) predicate.getLeftHandPredicate().accept( this );
final Map<SqmPath<?>, Set<String>> conjunctTreatUsages1;
if ( conjunctTreatUsages.isEmpty() ) {
conjunctTreatUsages1 = null;
}
else {
conjunctTreatUsages1 = new IdentityHashMap<>( conjunctTreatUsages );
conjunctTreatUsages.clear();
}
final Predicate predicate2 = (Predicate) predicate.getRightHandPredicate().accept( this );
if ( conjunctTreatUsages.isEmpty() || conjunctTreatUsages1 == null ) {
disjunction.add(
SqlAstTreeHelper.combinePredicates(
consumeConjunctTreatTypeRestrictions( conjunctTreatUsages1 ),
predicate1
)
);
disjunction.add( predicate2 );
}
else {
// If both disjunctions have treat type restrictions build the intersection of the two,
// so that we can push that up and infer during pruning, which entity subclasses can be omitted
final Map<SqmPath<?>, Set<String>> conjunctTreatUsages2 = new IdentityHashMap<>( conjunctTreatUsages );
final Iterator<Map.Entry<SqmPath<?>, Set<String>>> iterator = conjunctTreatUsages.entrySet().iterator();
while ( iterator.hasNext() ) {
final Map.Entry<SqmPath<?>, Set<String>> entry = iterator.next();
final Set<String> entityNames1 = conjunctTreatUsages1.get( entry.getKey() );
if ( entityNames1 == null ) {
iterator.remove();
continue;
}
// Intersect the two sets and transfer the common elements to the intersection
final Set<String> entityNames2 = entry.getValue();
final Set<String> intersected = new HashSet<>( entityNames1 );
intersected.retainAll( entityNames2 );
if ( intersected.isEmpty() ) {
iterator.remove();
continue;
}
entry.setValue( intersected );
entityNames1.removeAll( intersected );
entityNames2.removeAll( intersected );
if ( entityNames1.isEmpty() ) {
conjunctTreatUsages1.remove( entry.getKey() );
}
if ( entityNames2.isEmpty() ) {
conjunctTreatUsages2.remove( entry.getKey() );
}
}
disjunction.add(
SqlAstTreeHelper.combinePredicates(
consumeConjunctTreatTypeRestrictions( conjunctTreatUsages1 ),
predicate1
)
);
disjunction.add(
SqlAstTreeHelper.combinePredicates(
consumeConjunctTreatTypeRestrictions( conjunctTreatUsages2 ),
predicate2
)
);
}
return disjunction;
}

View File

@ -12,6 +12,7 @@ import org.hibernate.LockMode;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.spi.BaseSemanticQueryWalker;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
@ -20,6 +21,7 @@ import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.predicate.Predicate;
/**
*
@ -77,4 +79,9 @@ public class FakeSqmToSqlAstConverter extends BaseSemanticQueryWalker implements
public List<Expression> expandSelfRenderingFunctionMultiValueParameter(SqmParameter<?> sqmParameter) {
return null;
}
@Override
public Predicate visitNestedTopLevelPredicate(SqmPredicate predicate) {
return (Predicate) predicate.accept( this );
}
}

View File

@ -11,9 +11,11 @@ import java.util.List;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.predicate.Predicate;
/**
* Specialized SemanticQueryWalker (SQM visitor) for producing SQL AST.
@ -25,4 +27,6 @@ public interface SqmToSqlAstConverter extends SemanticQueryWalker<Object>, SqlAs
List<Expression> expandSelfRenderingFunctionMultiValueParameter(SqmParameter<?> sqmParameter);
Predicate visitNestedTopLevelPredicate(SqmPredicate predicate);
}

View File

@ -3710,7 +3710,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
}
}
private boolean hasNestedTableGroupsToRender(List<TableGroupJoin> nestedTableGroupJoins) {
protected boolean hasNestedTableGroupsToRender(List<TableGroupJoin> nestedTableGroupJoins) {
for ( TableGroupJoin nestedTableGroupJoin : nestedTableGroupJoins ) {
final TableGroup joinedGroup = nestedTableGroupJoin.getJoinedGroup();
final TableGroup realTableGroup;

View File

@ -92,7 +92,12 @@ public class ColumnReference implements Expression, Assignable {
this.readExpression = this.columnExpression;
}
else if ( customReadExpression != null ) {
this.readExpression = StringHelper.replace( customReadExpression, Template.TEMPLATE, qualifier );
if ( this.qualifier == null ) {
this.readExpression = StringHelper.replace( customReadExpression, Template.TEMPLATE + ".", "" );
}
else {
this.readExpression = StringHelper.replace( customReadExpression, Template.TEMPLATE, qualifier );
}
}
else {
this.readExpression = this.qualifier == null
@ -104,7 +109,12 @@ public class ColumnReference implements Expression, Assignable {
this.writeExpression = null;
}
else if ( customWriteExpression != null ) {
this.writeExpression = StringHelper.replace( customWriteExpression, Template.TEMPLATE, qualifier );
if ( this.qualifier == null ) {
this.writeExpression = StringHelper.replace( customWriteExpression, Template.TEMPLATE + ".", "" );
}
else {
this.writeExpression = StringHelper.replace( customWriteExpression, Template.TEMPLATE, qualifier );
}
}
else {
this.writeExpression = DEFAULT_COLUMN_WRITE_EXPRESSION;

View File

@ -124,6 +124,8 @@ public class TreatKeywordTest extends BaseCoreFunctionalTestCase {
assertEquals( 2, result.size() );
result = s.createQuery( "select treat (e as JoinedEntitySubclass) from JoinedEntity e" ).list();
assertEquals( 1, result.size() );
result = s.createQuery( "select e from JoinedEntity e where treat (e as JoinedEntitySubclass) is not null" ).list();
assertEquals( 1, result.size() );
result = s.createQuery( "select treat (e as JoinedEntitySubSubclass) from JoinedEntity e" ).list();
assertEquals( 0, result.size() );