From a9080f5f7da83b506e1a9a1eb3c0aeacafb1734e Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 6 Apr 2023 14:18:21 +0200 Subject: [PATCH] HHH-16392 Fix where clause in collection cleanup subqueries --- .../userguide/appendices/Annotations.adoc | 4 +- .../hibernate/annotations/SQLRestriction.java | 2 +- .../java/org/hibernate/annotations/Where.java | 2 +- .../sqm/internal/SimpleDeleteQueryPlan.java | 10 ++- .../internal/SqmMutationStrategyHelper.java | 89 +++++++++++++++++++ 5 files changed, 100 insertions(+), 7 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index 28ed4d93b7..15d3ad68ad 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -1427,9 +1427,9 @@ See the <> section for more info. +See the <> section for more info. [[annotations-hibernate-wherejointable]] ==== `@SQLJoinTableRestriction` diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/SQLRestriction.java b/hibernate-core/src/main/java/org/hibernate/annotations/SQLRestriction.java index dc03c6af4e..0fcd90a055 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/SQLRestriction.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/SQLRestriction.java @@ -16,7 +16,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Specifies a restriction written in native SQL to add to the generated - * SQL when querying an entity or collection. + * SQL for entities or collections. *

* For example, {@code @SQLRestriction} could be used to hide entity * instances which have been soft-deleted, either for the entity class diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Where.java b/hibernate-core/src/main/java/org/hibernate/annotations/Where.java index 73243cbe7d..f798986252 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Where.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Where.java @@ -16,7 +16,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Specifies a restriction written in native SQL to add to the generated - * SQL when querying an entity or collection. + * SQL for entities or collections. *

* For example, {@code @Where} could be used to hide entity instances which * have been soft-deleted, either for the entity class itself: diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java index fe12e52967..ec7ed955f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java @@ -124,8 +124,7 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan { jdbcDelete = deleteTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); } - final boolean missingRestriction = sqmDelete.getWhereClause() == null - || sqmDelete.getWhereClause().getPredicate() == null; + final boolean missingRestriction = sqmInterpretation.getSqlAst().getRestriction() == null; if ( missingRestriction ) { assert domainParameterXref.getSqmParameterCount() == 0; assert jdbcParamsXref.isEmpty(); @@ -171,7 +170,12 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan { tableGroup ); - matchingIdSubQuery.applyPredicate( sqmInterpretation.getSqlAst().getRestriction() ); + matchingIdSubQuery.applyPredicate( SqmMutationStrategyHelper.getIdSubqueryPredicate( + sqmInterpretation.getSqlAst().getRestriction(), + entityDescriptor, + tableGroup, + session + ) ); return new InSubQueryPredicate( fkColumnExpression, matchingIdSubQuery, false ); }, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java index 6ad9f6d8ca..d53bd21fa7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/SqmMutationStrategyHelper.java @@ -6,17 +6,24 @@ */ package org.hibernate.query.sqm.mutation.internal; +import java.util.ArrayList; +import java.util.List; import java.util.function.BiFunction; import java.util.function.Consumer; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; +import org.hibernate.persister.internal.SqlFragmentPredicate; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.from.NamedTableReference; +import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.FilterPredicate; +import org.hibernate.sql.ast.tree.predicate.Junction; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameterBindings; @@ -179,4 +186,86 @@ public class SqmMutationStrategyHelper { ); } } + + /** + * Translates the original delete predicate to be used in the id subquery + * forcing the use of the table alias qualifier + */ + public static Predicate getIdSubqueryPredicate( + Predicate predicate, + EntityMappingType entityDescriptor, + TableGroup tableGroup, + SharedSessionContractImplementor session) { + if ( predicate instanceof FilterPredicate || predicate instanceof SqlFragmentPredicate ) { + return getBaseRestrictions( entityDescriptor, tableGroup, session ).get( 0 ); + } + else if ( predicate instanceof Junction ) { + final Junction original = (Junction) predicate; + if ( original.getPredicates().size() > 1 ) { + final Junction junction = new Junction( + original.getNature(), + original.getExpressionType() + ); + junction.getPredicates().addAll( original.getPredicates() ); + final Predicate secondToLastPredicate = junction.getPredicates().get( junction.getPredicates().size() - 2 ); + final Predicate lastPredicate = junction.getPredicates().get( junction.getPredicates().size() - 1 ); + int filterPredicateIndex = -1; + int fragmentPredicateIndex = -1; + if ( lastPredicate instanceof Junction ) { + // If the mutation query specified an explicit where condition and there are multiple base + // restrictions they will be in a nested Junction predicate, so we need to process that one + final Predicate baseRestrictions = getIdSubqueryPredicate( + lastPredicate, + entityDescriptor, + tableGroup, + session + ); + junction.getPredicates().set( junction.getPredicates().size() - 1, baseRestrictions ); + predicate = junction; + } + else if ( secondToLastPredicate instanceof FilterPredicate ) { + filterPredicateIndex = junction.getPredicates().size() - 2; + fragmentPredicateIndex = filterPredicateIndex + 1; + } + else if ( lastPredicate instanceof FilterPredicate ) { + filterPredicateIndex = junction.getPredicates().size() - 1; + } + else if ( lastPredicate instanceof SqlFragmentPredicate ) { + fragmentPredicateIndex = junction.getPredicates().size() - 1; + } + if ( filterPredicateIndex != -1 || fragmentPredicateIndex != -1 ) { + final List baseRestrictions = getBaseRestrictions( + entityDescriptor, + tableGroup, + session + ); + int index = 0; + if ( filterPredicateIndex != -1 ) { + junction.getPredicates().set( filterPredicateIndex, baseRestrictions.get( index++ ) ); + } + if ( fragmentPredicateIndex != -1 ) { + junction.getPredicates().set( fragmentPredicateIndex, baseRestrictions.get( index ) ); + } + predicate = junction; + } + } + } + return predicate; + } + + private static List getBaseRestrictions( + EntityMappingType entityDescriptor, + TableGroup tableGroup, + SharedSessionContractImplementor session) { + final List baseRestrictions = new ArrayList<>( 2 ); + entityDescriptor.applyBaseRestrictions( + baseRestrictions::add, + tableGroup, + true, + session.getLoadQueryInfluencers().getEnabledFilters(), + null, + null + ); + return baseRestrictions; + } }