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:
parent
65e282766c
commit
0a5b62421e
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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() ) {
|
||||
|
|
|
@ -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 )
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() );
|
||||
|
||||
|
|
Loading…
Reference in New Issue