HHH-16392 Fix where clause in collection cleanup subqueries

This commit is contained in:
Marco Belladelli 2023-04-06 14:18:21 +02:00
parent 28d1781d47
commit 094f243413
No known key found for this signature in database
GPG Key ID: D1D0C3030AE3AA35
6 changed files with 108 additions and 15 deletions

View File

@ -1427,7 +1427,7 @@ See the <<chapters/domain/basic_types.adoc#mapping-database-generated-value-exam
[[annotations-hibernate-where]]
==== `@Where`
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Where.html[`@Where`] annotation is used to specify a custom SQL `WHERE` clause used when fetching an entity or a collection.
The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/Where.html[`@Where`] annotation is used to specify a custom SQL `WHERE` clause used to filter out entities or collections.
See the <<chapters/pc/PersistenceContext.adoc#pc-where-example,`@Where` mapping>> section for more info.

View File

@ -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.
* <p>
* For example, {@code @Where} could be used to hide entity instances which
* have been soft-deleted, either for the entity class itself:

View File

@ -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 );
},

View File

@ -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<Predicate> 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<Predicate> getBaseRestrictions(
EntityMappingType entityDescriptor,
TableGroup tableGroup,
SharedSessionContractImplementor session) {
final List<Predicate> baseRestrictions = new ArrayList<>( 2 );
entityDescriptor.applyBaseRestrictions(
baseRestrictions::add,
tableGroup,
true,
session.getLoadQueryInfluencers().getEnabledFilters(),
null,
null
);
return baseRestrictions;
}
}

View File

@ -10,7 +10,7 @@ import java.util.List;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.SQLRestriction;
import org.hibernate.annotations.Where;
import org.hibernate.query.MutationQuery;
import org.hibernate.testing.jdbc.SQLStatementInspector;
@ -36,7 +36,7 @@ import static java.util.Arrays.stream;
/**
* Same as {@link MutationQueriesWhereTest} and {@link MutationQueriesFilterTest},
* but using both {@link SQLRestriction @SQLRestriction} and {@link Filter @Filter}
* but using both {@link Where @Where} and {@link Filter @Filter}
*
* @author Marco Belladelli
*/
@ -176,7 +176,7 @@ public class MutationQueriesWhereAndFilterTest {
}
@Entity( name = "UserEntity" )
@SQLRestriction( "where_deleted = false" )
@Where( clause = "where_deleted = false" )
@FilterDef( name = "deleted_filter", defaultCondition = "filter_deleted = false" )
@Filter( name = "deleted_filter" )
public static class UserEntity {
@ -199,7 +199,7 @@ public class MutationQueriesWhereAndFilterTest {
@Entity( name = "DiscriminatorBase" )
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
@DiscriminatorColumn( name = "disc_col" )
@SQLRestriction( "where_deleted = false" )
@Where( clause = "where_deleted = false" )
@Filter( name = "deleted_filter" )
public static class DiscriminatorBase {
@Id
@ -225,7 +225,7 @@ public class MutationQueriesWhereAndFilterTest {
@Entity( name = "TablePerClassBase" )
@Inheritance( strategy = InheritanceType.TABLE_PER_CLASS )
@SQLRestriction( "where_deleted = false" )
@Where( clause = "where_deleted = false" )
@Filter( name = "deleted_filter" )
public static abstract class TablePerClassBase {
@Id

View File

@ -8,7 +8,7 @@ package org.hibernate.orm.test.query.mutationquery;
import java.util.List;
import org.hibernate.annotations.SQLRestriction;
import org.hibernate.annotations.Where;
import org.hibernate.query.MutationQuery;
import org.hibernate.testing.jdbc.SQLStatementInspector;
@ -34,7 +34,7 @@ import static java.util.Arrays.stream;
/**
* Same as {@link MutationQueriesFilterTest} and {@link MutationQueriesWhereAndFilterTest},
* but using only {@link SQLRestriction @SQLRestriction}
* but using only {@link Where @Where}
*
* @author Marco Belladelli
*/
@ -164,7 +164,7 @@ public class MutationQueriesWhereTest {
}
@Entity( name = "UserEntity" )
@SQLRestriction( "deleted = false" )
@Where( clause = "deleted = false" )
public static class UserEntity {
@Id
private Long id;
@ -183,7 +183,7 @@ public class MutationQueriesWhereTest {
@Entity( name = "DiscriminatorBase" )
@Inheritance( strategy = InheritanceType.SINGLE_TABLE )
@DiscriminatorColumn( name = "disc_col" )
@SQLRestriction( "deleted = false" )
@Where( clause = "deleted = false" )
public static class DiscriminatorBase {
@Id
private Long id;
@ -206,7 +206,7 @@ public class MutationQueriesWhereTest {
@Entity( name = "TablePerClassBase" )
@Inheritance( strategy = InheritanceType.TABLE_PER_CLASS )
@SQLRestriction( "deleted = false" )
@Where( clause = "deleted = false" )
public static abstract class TablePerClassBase {
@Id
private Long id;