diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java index a2a02d89c674..18862ede8e60 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java @@ -1558,9 +1558,9 @@ else if ( modelPart instanceof VirtualModelPart ) { } public static Expression buildColumnReferenceExpression( - TableGroup tableGroup, + @Nullable TableGroup tableGroup, ModelPart modelPart, - SqlExpressionResolver sqlExpressionResolver, + @Nullable SqlExpressionResolver sqlExpressionResolver, SessionFactoryImplementor sessionFactory) { final int jdbcTypeCount = modelPart.getJdbcTypeCount(); if ( modelPart instanceof EmbeddableValuedModelPart ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractMultiTableMutationQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractMultiTableMutationQueryPlan.java new file mode 100644 index 000000000000..5485ea312640 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractMultiTableMutationQueryPlan.java @@ -0,0 +1,123 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.sqm.internal; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.NonSelectQueryPlan; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.tree.SqmDmlStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + + +/** + * @since 7.1 + */ +public abstract class AbstractMultiTableMutationQueryPlan, F> implements NonSelectQueryPlan { + private final S statement; + private final DomainParameterXref domainParameterXref; + private final F strategy; + + private volatile MultiTableHandler handler; + + public AbstractMultiTableMutationQueryPlan(S statement, DomainParameterXref domainParameterXref, F strategy) { + this.statement = statement; + this.domainParameterXref = domainParameterXref; + this.strategy = strategy; + } + + protected abstract MultiTableHandlerBuildResult buildHandler( + S statement, + DomainParameterXref domainParameterXref, + F strategy, + DomainQueryExecutionContext context); + + @Override + public int executeUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), statement ); + final Interpretation interpretation = getInterpretation( context ); + return interpretation.handler().execute( interpretation.jdbcParameterBindings(), context ); + } + + // For Hibernate Reactive + protected S getStatement() { + return statement; + } + + // For Hibernate Reactive + protected Interpretation getInterpretation(DomainQueryExecutionContext context) { + Interpretation builtInterpretation = null; + MultiTableHandler localCopy = handler; + + if ( localCopy == null ) { + synchronized (this) { + localCopy = handler; + if ( localCopy == null ) { + final MultiTableHandlerBuildResult buildResult = buildHandler( + statement, + domainParameterXref, + strategy, + context + ); + builtInterpretation = new Interpretation( + buildResult.multiTableHandler(), + buildResult.firstJdbcParameterBindings() + ); + localCopy = buildResult.multiTableHandler(); + handler = localCopy; + } + else { + builtInterpretation = updateInterpretation( localCopy, context ); + } + } + } + else { + builtInterpretation = updateInterpretation( localCopy, context ); + } + return builtInterpretation != null ? builtInterpretation + : new Interpretation( localCopy, localCopy.createJdbcParameterBindings( context ) ); + } + + private @Nullable Interpretation updateInterpretation( + MultiTableHandler localCopy, + DomainQueryExecutionContext context) { + Interpretation builtInterpretation = null; + + // If the translation depends on parameter bindings or it isn't compatible with the current query options, + // we have to rebuild the JdbcSelect, which is still better than having to translate from SQM to SQL AST again + if ( localCopy.dependsOnParameterBindings() ) { + final JdbcParameterBindings jdbcParameterBindings = localCopy.createJdbcParameterBindings( context ); + // If the translation depends on the limit or lock options, we have to rebuild the JdbcSelect + // We could avoid this by putting the lock options into the cache key + if ( !localCopy.isCompatibleWith( jdbcParameterBindings, context.getQueryOptions() ) ) { + final MultiTableHandlerBuildResult buildResult = buildHandler( + statement, + domainParameterXref, + strategy, + context + ); + localCopy = buildResult.multiTableHandler(); + builtInterpretation = new Interpretation( + buildResult.multiTableHandler(), + buildResult.firstJdbcParameterBindings() + ); + handler = localCopy; + } + else { + builtInterpretation = new Interpretation( localCopy, jdbcParameterBindings ); + } + } + return builtInterpretation; + } + + // For Hibernate Reactive + protected record Interpretation( + MultiTableHandler handler, + JdbcParameterBindings jdbcParameterBindings + ) {} + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/CacheableSqmInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/CacheableSqmInterpretation.java new file mode 100644 index 000000000000..25a94e195a00 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/CacheableSqmInterpretation.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.sqm.internal; + +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.exec.spi.JdbcOperationQuery; +import org.hibernate.sql.exec.spi.JdbcParametersList; + +import java.util.List; +import java.util.Map; + +/** + * @since 7.1 + */ +public record CacheableSqmInterpretation( + S statement, + J jdbcOperation, + Map, Map, List>> jdbcParamsXref, + Map, MappingModelExpressible> sqmParameterMappingModelTypes) { + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java index 1fdd79ec3585..d9b57879cd9e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java @@ -18,12 +18,12 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.internal.EmptyScrollableResults; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.query.QueryTypeMismatchException; import org.hibernate.query.TupleTransformer; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; @@ -79,7 +79,7 @@ public class ConcreteSqmSelectQueryPlan implements SelectQueryPlan { private final SqmInterpreter, Void> listInterpreter; private final SqmInterpreter, ScrollMode> scrollInterpreter; - private volatile CacheableSqmInterpretation cacheableSqmInterpretation; + private volatile CacheableSqmInterpretation cacheableSqmInterpretation; public ConcreteSqmSelectQueryPlan( SqmSelectStatement sqm, @@ -97,16 +97,16 @@ public ConcreteSqmSelectQueryPlan( : ListResultsConsumer.UniqueSemantic.ALLOW; this.executeQueryInterpreter = (resultsConsumer, executionContext, sqmInterpretation, jdbcParameterBindings) -> { final SharedSessionContractImplementor session = executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); try { final SubselectFetch.RegistrationHandler subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), - sqmInterpretation.selectStatement, + sqmInterpretation.statement(), JdbcParametersList.empty(), jdbcParameterBindings ); session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true ); - final Expression fetchExpression = sqmInterpretation.selectStatement.getQueryPart() + final Expression fetchExpression = sqmInterpretation.statement().getQueryPart() .getFetchClauseExpression(); final int resultCountEstimate = fetchExpression != null ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) @@ -127,17 +127,17 @@ public ConcreteSqmSelectQueryPlan( }; this.listInterpreter = (unused, executionContext, sqmInterpretation, jdbcParameterBindings) -> { final SharedSessionContractImplementor session = executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); try { final SubselectFetch.RegistrationHandler subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), - sqmInterpretation.selectStatement, + sqmInterpretation.statement(), JdbcParametersList.empty(), jdbcParameterBindings ); session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true ); final Expression fetchExpression = - sqmInterpretation.selectStatement.getQueryPart() + sqmInterpretation.statement().getQueryPart() .getFetchClauseExpression(); final int resultCountEstimate = fetchExpression != null ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) @@ -160,7 +160,7 @@ public ConcreteSqmSelectQueryPlan( this.scrollInterpreter = (scrollMode, executionContext, sqmInterpretation, jdbcParameterBindings) -> { final SharedSessionContractImplementor session = executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); try { // final SubselectFetch.RegistrationHandler subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( // executionContext.getSession().getPersistenceContext().getBatchFetchQueue(), @@ -173,7 +173,7 @@ public ConcreteSqmSelectQueryPlan( session.getFactory().getJdbcServices().getJdbcSelectExecutor(); session.autoFlushIfRequired( jdbcSelect.getAffectedTableNames(), true ); final Expression fetchExpression = - sqmInterpretation.selectStatement.getQueryPart() + sqmInterpretation.statement().getQueryPart() .getFetchClauseExpression(); final int resultCountEstimate = fetchExpression != null ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) @@ -401,7 +401,7 @@ private T withCacheableSqmInterpretation(DomainQueryExecutionContext exec // to protect access. However, synchronized is much simpler here. We will verify // during throughput testing whether this is an issue and consider changes then - CacheableSqmInterpretation localCopy = cacheableSqmInterpretation; + CacheableSqmInterpretation localCopy = cacheableSqmInterpretation; JdbcParameterBindings jdbcParameterBindings = null; executionContext.getSession().autoPreFlush(); @@ -410,23 +410,23 @@ private T withCacheableSqmInterpretation(DomainQueryExecutionContext exec synchronized ( this ) { localCopy = cacheableSqmInterpretation; if ( localCopy == null ) { - localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext ); - jdbcParameterBindings = localCopy.firstParameterBindings; - localCopy.firstParameterBindings = null; + final MutableObject mutableValue = new MutableObject<>(); + localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue ); + jdbcParameterBindings = mutableValue.get(); cacheableSqmInterpretation = localCopy; } else { // If the translation depends on parameter bindings or it isn't compatible with the current query options, // we have to rebuild the JdbcSelect, which is still better than having to translate from SQM to SQL AST again - if ( localCopy.jdbcSelect.dependsOnParameterBindings() ) { + if ( localCopy.jdbcOperation().dependsOnParameterBindings() ) { jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext ); } // If the translation depends on the limit or lock options, we have to rebuild the JdbcSelect // We could avoid this by putting the lock options into the cache key - if ( !localCopy.jdbcSelect.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext ); - jdbcParameterBindings = localCopy.firstParameterBindings; - localCopy.firstParameterBindings = null; + if ( !localCopy.jdbcOperation().isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { + final MutableObject mutableValue = new MutableObject<>(); + localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue ); + jdbcParameterBindings = mutableValue.get(); cacheableSqmInterpretation = localCopy; } } @@ -435,15 +435,15 @@ private T withCacheableSqmInterpretation(DomainQueryExecutionContext exec else { // If the translation depends on parameter bindings or it isn't compatible with the current query options, // we have to rebuild the JdbcSelect, which is still better than having to translate from SQM to SQL AST again - if ( localCopy.jdbcSelect.dependsOnParameterBindings() ) { + if ( localCopy.jdbcOperation().dependsOnParameterBindings() ) { jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext ); } // If the translation depends on the limit or lock options, we have to rebuild the JdbcSelect // We could avoid this by putting the lock options into the cache key - if ( !localCopy.jdbcSelect.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext ); - jdbcParameterBindings = localCopy.firstParameterBindings; - localCopy.firstParameterBindings = null; + if ( !localCopy.jdbcOperation().isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { + final MutableObject mutableValue = new MutableObject<>(); + localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue ); + jdbcParameterBindings = mutableValue.get(); cacheableSqmInterpretation = localCopy; } } @@ -455,26 +455,29 @@ private T withCacheableSqmInterpretation(DomainQueryExecutionContext exec return interpreter.interpret( context, executionContext, localCopy, jdbcParameterBindings ); } - private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) { + // For Hibernate Reactive + protected JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) { return SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), domainParameterXref, - sqmInterpretation.getJdbcParamsXref(), + sqmInterpretation.jdbcParamsXref(), new SqmParameterMappingModelResolutionAccess() { //this is pretty ugly! @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypes().get(parameter); + return (MappingModelExpressible) sqmInterpretation.sqmParameterMappingModelTypes().get(parameter); } }, executionContext.getSession() ); } - private static CacheableSqmInterpretation buildCacheableSqmInterpretation( + // For Hibernate Reactive + protected static CacheableSqmInterpretation buildInterpretation( SqmSelectStatement sqm, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext executionContext) { + DomainQueryExecutionContext executionContext, + MutableObject firstJdbcParameterBindingsConsumer) { final SharedSessionContractImplementor session = executionContext.getSession(); final SessionFactoryImplementor sessionFactory = session.getFactory(); @@ -511,13 +514,12 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter( sqmInterpretation.getSqlAst(), selectTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ), jdbcParamsXref, - sqmInterpretation.getSqmParameterMappingModelTypeResolutions(), - jdbcParameterBindings + sqmInterpretation.getSqmParameterMappingModelTypeResolutions() ); } @@ -525,43 +527,10 @@ private interface SqmInterpreter { T interpret( X context, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings); } - private static class CacheableSqmInterpretation { - private final SelectStatement selectStatement; - private final JdbcOperationQuerySelect jdbcSelect; - private final Map, Map, List>> jdbcParamsXref; - private final Map, MappingModelExpressible> sqmParameterMappingModelTypes; - private transient JdbcParameterBindings firstParameterBindings; - - CacheableSqmInterpretation( - SelectStatement selectStatement, - JdbcOperationQuerySelect jdbcSelect, - Map, Map, List>> jdbcParamsXref, - Map, MappingModelExpressible> sqmParameterMappingModelTypes, - JdbcParameterBindings firstParameterBindings) { - this.selectStatement = selectStatement; - this.jdbcSelect = jdbcSelect; - this.jdbcParamsXref = jdbcParamsXref; - this.sqmParameterMappingModelTypes = sqmParameterMappingModelTypes; - this.firstParameterBindings = firstParameterBindings; - } - - JdbcOperationQuerySelect getJdbcSelect() { - return jdbcSelect; - } - - Map, Map, List>> getJdbcParamsXref() { - return jdbcParamsXref; - } - - public Map, MappingModelExpressible> getSqmParameterMappingModelTypes() { - return sqmParameterMappingModelTypes; - } - } - private static class MySqmJdbcExecutionContextAdapter extends SqmJdbcExecutionContextAdapter { private final SubselectFetch.RegistrationHandler subSelectFetchKeyHandler; private final String hql; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableDeleteQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableDeleteQueryPlan.java index e1b57352f065..977b04bb04c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableDeleteQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableDeleteQueryPlan.java @@ -4,32 +4,29 @@ */ package org.hibernate.query.sqm.internal; -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.NonSelectQueryPlan; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; /** * @author Steve Ebersole */ -public class MultiTableDeleteQueryPlan implements NonSelectQueryPlan { - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; - private final SqmMultiTableMutationStrategy deleteStrategy; +public class MultiTableDeleteQueryPlan extends AbstractMultiTableMutationQueryPlan, SqmMultiTableMutationStrategy> { public MultiTableDeleteQueryPlan( - SqmDeleteStatement sqmDelete, + SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, SqmMultiTableMutationStrategy deleteStrategy) { - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; - this.deleteStrategy = deleteStrategy; + super( sqmDelete, domainParameterXref, deleteStrategy ); } @Override - public int executeUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete ); - return deleteStrategy.executeDelete( sqmDelete, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmDeleteStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableInsertQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableInsertQueryPlan.java index 5d8bcb1d1b91..bdbdd3b592ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableInsertQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableInsertQueryPlan.java @@ -4,32 +4,29 @@ */ package org.hibernate.query.sqm.internal; -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.NonSelectQueryPlan; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; /** * @author Christian Beikov */ -public class MultiTableInsertQueryPlan implements NonSelectQueryPlan { - private final SqmInsertStatement sqmInsert; - private final DomainParameterXref domainParameterXref; - private final SqmMultiTableInsertStrategy mutationStrategy; +public class MultiTableInsertQueryPlan extends AbstractMultiTableMutationQueryPlan, SqmMultiTableInsertStrategy> { public MultiTableInsertQueryPlan( SqmInsertStatement sqmInsert, DomainParameterXref domainParameterXref, SqmMultiTableInsertStrategy mutationStrategy) { - this.sqmInsert = sqmInsert; - this.domainParameterXref = domainParameterXref; - this.mutationStrategy = mutationStrategy; + super( sqmInsert, domainParameterXref, mutationStrategy ); } @Override - public int executeUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmInsert ); - return mutationStrategy.executeInsert( sqmInsert, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmInsertStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableInsertStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableUpdateQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableUpdateQueryPlan.java index ce6846aeaf0b..50d16147dc20 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableUpdateQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/MultiTableUpdateQueryPlan.java @@ -4,32 +4,29 @@ */ package org.hibernate.query.sqm.internal; -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.NonSelectQueryPlan; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; /** * @author Steve Ebersole */ -public class MultiTableUpdateQueryPlan implements NonSelectQueryPlan { - private final SqmUpdateStatement sqmUpdate; - private final DomainParameterXref domainParameterXref; - private final SqmMultiTableMutationStrategy mutationStrategy; +public class MultiTableUpdateQueryPlan extends AbstractMultiTableMutationQueryPlan, SqmMultiTableMutationStrategy> { public MultiTableUpdateQueryPlan( - SqmUpdateStatement sqmUpdate, + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, SqmMultiTableMutationStrategy mutationStrategy) { - this.sqmUpdate = sqmUpdate; - this.domainParameterXref = domainParameterXref; - this.mutationStrategy = mutationStrategy; + super( sqmUpdate, domainParameterXref, mutationStrategy ); } @Override - public int executeUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmUpdate ); - return mutationStrategy.executeUpdate( sqmUpdate, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmUpdateStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } 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 b2770075706e..ea8ce22e7bbe 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 @@ -4,28 +4,20 @@ */ package org.hibernate.query.sqm.internal; -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.dialect.DmlTargetColumnQualifierSupport; -import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.MutableObject; -import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.SoftDeleteMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.NonSelectQueryPlan; -import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.sql.SqmTranslator; +import org.hibernate.query.sqm.sql.StandardSqmTranslation; +import org.hibernate.query.sqm.tree.SqmDmlStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; import org.hibernate.sql.ast.tree.MutationStatement; import org.hibernate.sql.ast.tree.expression.Expression; @@ -37,86 +29,46 @@ import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcMutationExecutor; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.SqlSelectionImpl; +import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; /** * @author Steve Ebersole */ -public class SimpleDeleteQueryPlan implements NonSelectQueryPlan { - private final EntityMappingType entityDescriptor; - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; +public class SimpleDeleteQueryPlan extends SimpleNonSelectQueryPlan { - private JdbcOperationQueryMutation jdbcOperation; + private final EntityPersister entityDescriptor; - private SqmTranslation sqmInterpretation; - private Map, Map, List>> jdbcParamsXref; + private volatile List collectionTableDeletes; public SimpleDeleteQueryPlan( - EntityMappingType entityDescriptor, + EntityPersister entityDescriptor, SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref) { - assert entityDescriptor.getEntityName().equals( sqmDelete.getTarget().getEntityName() ); - + super( sqmDelete, domainParameterXref ); this.entityDescriptor = entityDescriptor; - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; - } - - public EntityMappingType getEntityDescriptor() { - return entityDescriptor; } @Override - public int executeUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete ); - - final SharedSessionContractImplementor session = executionContext.getSession(); - final SessionFactoryImplementor factory = session.getFactory(); - final JdbcServices jdbcServices = factory.getJdbcServices(); - SqlAstTranslator sqlAstTranslator = null; - if ( jdbcOperation == null ) { - sqlAstTranslator = createTranslator( executionContext ); - } - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter); - } - }, - session - ); - - if ( jdbcOperation != null - && ! jdbcOperation.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - sqlAstTranslator = createTranslator( executionContext ); - } - - if ( sqlAstTranslator != null ) { - jdbcOperation = sqlAstTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - final boolean missingRestriction = sqmInterpretation.getSqlAst().getRestriction() == null; - if ( missingRestriction ) { - assert domainParameterXref.getSqmParameterCount() == 0; - assert jdbcParamsXref.isEmpty(); - } - - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.usingLockingAndPaging( executionContext ); - - SqmMutationStrategyHelper.cleanUpCollectionTables( + protected Interpretation buildInterpretation( + SqmDmlStatement sqm, + DomainParameterXref domainParameterXref, + DomainQueryExecutionContext context) { + final Interpretation sqmInterpretation = + super.buildInterpretation( sqm, domainParameterXref, context ); + + final SessionFactoryImplementor factory = context.getSession().getFactory(); + final AbstractUpdateOrDeleteStatement statement = + (AbstractUpdateOrDeleteStatement) sqmInterpretation.interpretation().statement(); + final ArrayList collectionTableDeletes = new ArrayList<>(); + SqmMutationStrategyHelper.visitCollectionTableDeletes( entityDescriptor, (tableReference, attributeMapping) -> { final TableGroup collectionTableGroup = new MutatingTableReferenceGroupWrapper( @@ -130,13 +82,13 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter additionalPredicate.set( Predicate.combinePredicates( additionalPredicate.get(), p ) ), collectionTableGroup, factory.getJdbcServices().getDialect().getDmlTargetColumnQualifierSupport() == DmlTargetColumnQualifierSupport.TABLE_ALIAS, - executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), + context.getSession().getLoadQueryInfluencers().getEnabledFilters(), false, null, null ); - if ( missingRestriction ) { + if ( statement.getRestriction() == null ) { return additionalPredicate.get(); } @@ -153,86 +105,85 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter session - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContextAdapter - ); + // For Hibernate Reactive + protected List getCollectionTableDeletes() { + return collectionTableDeletes; } - protected SqlAstTranslator createTranslator(DomainQueryExecutionContext executionContext) { + @Override + protected int execute(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final SqmTranslator translator = - factory.getQueryEngine().getSqmTranslatorFactory().createMutationTranslator( - sqmDelete, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ); - //noinspection unchecked - sqmInterpretation = (SqmTranslation) translator.translate(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - final MutationStatement ast = createDeleteAst(); - return factory.getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, ast ); + final JdbcMutationExecutor jdbcMutationExecutor = factory.getJdbcServices().getJdbcMutationExecutor(); + for ( JdbcOperationQueryMutation delete : collectionTableDeletes ) { + jdbcMutationExecutor.execute( + delete, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + return super.execute( sqmInterpretation, jdbcParameterBindings, executionContext ); } - private MutationStatement createDeleteAst() { - final MutationStatement ast; - if ( entityDescriptor.getSoftDeleteMapping() == null ) { - ast = sqmInterpretation.getSqlAst(); + @Override + protected SqmTranslation buildTranslation(SqmDmlStatement sqm, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext) { + final SqmTranslation sqmTranslation = + super.buildTranslation( sqm, domainParameterXref, executionContext ); + + final SoftDeleteMapping columnMapping = entityDescriptor.getSoftDeleteMapping(); + if ( columnMapping == null ) { + return sqmTranslation; } else { - final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); + final AbstractUpdateOrDeleteStatement sqlDeleteAst = + (AbstractUpdateOrDeleteStatement) sqmTranslation.getSqlAst(); final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); - final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); final Assignment assignment = columnMapping.createSoftDeleteAssignment( targetTable ); - ast = new UpdateStatement( - targetTable, - Collections.singletonList( assignment ), - sqlDeleteAst.getRestriction() + return new StandardSqmTranslation<>( + new UpdateStatement( + sqlDeleteAst, + targetTable, + sqlDeleteAst.getFromClause(), + Collections.singletonList( assignment ), + sqlDeleteAst.getRestriction(), + sqlDeleteAst.getReturningColumns() + ), + sqmTranslation.getJdbcParamsBySqmParam(), + sqmTranslation.getSqmParameterMappingModelTypeResolutions(), + sqmTranslation.getSqlExpressionResolver(), + sqmTranslation.getFromClauseAccess() ); } - return ast; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java deleted file mode 100644 index 0371196a46aa..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.query.sqm.internal; - -import java.util.List; -import java.util.Map; - -import org.hibernate.action.internal.BulkOperationCleanupAction; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.NonSelectQueryPlan; -import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcParametersList; - -/** - * @author Gavin King - */ -public class SimpleInsertQueryPlan implements NonSelectQueryPlan { - private final SqmInsertStatement sqmInsert; - private final DomainParameterXref domainParameterXref; - private Map, MappingModelExpressible> paramTypeResolutions; - - private JdbcOperationQueryMutation jdbcInsert; - private Map, Map, List>> jdbcParamsXref; - - public SimpleInsertQueryPlan( - SqmInsertStatement sqmInsert, - DomainParameterXref domainParameterXref) { - this.sqmInsert = sqmInsert; - this.domainParameterXref = domainParameterXref; - } - - private SqlAstTranslator createInsertTranslator(DomainQueryExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - - final SqmTranslation sqmInterpretation = - factory.getQueryEngine().getSqmTranslatorFactory() - .createMutationTranslator( - sqmInsert, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ) - .translate(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - this.paramTypeResolutions = sqmInterpretation.getSqmParameterMappingModelTypeResolutions(); - - return factory.getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqmInterpretation.getSqlAst() ); - } - - @Override - public int executeUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmInsert ); - final SharedSessionContractImplementor session = executionContext.getSession(); - final SessionFactoryImplementor factory = session.getFactory(); - final JdbcServices jdbcServices = factory.getJdbcServices(); - SqlAstTranslator insertTranslator = null; - if ( jdbcInsert == null ) { - insertTranslator = createInsertTranslator( executionContext ); - } - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - session - ); - - if ( jdbcInsert != null && !jdbcInsert.isCompatibleWith( - jdbcParameterBindings, - executionContext.getQueryOptions() - ) ) { - insertTranslator = createInsertTranslator( executionContext ); - } - - if ( insertTranslator != null ) { - jdbcInsert = insertTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - return jdbcServices.getJdbcMutationExecutor().execute( - jdbcInsert, - jdbcParameterBindings, - sql -> session - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ) - ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleNonSelectQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleNonSelectQueryPlan.java new file mode 100644 index 000000000000..687628d732b4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleNonSelectQueryPlan.java @@ -0,0 +1,223 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.sqm.internal; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.NonSelectQueryPlan; +import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; +import org.hibernate.query.sqm.sql.SqmTranslation; +import org.hibernate.query.sqm.tree.SqmDmlStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; + +import java.util.List; +import java.util.Map; + +import static org.hibernate.query.sqm.internal.SqmUtil.generateJdbcParamsXref; + + +/** + * @since 7.1 + */ +public class SimpleNonSelectQueryPlan implements NonSelectQueryPlan { + private final SqmDmlStatement statement; + private final DomainParameterXref domainParameterXref; + + private volatile CacheableSqmInterpretation interpretation; + + public SimpleNonSelectQueryPlan(SqmDmlStatement statement, DomainParameterXref domainParameterXref) { + this.statement = statement; + this.domainParameterXref = domainParameterXref; + } + + // For Hibernate Reactive + protected SqmDmlStatement getStatement() { + return statement; + } + + @Override + public int executeUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), statement ); + final Interpretation interpretation = getInterpretation( context ); + final ExecutionContext executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return execute( interpretation.interpretation, interpretation.jdbcParameterBindings, executionContext ); + } + + protected int execute(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + final SharedSessionContractImplementor session = executionContext.getSession(); + return session.getFactory().getJdbcServices().getJdbcMutationExecutor().execute( + sqmInterpretation.jdbcOperation(), + jdbcParameterBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + + private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) { + return SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + domainParameterXref, + sqmInterpretation.jdbcParamsXref(), + new SqmParameterMappingModelResolutionAccess() { + //this is pretty ugly! + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) sqmInterpretation.sqmParameterMappingModelTypes().get(parameter); + } + }, + executionContext.getSession() + ); + } + + protected SqmTranslation buildTranslation( + SqmDmlStatement sqm, + DomainParameterXref domainParameterXref, + DomainQueryExecutionContext executionContext) { + final SharedSessionContractImplementor session = executionContext.getSession(); + final SessionFactoryImplementor sessionFactory = session.getFactory(); + + return sessionFactory.getQueryEngine().getSqmTranslatorFactory() + .createMutationTranslator( + sqm, + executionContext.getQueryOptions(), + domainParameterXref, + executionContext.getQueryParameterBindings(), + executionContext.getSession().getLoadQueryInfluencers(), + sessionFactory.getSqlTranslationEngine() + ) + .translate(); + } + + // For Hibernate Reactive + protected Interpretation getInterpretation(DomainQueryExecutionContext context) { + Interpretation builtInterpretation = null; + CacheableSqmInterpretation localCopy = interpretation; + + if ( localCopy == null ) { + synchronized ( this ) { + localCopy = interpretation; + if ( localCopy == null ) { + builtInterpretation = buildInterpretation( statement, domainParameterXref, context ); + localCopy = builtInterpretation.interpretation; + interpretation = builtInterpretation.interpretation; + } + else { + builtInterpretation = updateInterpretation( localCopy, context ); + } + } + } + else { + builtInterpretation = updateInterpretation( localCopy, context ); + } + + return builtInterpretation != null ? builtInterpretation + : new Interpretation( localCopy, createJdbcParameterBindings( localCopy, context ) ); + } + + private @Nullable Interpretation updateInterpretation( + CacheableSqmInterpretation localCopy, + DomainQueryExecutionContext context) { + Interpretation builtInterpretation = null; + if ( localCopy.jdbcOperation().dependsOnParameterBindings() ) { + final JdbcParameterBindings jdbcParameterBindings = createJdbcParameterBindings( localCopy, context ); + // If the translation depends on the limit or lock options, we have to rebuild the JdbcSelect + // We could avoid this by putting the lock options into the cache key + if ( !localCopy.jdbcOperation().isCompatibleWith( jdbcParameterBindings, context.getQueryOptions() ) ) { + builtInterpretation = buildInterpretation( + localCopy.statement(), + localCopy.jdbcParamsXref(), + localCopy.sqmParameterMappingModelTypes(), + jdbcParameterBindings, + context + ); + interpretation = builtInterpretation.interpretation; + } + else { + builtInterpretation = new Interpretation( localCopy, jdbcParameterBindings ); + } + } + return builtInterpretation; + } + + // For Hibernate Reactive + protected record Interpretation( + CacheableSqmInterpretation interpretation, + JdbcParameterBindings jdbcParameterBindings + ) {} + + protected Interpretation buildInterpretation( + SqmDmlStatement sqm, + DomainParameterXref domainParameterXref, + DomainQueryExecutionContext executionContext) { + final SharedSessionContractImplementor session = executionContext.getSession(); + + final SqmTranslation sqmInterpretation = + buildTranslation( sqm, domainParameterXref, executionContext ); + final var jdbcParamsXref = + generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam ); + final Map, MappingModelExpressible> parameterModelTypeResolutions = + sqmInterpretation.getSqmParameterMappingModelTypeResolutions(); + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override + @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) + parameterModelTypeResolutions.get( parameter ); + } + }, + session + ); + return buildInterpretation( + sqmInterpretation.getSqlAst(), + jdbcParamsXref, + parameterModelTypeResolutions, + jdbcParameterBindings, + executionContext + ); + } + + protected Interpretation buildInterpretation( + MutationStatement mutationStatement, + Map, Map, List>> jdbcParamsXref, + Map, MappingModelExpressible> parameterModelTypeResolutions, + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext executionContext) { + final SharedSessionContractImplementor session = executionContext.getSession(); + final SessionFactoryImplementor sessionFactory = session.getFactory(); + final SqlAstTranslator mutationTranslator = + sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() + .buildMutationTranslator( sessionFactory, mutationStatement ); + return new Interpretation( + new CacheableSqmInterpretation<>( + mutationStatement, + mutationTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ), + jdbcParamsXref, + parameterModelTypeResolutions + ), + jdbcParameterBindings + ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java deleted file mode 100644 index cf7b4dbb434f..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.query.sqm.internal; - -import java.util.List; -import java.util.Map; - -import org.hibernate.action.internal.BulkOperationCleanupAction; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.NonSelectQueryPlan; -import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcParametersList; - -/** - * @author Steve Ebersole - */ -public class SimpleUpdateQueryPlan implements NonSelectQueryPlan { - private final SqmUpdateStatement sqmUpdate; - private final DomainParameterXref domainParameterXref; - - private JdbcOperationQueryMutation jdbcUpdate; - private Map, Map, List>> jdbcParamsXref; - private Map, MappingModelExpressible> sqmParamMappingTypeResolutions; - - public SimpleUpdateQueryPlan( - SqmUpdateStatement sqmUpdate, - DomainParameterXref domainParameterXref) { - this.sqmUpdate = sqmUpdate; - this.domainParameterXref = domainParameterXref; - } - - @Override - public int executeUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmUpdate ); - final SharedSessionContractImplementor session = executionContext.getSession(); - final SessionFactoryImplementor factory = session.getFactory(); - final JdbcServices jdbcServices = factory.getJdbcServices(); - SqlAstTranslator updateTranslator = null; - if ( jdbcUpdate == null ) { - updateTranslator = createUpdateTranslator( executionContext ); - } - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmParamMappingTypeResolutions.get(parameter); - } - }, - session - ); - - if ( jdbcUpdate != null && !jdbcUpdate.isCompatibleWith( - jdbcParameterBindings, - executionContext.getQueryOptions() - ) ) { - updateTranslator = createUpdateTranslator( executionContext ); - } - - if ( updateTranslator != null ) { - jdbcUpdate = updateTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - return jdbcServices.getJdbcMutationExecutor().execute( - jdbcUpdate, - jdbcParameterBindings, - sql -> session - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ) - ); - } - - private SqlAstTranslator createUpdateTranslator(DomainQueryExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - - final SqmTranslation sqmInterpretation = - factory.getQueryEngine().getSqmTranslatorFactory() - .createMutationTranslator( - sqmUpdate, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ) - .translate(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - this.sqmParamMappingTypeResolutions = sqmInterpretation.getSqmParameterMappingModelTypeResolutions(); - - return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqmInterpretation.getSqlAst() ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java index 08385f2a37d6..1cbe89f1fae8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmQueryImpl.java @@ -615,7 +615,7 @@ private NonSelectQueryPlan buildUpdateQueryPlan() { .getEntityDescriptor( sqmUpdate.getTarget().getModel().getHibernateEntityName() ); final SqmMultiTableMutationStrategy multiTableStrategy = persister.getSqmMultiTableMutationStrategy(); return multiTableStrategy == null - ? new SimpleUpdateQueryPlan( sqmUpdate, domainParameterXref ) + ? new SimpleNonSelectQueryPlan( sqmUpdate, domainParameterXref ) : new MultiTableUpdateQueryPlan( sqmUpdate, domainParameterXref, multiTableStrategy ); } @@ -653,13 +653,13 @@ else if ( sqmInsert instanceof SqmInsertValuesStatement insertValues final SqmInsertValuesStatement subInsert = insertValues.copyWithoutValues( SqmCopyContext.simpleContext() ); subInsert.values( valuesList.get( i ) ); - planParts[i] = new SimpleInsertQueryPlan( subInsert, domainParameterXref ); + planParts[i] = new SimpleNonSelectQueryPlan( subInsert, domainParameterXref ); } return new AggregatedNonSelectQueryPlanImpl( planParts ); } - return new SimpleInsertQueryPlan( sqmInsert, domainParameterXref ); + return new SimpleNonSelectQueryPlan( sqmInsert, domainParameterXref ); } protected boolean hasIdentifierAssigned(SqmInsertStatement sqmInsert, EntityPersister entityDescriptor) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/AbstractMutationHandler.java similarity index 50% rename from hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java rename to hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/AbstractMutationHandler.java index 9ed3ec2a9b1e..bf9afd069372 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/AbstractMutationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/AbstractMutationHandler.java @@ -2,29 +2,28 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.query.sqm.mutation.spi; +package org.hibernate.query.sqm.mutation.internal; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.query.sqm.mutation.internal.Handler; -import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.sqm.tree.SqmDmlStatement; /** * @author Steve Ebersole */ public abstract class AbstractMutationHandler implements Handler { - private final SqmDeleteOrUpdateStatement sqmDeleteOrUpdateStatement; + private final SqmDmlStatement sqmDmlStatement; private final SessionFactoryImplementor sessionFactory; - private final EntityMappingType entityDescriptor; + private final EntityPersister entityDescriptor; public AbstractMutationHandler( - SqmDeleteOrUpdateStatement sqmDeleteOrUpdateStatement, + SqmDmlStatement sqmDmlStatement, SessionFactoryImplementor sessionFactory) { - this.sqmDeleteOrUpdateStatement = sqmDeleteOrUpdateStatement; + this.sqmDmlStatement = sqmDmlStatement; this.sessionFactory = sessionFactory; - final String entityName = sqmDeleteOrUpdateStatement.getTarget() + final String entityName = sqmDmlStatement.getTarget() .getModel() .getHibernateEntityName(); @@ -32,11 +31,11 @@ public AbstractMutationHandler( } - public SqmDeleteOrUpdateStatement getSqmDeleteOrUpdateStatement() { - return sqmDeleteOrUpdateStatement; + public SqmDmlStatement getSqmStatement() { + return sqmDmlStatement; } - public EntityMappingType getEntityDescriptor() { + public EntityPersister getEntityDescriptor() { return entityDescriptor; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/Handler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/Handler.java index d847a709ce9b..4d0a4fd08149 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/Handler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/Handler.java @@ -5,6 +5,9 @@ package org.hibernate.query.sqm.mutation.internal; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * Simply as a matter of code structuring it is often worthwhile to put all of the execution code into a separate @@ -14,8 +17,10 @@ * queries * * @author Steve Ebersole + * @deprecated Moved to {@link MultiTableHandler} */ -public interface Handler { +@Deprecated(forRemoval = true, since = "7.1") +public interface Handler extends MultiTableHandler { /** * Execute the multi-table update or delete indicated by the SQM AST * passed in when this Handler was created. @@ -24,5 +29,17 @@ public interface Handler { * * @return The "number of rows affected" count */ - int execute(DomainQueryExecutionContext executionContext); + default int execute(DomainQueryExecutionContext executionContext) { + return execute( createJdbcParameterBindings( executionContext ), executionContext ); + } + + @Override + default boolean dependsOnParameterBindings() { + return true; + } + + @Override + default boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java index 15e5cfc8e31d..7b50df3011b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MatchingIdSelectionHelper.java @@ -6,20 +6,24 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SqmQuerySource; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.internal.SqmUtil; @@ -42,6 +46,7 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.internal.RowTransformerArrayImpl; @@ -220,10 +225,11 @@ public static SqmSelectStatement generateMatchingIdSelectStatement( * Centralized selection of ids matching the restriction of the DELETE * or UPDATE SQM query */ - public static List selectMatchingIds( + public static CacheableSqmInterpretation createMatchingIdsSelect( SqmDeleteOrUpdateStatement sqmMutationStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext executionContext) { + DomainQueryExecutionContext executionContext, + MutableObject firstJdbcParameterBindingsConsumer) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final EntityMappingType entityDescriptor = @@ -276,10 +282,12 @@ public static List selectMatchingIds( .getSqlAstTranslatorFactory() .buildSelectTranslator( factory, translation.getSqlAst() ); + final Map, Map, List>> jdbcParamsXref = + SqmUtil.generateJdbcParamsXref( domainParameterXref, translator ); final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), domainParameterXref, - SqmUtil.generateJdbcParamsXref( domainParameterXref, translator ), + jdbcParamsXref, new SqmParameterMappingModelResolutionAccess() { @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { @@ -304,22 +312,43 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter( + translation.getSqlAst(), + sqlAstSelectTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ), + jdbcParamsXref, + translation.getSqmParameterMappingModelTypeResolutions() ); - lockOptions.setLockMode( lockMode ); + } + + /** + * Centralized selection of ids matching the restriction of the DELETE + * or UPDATE SQM query + */ + public static List selectMatchingIds( + SqmDeleteOrUpdateStatement sqmMutationStatement, + DomainParameterXref domainParameterXref, + DomainQueryExecutionContext executionContext) { + final MutableObject jdbcParameterBindings = new MutableObject<>(); + final CacheableSqmInterpretation interpretation = + createMatchingIdsSelect( sqmMutationStatement, domainParameterXref, executionContext, jdbcParameterBindings ); + return selectMatchingIds( interpretation, jdbcParameterBindings.get(), executionContext ); + } + public static List selectMatchingIds( + CacheableSqmInterpretation interpretation, + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext executionContext) { final RowTransformer rowTransformer; - if ( sqmQuerySpec.getSelectClause().getSelections().size() == 1 ) { + if ( interpretation.statement().getDomainResultDescriptors().size() == 1 ) { rowTransformer = RowTransformerSingularReturnImpl.instance(); } else { rowTransformer = RowTransformerArrayImpl.instance(); } //noinspection unchecked - return jdbcServices.getJdbcSelectExecutor().list( - idSelectJdbcOperation, + return executionContext.getSession().getFactory().getJdbcServices().getJdbcSelectExecutor().list( + interpretation.jdbcOperation(), jdbcParameterBindings, SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), (RowTransformer) rowTransformer, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java index 1e538e4ff097..ee8a512a689b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java @@ -115,6 +115,11 @@ public TableGroup getMutatingTableGroup() { return mutatingTableGroup; } + @Override // promote protected to public + public SqmStatement getStatement() { + return super.getStatement(); + } + @Override // promote protected to public public Stack getProcessingStateStack() { return super.getProcessingStateStack(); 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 8270e925b2b0..32eb45fb848d 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 @@ -42,11 +42,12 @@ import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** @@ -98,64 +99,64 @@ else if ( modelPart instanceof EmbeddedAttributeMapping ) { ); } - public static void cleanUpCollectionTables( + public static void visitCollectionTableDeletes( EntityMappingType entityDescriptor, BiFunction restrictionProducer, JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { + QueryOptions queryOptions, + Consumer jdbcMutationConsumer) { if ( ! entityDescriptor.getEntityPersister().hasCollections() ) { // none to clean-up return; } - entityDescriptor.visitSubTypeAttributeMappings( attributeMapping -> { if ( attributeMapping instanceof PluralAttributeMapping ) { - cleanUpCollectionTable( + visitCollectionTableDeletes( (PluralAttributeMapping) attributeMapping, - entityDescriptor, restrictionProducer, jdbcParameterBindings, - executionContext + queryOptions, + jdbcMutationConsumer ); } else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { - cleanUpCollectionTables( + visitCollectionTableDeletes( (EmbeddedAttributeMapping) attributeMapping, - entityDescriptor, restrictionProducer, jdbcParameterBindings, - executionContext + queryOptions, + jdbcMutationConsumer ); } } ); } - private static void cleanUpCollectionTables( + private static void visitCollectionTableDeletes( EmbeddedAttributeMapping attributeMapping, - EntityMappingType entityDescriptor, BiFunction restrictionProducer, JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { + QueryOptions queryOptions, + Consumer jdbcMutationConsumer) { attributeMapping.visitSubParts( modelPart -> { if ( modelPart instanceof PluralAttributeMapping ) { - cleanUpCollectionTable( + visitCollectionTableDeletes( (PluralAttributeMapping) modelPart, - entityDescriptor, restrictionProducer, jdbcParameterBindings, - executionContext + queryOptions, + jdbcMutationConsumer ); } else if ( modelPart instanceof EmbeddedAttributeMapping ) { - cleanUpCollectionTables( + visitCollectionTableDeletes( (EmbeddedAttributeMapping) modelPart, - entityDescriptor, restrictionProducer, jdbcParameterBindings, - executionContext + queryOptions, + jdbcMutationConsumer ); } }, @@ -163,15 +164,14 @@ else if ( modelPart instanceof EmbeddedAttributeMapping ) { ); } - private static void cleanUpCollectionTable( + private static void visitCollectionTableDeletes( PluralAttributeMapping attributeMapping, - EntityMappingType entityDescriptor, BiFunction restrictionProducer, JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { + QueryOptions queryOptions, + Consumer jdbcOperationConsumer) { final String separateCollectionTable = attributeMapping.getSeparateCollectionTable(); - - final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); + final SessionFactoryImplementor sessionFactory = attributeMapping.getCollectionDescriptor().getFactory(); final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); if ( separateCollectionTable == null ) { @@ -192,18 +192,11 @@ private static void cleanUpCollectionTable( restrictionProducer.apply( tableReference, attributeMapping ) ); - jdbcServices.getJdbcMutationExecutor().execute( + jdbcOperationConsumer.accept( jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() .buildMutationTranslator( sessionFactory, sqlAstDelete ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ), - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContext + .translate( jdbcParameterBindings, queryOptions ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java index cbd025c1ddfc..7ea2a1916e14 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/AbstractCteMutationHandler.java @@ -14,18 +14,21 @@ import org.hibernate.LockOptions; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.internal.SqmUtil; import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler; +import org.hibernate.query.sqm.mutation.internal.AbstractMutationHandler; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.expression.SqmExpression; @@ -53,6 +56,7 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; @@ -68,58 +72,42 @@ public abstract class AbstractCteMutationHandler extends AbstractMutationHandler public static final String CTE_TABLE_IDENTIFIER = "id"; - private final CteTable cteTable; private final DomainParameterXref domainParameterXref; - private final CteMutationStrategy strategy; + private final Map, Map, List>> jdbcParamsXref; + private final Map, MappingModelExpressible> resolvedParameterMappingModelTypes; + + private final JdbcOperationQuerySelect select; public AbstractCteMutationHandler( CteTable cteTable, SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { super( sqmStatement, sessionFactory ); - this.cteTable = cteTable; - this.domainParameterXref = domainParameterXref; - - this.strategy = strategy; - } - - public CteTable getCteTable() { - return cteTable; - } - - public DomainParameterXref getDomainParameterXref() { - return domainParameterXref; - } - - public CteMutationStrategy getStrategy() { - return strategy; - } - @Override - public int execute(DomainQueryExecutionContext executionContext) { - final SqmDeleteOrUpdateStatement sqmMutationStatement = getSqmDeleteOrUpdateStatement(); - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final SessionFactoryImplementor factory = context.getSession().getFactory(); final EntityMappingType entityDescriptor = getEntityDescriptor(); final String explicitDmlTargetAlias; // We need an alias because we try to acquire a WRITE lock for these rows in the CTE - if ( sqmMutationStatement.getTarget().getExplicitAlias() == null ) { + if ( sqmStatement.getTarget().getExplicitAlias() == null ) { explicitDmlTargetAlias = "dml_target"; } else { - explicitDmlTargetAlias = sqmMutationStatement.getTarget().getExplicitAlias(); + explicitDmlTargetAlias = sqmStatement.getTarget().getExplicitAlias(); } final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( entityDescriptor, - sqmMutationStatement, - sqmMutationStatement.getTarget(), + sqmStatement, + sqmStatement.getTarget(), explicitDmlTargetAlias, domainParameterXref, - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), + context.getQueryOptions(), + context.getSession().getLoadQueryInfluencers(), + context.getQueryParameterBindings(), factory.getSqlTranslationEngine() ); final Map, List> parameterResolutions; @@ -130,18 +118,18 @@ public int execute(DomainQueryExecutionContext executionContext) { parameterResolutions = new IdentityHashMap<>(); } - final Predicate restriction = sqmConverter.visitWhereClause( sqmMutationStatement.getWhereClause() ); + final Predicate restriction = sqmConverter.visitWhereClause( sqmStatement.getWhereClause() ); sqmConverter.pruneTableGroupJoins(); final CteStatement idSelectCte = new CteStatement( - getCteTable(), + cteTable, MatchingIdSelectionHelper.generateMatchingIdSelectStatement( entityDescriptor, - sqmMutationStatement, + sqmStatement, true, restriction, sqmConverter, - executionContext + context ), // The id-select cte will be reused multiple times CteMaterialization.MATERIALIZED @@ -178,24 +166,60 @@ public int execute(DomainQueryExecutionContext executionContext) { statement.addCteStatement( idSelectCte ); addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); + this.domainParameterXref = domainParameterXref; + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ); + this.resolvedParameterMappingModelTypes = sqmConverter.getSqmParameterMappingModelExpressibleResolutions(); + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), + context.getQueryParameterBindings(), domainParameterXref, - SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ), + jdbcParamsXref, new SqmParameterMappingModelResolutionAccess() { @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter ); + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); } }, - executionContext.getSession() + context.getSession() ); + + this.select = translator.translate( jdbcParameterBindings, context.getQueryOptions() ); + firstJdbcParameterBindingsConsumer.set( jdbcParameterBindings ); + } + + @Override + public JdbcParameterBindings createJdbcParameterBindings(DomainQueryExecutionContext context) { + return SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + } + + @Override + public boolean dependsOnParameterBindings() { + return select.dependsOnParameterBindings(); + } + + @Override + public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + return select.isCompatibleWith( jdbcParameterBindings, queryOptions ); + } + + @Override + public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext) { final LockOptions lockOptions = executionContext.getQueryOptions().getLockOptions(); // Acquire a WRITE lock for the rows that are about to be modified lockOptions.setLockMode( LockMode.WRITE ); - final JdbcOperationQuerySelect select = translator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); executionContext.getSession().autoFlushIfRequired( select.getAffectedTableNames() ); - List list = jdbcServices.getJdbcSelectExecutor().list( + List list = executionContext.getSession().getFactory().getJdbcServices().getJdbcSelectExecutor().list( select, jdbcParameterBindings, SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), @@ -207,6 +231,11 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter sqmDeleteStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory ); + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory, context, firstJdbcParameterBindingsConsumer ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java index 5edfe546c7a8..cabbe520944d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java @@ -12,10 +12,10 @@ import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; import org.hibernate.id.OptimizableGenerator; import org.hibernate.id.enhanced.Optimizer; +import org.hibernate.internal.util.MutableObject; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.Stack; import org.hibernate.metamodel.mapping.BasicValuedMapping; -import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.persister.entity.EntityPersister; @@ -23,6 +23,8 @@ import org.hibernate.query.SortDirection; import org.hibernate.query.results.internal.TableGroupImpl; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.BinaryArithmeticOperator; import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.NodeBuilder; @@ -91,6 +93,7 @@ import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; @@ -117,60 +120,25 @@ public class CteInsertHandler implements InsertHandler { public static final String CTE_TABLE_IDENTIFIER = "id"; public static final String ROW_NUMBERS_WITH_SEQUENCE_VALUE = "rows_with_next_val"; - private final SqmInsertStatement sqmStatement; - - private final SessionFactoryImplementor sessionFactory; - private final EntityMappingType entityDescriptor; - private final CteTable cteTable; private final DomainParameterXref domainParameterXref; + private final Map, Map, List>> jdbcParamsXref; + private final Map, MappingModelExpressible> resolvedParameterMappingModelTypes; + + private final JdbcOperationQuerySelect select; public CteInsertHandler( CteTable cteTable, SqmInsertStatement sqmStatement, DomainParameterXref domainParameterXref, - SessionFactoryImplementor sessionFactory) { - this.sqmStatement = sqmStatement; - this.sessionFactory = sessionFactory; - - final String entityName = this.sqmStatement.getTarget() + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final String entityName = sqmStatement.getTarget() .getModel() .getHibernateEntityName(); - this.entityDescriptor = sessionFactory.getMappingMetamodel().getEntityDescriptor( entityName ); - this.cteTable = cteTable; - this.domainParameterXref = domainParameterXref; - } - - public static CteTable createCteTable(CteTable sqmCteTable, List sqmCteColumns) { - return new CteTable( sqmCteTable.getTableExpression(), sqmCteColumns ); - } - - public SqmInsertStatement getSqmStatement() { - return sqmStatement; - } - - public EntityMappingType getEntityDescriptor() { - return entityDescriptor; - } - - public CteTable getCteTable() { - return cteTable; - } - - public DomainParameterXref getDomainParameterXref() { - return domainParameterXref; - } - - private NodeBuilder getCriteriaBuilder() { - return sessionFactory.getQueryEngine().getCriteriaBuilder(); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - final SqmInsertStatement sqmInsertStatement = getSqmStatement(); - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final EntityPersister entityDescriptor = getEntityDescriptor().getEntityPersister(); - final SqmRoot target = sqmInsertStatement.getTarget(); + final SessionFactoryImplementor factory = context.getSession().getFactory(); + final EntityPersister entityDescriptor = factory.getMappingMetamodel().getEntityDescriptor( entityName ); + final SqmRoot target = sqmStatement.getTarget(); final String explicitDmlTargetAlias = target.getExplicitAlias() == null ? "dml_target" @@ -178,13 +146,13 @@ public int execute(DomainQueryExecutionContext executionContext) { final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( entityDescriptor, - sqmInsertStatement, + sqmStatement, target, explicitDmlTargetAlias, domainParameterXref, - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), + context.getQueryOptions(), + context.getSession().getLoadQueryInfluencers(), + context.getQueryParameterBindings(), factory.getSqlTranslationEngine() ); final TableGroup insertingTableGroup = sqmConverter.getMutatingTableGroup(); @@ -212,7 +180,7 @@ public int execute(DomainQueryExecutionContext executionContext) { ) ); }, - sqmInsertStatement, + sqmStatement, entityDescriptor, insertingTableGroup ); @@ -225,13 +193,13 @@ public int execute(DomainQueryExecutionContext executionContext) { final Stack processingStateStack = sqmConverter.getProcessingStateStack(); final SqlAstProcessingState oldState = processingStateStack.pop(); final Statement queryStatement; - if ( sqmInsertStatement instanceof SqmInsertSelectStatement ) { - final QueryPart queryPart = sqmConverter.visitQueryPart( ( (SqmInsertSelectStatement) sqmInsertStatement ).getSelectQueryPart() ); + if ( sqmStatement instanceof SqmInsertSelectStatement ) { + final QueryPart queryPart = sqmConverter.visitQueryPart( ( (SqmInsertSelectStatement) sqmStatement ).getSelectQueryPart() ); queryPart.visitQuerySpecs( querySpec -> { // This returns true if the insertion target uses a sequence with an optimizer // in which case we will fill the row_number column instead of the id column - if ( additionalInsertValues.applySelections( querySpec, sessionFactory ) ) { + if ( additionalInsertValues.applySelections( querySpec, factory ) ) { final CteColumn rowNumberColumn = cteTable.getCteColumns() .get( cteTable.getCteColumns().size() - 1 ); targetPathCteColumns.set( @@ -245,7 +213,7 @@ public int execute(DomainQueryExecutionContext executionContext) { 0, SqmInsertStrategyHelper.createRowNumberingExpression( querySpec, - sessionFactory + factory ) ) ); @@ -255,7 +223,7 @@ public int execute(DomainQueryExecutionContext executionContext) { queryStatement = new SelectStatement( queryPart ); } else { - final List sqmValuesList = ( (SqmInsertValuesStatement) sqmInsertStatement ).getValuesList(); + final List sqmValuesList = ( (SqmInsertValuesStatement) sqmStatement ).getValuesList(); final List valuesList = new ArrayList<>( sqmValuesList.size() ); for ( SqmValues sqmValues : sqmValuesList ) { final Values values = sqmConverter.visitValues( sqmValues ); @@ -280,7 +248,7 @@ public int execute(DomainQueryExecutionContext executionContext) { false, null, columnReference.getJdbcMapping() - ) + ) ) ); } @@ -291,7 +259,7 @@ public int execute(DomainQueryExecutionContext executionContext) { 0, SqmInsertStrategyHelper.createRowNumberingExpression( querySpec, - sessionFactory + factory ) ) ); @@ -318,7 +286,7 @@ public int execute(DomainQueryExecutionContext executionContext) { targetPathCteColumns.add( rowNumberColumn ); } - final CteTable entityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); + final CteTable entityCteTable = createCteTable( cteTable, targetPathCteColumns ); // Create the main query spec that will return the count of rows final QuerySpec querySpec = new QuerySpec( true, 1 ); @@ -327,7 +295,7 @@ public int execute(DomainQueryExecutionContext executionContext) { final CteStatement entityCte; if ( additionalInsertValues.requiresRowNumberIntermediate() ) { - final CteTable fullEntityCteTable = getCteTable(); + final CteTable fullEntityCteTable = cteTable; final String baseTableName = "base_" + entityCteTable.getTableExpression(); final CteStatement baseEntityCte = new CteStatement( entityCteTable.withName( baseTableName ), @@ -385,12 +353,12 @@ public int execute(DomainQueryExecutionContext executionContext) { (BulkInsertionCapableIdentifierGenerator) entityDescriptor.getGenerator(); final String fragment = generator.determineBulkInsertionIdentifierGenerationSelectFragment( - sessionFactory.getSqlStringGenerationContext() + factory.getSqlStringGenerationContext() ); final Expression lowValueExpression = optimizer.createLowValueExpression( new SelfRenderingSqlFragmentExpression( fragment ), - sessionFactory + factory ); rowsWithSequenceQuery.getSelectClause() .addSqlSelection( new SqlSelectionImpl( 1, lowValueExpression ) ); @@ -488,12 +456,12 @@ public int execute(DomainQueryExecutionContext executionContext) { ) ); final CteTable finalEntityCteTable; - if ( targetPathCteColumns.contains( getCteTable().getCteColumns().get( 0 ) ) ) { + if ( targetPathCteColumns.contains( cteTable.getCteColumns().get( 0 ) ) ) { finalEntityCteTable = entityCteTable; } else { - targetPathCteColumns.add( 0, getCteTable().getCteColumns().get( 0 ) ); - finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); + targetPathCteColumns.add( 0, cteTable.getCteColumns().get( 0 ) ); + finalEntityCteTable = createCteTable( cteTable, targetPathCteColumns ); } final List cteColumns = finalEntityCteTable.getCteColumns(); for ( int i = 1; i < cteColumns.size(); i++ ) { @@ -532,7 +500,7 @@ else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) ); statement.addCteStatement( baseEntityCte ); targetPathCteColumns.add( 0, cteTable.getCteColumns().get( 0 ) ); - final CteTable finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); + final CteTable finalEntityCteTable = createCteTable( cteTable, targetPathCteColumns ); final QuerySpec finalQuerySpec = new QuerySpec( true ); final SelectStatement finalQueryStatement = new SelectStatement( finalQuerySpec ); entityCte = new CteStatement( @@ -556,6 +524,7 @@ else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) final String baseInsertCte = addDmlCtes( statement, entityCte, + entityDescriptor, targetPathColumns, assignsId, sqmConverter, @@ -586,25 +555,64 @@ else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() .getSqlAstTranslatorFactory() .buildSelectTranslator( factory, statement ); + + this.domainParameterXref = domainParameterXref; + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ); + this.resolvedParameterMappingModelTypes = sqmConverter.getSqmParameterMappingModelExpressibleResolutions(); + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), + context.getQueryParameterBindings(), domainParameterXref, - SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ), + jdbcParamsXref, new SqmParameterMappingModelResolutionAccess() { @Override @SuppressWarnings("unchecked") public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter ); + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); } }, - executionContext.getSession() + context.getSession() ); - final JdbcOperationQuerySelect select = translator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + this.select = translator.translate( jdbcParameterBindings, context.getQueryOptions() ); + firstJdbcParameterBindingsConsumer.set( jdbcParameterBindings ); + } + + public static CteTable createCteTable(CteTable sqmCteTable, List sqmCteColumns) { + return new CteTable( sqmCteTable.getTableExpression(), sqmCteColumns ); + } + + @Override + public JdbcParameterBindings createJdbcParameterBindings(DomainQueryExecutionContext context) { + return SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + } + + @Override + public boolean dependsOnParameterBindings() { + return select.dependsOnParameterBindings(); + } + + @Override + public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + return select.isCompatibleWith( jdbcParameterBindings, queryOptions ); + } - executionContext.getSession().autoFlushIfRequired( select.getAffectedTableNames() ); - List list = jdbcServices.getJdbcSelectExecutor().list( + @Override + public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { + context.getSession().autoFlushIfRequired( select.getAffectedTableNames() ); + List list = context.getSession().getFactory().getJdbcServices().getJdbcSelectExecutor().list( select, jdbcParameterBindings, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), + SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ), RowTransformerSingularReturnImpl.instance(), null, ListResultsConsumer.UniqueSemantic.NONE, @@ -613,10 +621,15 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter arg = new SqmStar( getCriteriaBuilder() ); + final SqmExpression arg = new SqmStar( factory.getQueryEngine().getCriteriaBuilder() ); return factory.getQueryEngine().getSqmFunctionRegistry().findFunctionDescriptor( "count" ) .generateSqmExpression( arg, null, factory.getQueryEngine() ) .convertToSqlAst( sqmConverter ); @@ -625,14 +638,13 @@ protected Expression createCountStar( protected String addDmlCtes( CteContainer statement, CteStatement queryCte, + EntityPersister entityPersister, List, Assignment>> assignments, boolean assignsId, MultiTableSqmMutationConverter sqmConverter, SessionFactoryImplementor factory) { + final SqmInsertStatement sqmStatement = (SqmInsertStatement) sqmConverter.getStatement(); final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup(); - final EntityMappingType entityDescriptor = getEntityDescriptor(); - - final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); final String rootEntityName = entityPersister.getRootEntityName(); final EntityPersister rootEntityDescriptor = factory.getMappingMetamodel() @@ -692,15 +704,14 @@ protected String addDmlCtes( // Add the root insert as cte - final EntityPersister persister = entityDescriptor.getEntityPersister(); - final String rootTableName = persister.getTableName( 0 ); + final String rootTableName = entityPersister.getTableName( 0 ); final TableReference rootTableReference = updatingTableGroup.getTableReference( updatingTableGroup.getNavigablePath(), rootTableName, true ); - final Generator identifierGenerator = entityDescriptor.getEntityPersister().getGenerator(); + final Generator identifierGenerator = entityPersister.getGenerator(); final List, Assignment>> tableAssignments = assignmentsByTable.get( rootTableReference ); if ( !assignsId && ( tableAssignments == null || tableAssignments.isEmpty() ) && !identifierGenerator.generatedOnExecution() ) { @@ -709,10 +720,10 @@ protected String addDmlCtes( final ConflictClause conflictClause = sqmConverter.visitConflictClause( sqmStatement.getConflictClause() ); - final int tableSpan = persister.getTableSpan(); - final List keyCteColumns = queryCte.getCteTable().findCteColumns( persister.getIdentifierMapping() ); + final int tableSpan = entityPersister.getTableSpan(); + final List keyCteColumns = queryCte.getCteTable().findCteColumns( entityPersister.getIdentifierMapping() ); for ( int tableIndex = 0; tableIndex < tableSpan; tableIndex++ ) { - final String tableExpression = persister.getTableName( tableIndex ); + final String tableExpression = entityPersister.getTableName( tableIndex ); final TableReference updatingTableReference = updatingTableGroup.getTableReference( updatingTableGroup.getNavigablePath(), tableExpression, @@ -723,7 +734,7 @@ protected String addDmlCtes( updatingTableReference, tableExpression ); - final String[] keyColumns = persister.getKeyColumns( tableIndex ); + final String[] keyColumns = entityPersister.getKeyColumns( tableIndex ); final List returningColumnReferences = new ArrayList<>( keyColumns.length + ( assignmentList == null ? 0 : assignmentList.size() ) ); @@ -733,7 +744,7 @@ protected String addDmlCtes( final CteTable dmlResultCte; if ( tableIndex == 0 && !assignsId && identifierGenerator.generatedOnExecution() ) { // Special handling for identity generation - final String cteTableName = getCteTableName( tableExpression, "base_" ); + final String cteTableName = getCteTableName( tableExpression, "base_", factory ); if ( statement.getCteStatement( cteTableName ) != null ) { // Since secondary tables could appear multiple times, we have to skip duplicates continue; @@ -789,7 +800,7 @@ protected String addDmlCtes( ); final TableGroup rootInsertCteTableGroup = new CteTableGroup( new NamedTableReference( - getCteTableName( tableExpression ), + getCteTableName( tableExpression, factory ), "t" ) ); @@ -847,7 +858,7 @@ protected String addDmlCtes( finalReturningColumns.addAll( keyCteColumns ); finalReturningColumns.add( rowNumberColumn ); final CteTable finalResultCte = new CteTable( - getCteTableName( tableExpression ), + getCteTableName( tableExpression, factory ), finalReturningColumns ); final QuerySpec finalResultQuery = new QuerySpec( true ); @@ -876,7 +887,7 @@ protected String addDmlCtes( new SqlSelectionImpl( SqmInsertStrategyHelper.createRowNumberingExpression( querySpec, - sessionFactory + factory ) ) ); @@ -890,7 +901,7 @@ protected String addDmlCtes( finalCteStatement = new CteStatement( finalResultCte, finalResultStatement ); } else { - final String cteTableName = getCteTableName( tableExpression ); + final String cteTableName = getCteTableName( tableExpression, factory ); if ( statement.getCteStatement( cteTableName ) != null ) { // Since secondary tables could appear multiple times, we have to skip duplicates continue; @@ -967,7 +978,15 @@ protected String addDmlCtes( if ( conflictClause != null ) { if ( conflictClause.isDoNothing() && conflictClause.getConstraintColumnNames().isEmpty() ) { // Conflict clauses that use a constraint name and do nothing can just use the conflict clause as it is - handleConflictClause( dmlResultCte, dmlStatement, queryCte, tableIndex, conflictClause, statement ); + handleConflictClause( + dmlResultCte, + dmlStatement, + queryCte, + entityPersister, + tableIndex, + conflictClause, + statement + ); } else { final List compatibleAssignments = getCompatibleAssignments( dmlStatement, conflictClause ); @@ -977,6 +996,7 @@ protected String addDmlCtes( dmlResultCte, dmlStatement, queryCte, + entityPersister, tableIndex, new ConflictClause( conflictClause.getConstraintName(), @@ -993,6 +1013,7 @@ else if ( targetColumnsContainAllConstraintColumns( dmlStatement, conflictClause dmlResultCte, dmlStatement, queryCte, + entityPersister, tableIndex, new ConflictClause( conflictClause.getConstraintName(), @@ -1019,22 +1040,24 @@ else if ( targetColumnsContainAllConstraintColumns( dmlStatement, conflictClause statement.addCteStatement( queryCte ); } } - return getCteTableName( rootTableName ); + return getCteTableName( rootTableName, factory ); } private void handleConflictClause( CteTable dmlResultCte, InsertSelectStatement insertStatement, CteStatement queryCte, + EntityPersister entityDescriptor, int tableIndex, ConflictClause conflictClause, CteContainer statement) { + final SessionFactoryImplementor sessionFactory = entityDescriptor.getFactory(); if ( sessionFactory.getJdbcServices().getDialect().supportsConflictClauseForInsertCTE() ) { insertStatement.setConflictClause( conflictClause ); statement.addCteStatement( new CteStatement( dmlResultCte, insertStatement ) ); } else { - final NodeBuilder nodeBuilder = getCriteriaBuilder(); + final NodeBuilder nodeBuilder = sessionFactory.getQueryEngine().getCriteriaBuilder(); // Build an exists subquery clause to only insert if no row with a matching constraint column value exists i.e. // insert into target (c1, c2) // select e.c1, e.c2 from HTE_target e @@ -1070,7 +1093,8 @@ private void handleConflictClause( columnsToMatch = Arrays.asList( ((EntityPersister) entityDescriptor).getKeyColumns( tableIndex ) ), insertStatement, false, - true + true, + booleanType ); if ( predicate == null ) { throw new IllegalArgumentException( "Couldn't infer conflict constraint columns" ); @@ -1079,7 +1103,8 @@ private void handleConflictClause( } else { columnsToMatch = constraintColumnNames; - subquery.applyPredicate( buildColumnMatchPredicate( constraintColumnNames, insertStatement, true, true ) ); + subquery.applyPredicate( + buildColumnMatchPredicate( constraintColumnNames, insertStatement, true, true, booleanType ) ); } insertQuerySpec.applyPredicate( new ExistsPredicate( subquery, true, booleanType ) ); @@ -1134,7 +1159,8 @@ private void handleConflictClause( columnsToMatch, insertStatement, true, - false + false, + booleanType ) ) ); @@ -1154,13 +1180,14 @@ private void handleConflictClause( columnsToMatch, insertStatement, true, - false + false, + booleanType ) ); final QuerySpec matchCteSubquery = new QuerySpec( false, 1 ); matchCteSubquery.getSelectClause().addSqlSelection( new SqlSelectionImpl( new QueryLiteral<>( 1, - getCriteriaBuilder().getIntegerType() + nodeBuilder.getIntegerType() ) ) ); matchCteSubquery.getFromClause().addRoot( updateSubquery.getFromClause().getRoots().get( 0 ) ); @@ -1233,8 +1260,8 @@ private Predicate buildColumnMatchPredicate( List constraintColumnNames, InsertSelectStatement dmlStatement, boolean errorIfMissing, - boolean compareAgainstSelectItems) { - final BasicType booleanType = getCriteriaBuilder().getBooleanType(); + boolean compareAgainstSelectItems, + BasicType booleanType) { final QuerySpec insertQuerySpec = (QuerySpec) dmlStatement.getSourceSelectStatement(); Predicate predicate = null; OUTER: for ( String constraintColumnName : constraintColumnNames ) { @@ -1373,11 +1400,11 @@ private TableReference resolveTableReference( throw new SemanticException( "Assignment referred to column of a joined association: " + columnReference ); } - protected String getCteTableName(String tableExpression) { - return getCteTableName( tableExpression, "" ); + protected String getCteTableName(String tableExpression, SessionFactoryImplementor sessionFactory) { + return getCteTableName( tableExpression, "", sessionFactory ); } - protected String getCteTableName(String tableExpression, String subPrefix) { + protected String getCteTableName(String tableExpression, String subPrefix, SessionFactoryImplementor sessionFactory) { final Dialect dialect = sessionFactory.getJdbcServices().getDialect(); if ( Identifier.isQuoted( tableExpression ) ) { tableExpression = tableExpression.substring( 1, tableExpression.length() - 1 ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertStrategy.java index f8c461586b17..a8bccd347820 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertStrategy.java @@ -8,6 +8,7 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.SingleTableSubclass; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -15,9 +16,13 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.InsertHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + /** * @asciidoc @@ -151,11 +156,16 @@ public CteInsertStrategy( } @Override - public int executeInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new CteInsertHandler( entityCteTable, sqmInsertStatement, domainParameterXref, sessionFactory ).execute( context ); + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final InsertHandler multiTableHandler = new CteInsertHandler( + entityCteTable, + sqmInsertStatement, + domainParameterXref, + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } protected EntityPersister getRootDescriptor() { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteMutationStrategy.java index 11727bdb2e68..bce9aa166d7d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteMutationStrategy.java @@ -8,16 +8,20 @@ import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @asciidoc @@ -88,41 +92,51 @@ public CteMutationStrategy( } @Override - public int executeDelete( - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - checkMatch( sqmDelete ); + public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = sqmStatement instanceof SqmDeleteStatement sqmDelete + ? buildHandler( sqmDelete, domainParameterXref, context, firstJdbcParameterBindings) + : buildHandler( (SqmUpdateStatement) sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); + } - final CteDeleteHandler deleteHandler; + public MultiTableHandler buildHandler(SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { + checkMatch( sqmDelete ); if ( rootDescriptor.getSoftDeleteMapping() != null ) { - deleteHandler = new CteSoftDeleteHandler( + return new CteSoftDeleteHandler( idCteTable, sqmDelete, domainParameterXref, this, - sessionFactory + sessionFactory, + context, + firstJdbcParameterBindingsConsumer ); } else { - deleteHandler = new CteDeleteHandler( + return new CteDeleteHandler( idCteTable, sqmDelete, domainParameterXref, this, - sessionFactory + sessionFactory, + context, + firstJdbcParameterBindingsConsumer ); } - return deleteHandler.execute( context ); } - @Override - public int executeUpdate( - SqmUpdateStatement sqmUpdate, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { + public MultiTableHandler buildHandler(SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { checkMatch( sqmUpdate ); - return new CteUpdateHandler( idCteTable, sqmUpdate, domainParameterXref, this, sessionFactory ).execute( context ); + return new CteUpdateHandler( + idCteTable, + sqmUpdate, + domainParameterXref, + this, + sessionFactory, + context, + firstJdbcParameterBindingsConsumer + ); } protected void checkMatch(SqmDeleteOrUpdateStatement sqmStatement) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteSoftDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteSoftDeleteHandler.java index 270b61706015..2df88cf41b77 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteSoftDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteSoftDeleteHandler.java @@ -5,8 +5,10 @@ package org.hibernate.query.sqm.mutation.internal.cte; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.SoftDeleteMapping; import org.hibernate.metamodel.mapping.TableDetails; +import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.sql.ast.tree.MutationStatement; @@ -19,6 +21,7 @@ import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; import java.util.ArrayList; import java.util.Collections; @@ -35,10 +38,13 @@ protected CteSoftDeleteHandler( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory ); + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory, context, firstJdbcParameterBindingsConsumer ); } + @Override protected void applyDmlOperations( CteContainer statement, CteStatement idSelectCte, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java index 749706d28ec4..7718b099b6d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java @@ -13,11 +13,13 @@ import org.hibernate.boot.model.relational.QualifiedNameParser; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.SemanticException; import org.hibernate.query.results.internal.TableGroupImpl; +import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; @@ -46,6 +48,7 @@ import org.hibernate.sql.ast.tree.select.SelectClause; import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.results.internal.SqlSelectionImpl; /** @@ -62,8 +65,10 @@ public CteUpdateHandler( SqmUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory ); + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory, context, firstJdbcParameterBindingsConsumer ); } @Override @@ -74,7 +79,7 @@ protected void addDmlCtes( Map, List> parameterResolutions, SessionFactoryImplementor factory) { final TableGroup updatingTableGroup = sqmConverter.getMutatingTableGroup(); - final SqmUpdateStatement updateStatement = (SqmUpdateStatement) getSqmDeleteOrUpdateStatement(); + final SqmUpdateStatement updateStatement = (SqmUpdateStatement) getSqmStatement(); final EntityMappingType entityDescriptor = getEntityDescriptor(); final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/AbstractInlineHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/AbstractInlineHandler.java new file mode 100644 index 000000000000..ed7c80401ed5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/AbstractInlineHandler.java @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.sqm.mutation.internal.inline; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.metamodel.MappingMetamodel; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.Handler; +import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + + +/** + * DeleteHandler for the in-line strategy + * + * @author Vlad Mihalcea + * @author Steve Ebersole + */ +public abstract class AbstractInlineHandler implements Handler { + private final MatchingIdRestrictionProducer matchingIdsPredicateProducer; + private final EntityPersister entityDescriptor; + private final DomainParameterXref domainParameterXref; + private final CacheableSqmInterpretation matchingIdsInterpretation; + + protected AbstractInlineHandler( + MatchingIdRestrictionProducer matchingIdsPredicateProducer, + SqmDeleteOrUpdateStatement sqmStatement, + DomainParameterXref domainParameterXref, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final SessionFactoryImplementor sessionFactory = context.getSession().getFactory(); + final MappingMetamodel domainModel = sessionFactory.getMappingMetamodel(); + final String mutatingEntityName = sqmStatement.getTarget().getModel().getHibernateEntityName(); + this.entityDescriptor = domainModel.getEntityDescriptor( mutatingEntityName ); + + this.domainParameterXref = domainParameterXref; + this.matchingIdsPredicateProducer = matchingIdsPredicateProducer; + this.matchingIdsInterpretation = MatchingIdSelectionHelper.createMatchingIdsSelect( + sqmStatement, + domainParameterXref, + context, + firstJdbcParameterBindingsConsumer + ); + } + + @Override + public JdbcParameterBindings createJdbcParameterBindings(DomainQueryExecutionContext context) { + return SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + matchingIdsInterpretation.jdbcParamsXref(), + new SqmParameterMappingModelResolutionAccess() { + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) matchingIdsInterpretation.sqmParameterMappingModelTypes().get( parameter ); + } + }, + context.getSession() + ); + } + + @Override + public boolean dependsOnParameterBindings() { + return matchingIdsInterpretation.jdbcOperation().dependsOnParameterBindings(); + } + + @Override + public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + return matchingIdsInterpretation.jdbcOperation().isCompatibleWith( jdbcParameterBindings, queryOptions ); + } + + public EntityPersister getEntityDescriptor() { + return entityDescriptor; + } + + protected MatchingIdRestrictionProducer getMatchingIdsPredicateProducer() { + return matchingIdsPredicateProducer; + } + + protected DomainParameterXref getDomainParameterXref() { + return domainParameterXref; + } + + protected CacheableSqmInterpretation getMatchingIdsInterpretation() { + return matchingIdsInterpretation; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java index ab703d39b5d8..42f712908e59 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineDeleteHandler.java @@ -4,13 +4,16 @@ */ package org.hibernate.query.sqm.mutation.internal.inline; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.MutableInteger; -import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SoftDeleteMapping; import org.hibernate.metamodel.mapping.TableDetails; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; @@ -19,19 +22,20 @@ import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; +import org.hibernate.sql.ast.tree.MutationStatement; import org.hibernate.sql.ast.tree.delete.DeleteStatement; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcMutationExecutor; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.StatementCreatorHelper; -import java.sql.PreparedStatement; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -43,129 +47,83 @@ * @author Vlad Mihalcea * @author Steve Ebersole */ -public class InlineDeleteHandler implements DeleteHandler { - private final MatchingIdRestrictionProducer matchingIdsPredicateProducer; - private final SqmDeleteStatement sqmDeleteStatement; - private final DomainParameterXref domainParameterXref; +public class InlineDeleteHandler extends AbstractInlineHandler implements DeleteHandler { - private final DomainQueryExecutionContext executionContext; - - private final SessionFactoryImplementor sessionFactory; - private final SqlAstTranslatorFactory sqlAstTranslatorFactory; - private final JdbcMutationExecutor jdbcMutationExecutor; + private final List tableDeleters; protected InlineDeleteHandler( MatchingIdRestrictionProducer matchingIdsPredicateProducer, - SqmDeleteStatement sqmDeleteStatement, + SqmDeleteStatement sqmStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - this.sqmDeleteStatement = sqmDeleteStatement; - - this.domainParameterXref = domainParameterXref; - this.matchingIdsPredicateProducer = matchingIdsPredicateProducer; - - this.executionContext = context; - - this.sessionFactory = executionContext.getSession().getFactory(); - this.sqlAstTranslatorFactory = sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); - this.jdbcMutationExecutor = sessionFactory.getJdbcServices().getJdbcMutationExecutor(); - } + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( matchingIdsPredicateProducer, sqmStatement, domainParameterXref, context, firstJdbcParameterBindingsConsumer ); + final List tableDeleters = new ArrayList<>(); - @Override - public int execute(DomainQueryExecutionContext executionContext) { - final List idsAndFks = MatchingIdSelectionHelper.selectMatchingIds( - sqmDeleteStatement, - domainParameterXref, - executionContext - ); - - if ( idsAndFks == null || idsAndFks.isEmpty() ) { - return 0; + final SoftDeleteMapping softDeleteMapping = getEntityDescriptor().getSoftDeleteMapping(); + if ( softDeleteMapping != null ) { + tableDeleters.add( createSoftDeleter() ); } - - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - - final String mutatingEntityName = sqmDeleteStatement.getTarget().getModel().getHibernateEntityName(); - final EntityMappingType entityDescriptor = factory.getMappingMetamodel().getEntityDescriptor( mutatingEntityName ); - - final List inListExpressions = matchingIdsPredicateProducer.produceIdExpressionList( idsAndFks, entityDescriptor ); - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( domainParameterXref.getQueryParameterCount() ); - - // delete from the tables - final MutableInteger valueIndexCounter = new MutableInteger(); - SqmMutationStrategyHelper.visitCollectionTables( - entityDescriptor, - pluralAttribute -> { - if ( pluralAttribute.getSeparateCollectionTable() != null ) { - // this collection has a separate collection table, meaning it is one of: - // 1) element-collection - // 2) many-to-many - // 3) one-to many using a dedicated join-table - // - // in all of these cases, we should clean up the matching rows in the - // collection table - final ModelPart fkTargetPart = pluralAttribute.getKeyDescriptor().getTargetPart(); - final int valueIndex; - if ( fkTargetPart.isEntityIdentifierMapping() ) { - valueIndex = 0; - } - else { - if ( valueIndexCounter.get() == 0 ) { - valueIndexCounter.set( entityDescriptor.getIdentifierMapping().getJdbcTypeCount() ); + else { + // delete from the tables + final MutableInteger valueIndexCounter = new MutableInteger(); + SqmMutationStrategyHelper.visitCollectionTables( + getEntityDescriptor(), + pluralAttribute -> { + if ( pluralAttribute.getSeparateCollectionTable() != null ) { + // this collection has a separate collection table, meaning it is one of: + // 1) element-collection + // 2) many-to-many + // 3) one-to many using a dedicated join-table + // + // in all of these cases, we should clean up the matching rows in the + // collection table + final ModelPart fkTargetPart = pluralAttribute.getKeyDescriptor().getTargetPart(); + final int valueIndex; + if ( fkTargetPart.isEntityIdentifierMapping() ) { + valueIndex = 0; } - valueIndex = valueIndexCounter.get(); - valueIndexCounter.plus( fkTargetPart.getJdbcTypeCount() ); + else { + if ( valueIndexCounter.get() == 0 ) { + valueIndexCounter.set( + getEntityDescriptor().getIdentifierMapping().getJdbcTypeCount() ); + } + valueIndex = valueIndexCounter.get(); + valueIndexCounter.plus( fkTargetPart.getJdbcTypeCount() ); + } + final NamedTableReference targetTableReference = new NamedTableReference( + pluralAttribute.getSeparateCollectionTable(), + DeleteStatement.DEFAULT_ALIAS + ); + + tableDeleters.add( new TableDeleter( + new DeleteStatement( targetTableReference, null ), + () -> fkTargetPart::forEachSelectable, + valueIndex, + fkTargetPart + ) ); } - - executeDelete( - pluralAttribute.getSeparateCollectionTable(), - entityDescriptor, - () -> fkTargetPart::forEachSelectable, - inListExpressions, - valueIndex, - fkTargetPart, - jdbcParameterBindings, - executionContext - ); } - } - ); - - final SoftDeleteMapping softDeleteMapping = entityDescriptor.getSoftDeleteMapping(); - if ( softDeleteMapping != null ) { - performSoftDelete( - entityDescriptor, - inListExpressions, - jdbcParameterBindings, - executionContext ); - } - else { - entityDescriptor.visitConstraintOrderedTables( - (tableExpression, tableKeyColumnsVisitationSupplier) -> executeDelete( - tableExpression, - entityDescriptor, + + getEntityDescriptor().visitConstraintOrderedTables( + (tableExpression, tableKeyColumnsVisitationSupplier) -> tableDeleters.add( new TableDeleter( + new DeleteStatement( + new NamedTableReference( tableExpression, DeleteStatement.DEFAULT_ALIAS ), + null + ), tableKeyColumnsVisitationSupplier, - inListExpressions, 0, - null, - jdbcParameterBindings, - executionContext - ) + null + ) ) ); } - return idsAndFks.size(); + this.tableDeleters = tableDeleters; } - /** - * Perform a soft-delete, which just needs to update the root table - */ - private void performSoftDelete( - EntityMappingType entityDescriptor, - List idExpressions, - JdbcParameterBindings jdbcParameterBindings, - DomainQueryExecutionContext executionContext) { + private TableDeleter createSoftDeleter() { + final EntityPersister entityDescriptor = getEntityDescriptor(); final TableDetails softDeleteTable = entityDescriptor.getSoftDeleteTableDetails(); final SoftDeleteMapping softDeleteMapping = entityDescriptor.getSoftDeleteMapping(); assert softDeleteMapping != null; @@ -175,83 +133,111 @@ private void performSoftDelete( DeleteStatement.DEFAULT_ALIAS ); - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); - - final Predicate matchingIdsPredicate = matchingIdsPredicateProducer.produceRestriction( - idExpressions, - entityDescriptor, - 0, - entityDescriptor.getIdentifierMapping(), - targetTableReference, - null, - executionContextAdapter - ); - - final Predicate predicate = Predicate.combinePredicates( - matchingIdsPredicate, - softDeleteMapping.createNonDeletedRestriction( targetTableReference ) - ); final Assignment softDeleteAssignment = softDeleteMapping.createSoftDeleteAssignment( targetTableReference ); final UpdateStatement updateStatement = new UpdateStatement( targetTableReference, Collections.singletonList( softDeleteAssignment ), - predicate + softDeleteMapping.createNonDeletedRestriction( targetTableReference ) ); - final JdbcOperationQueryMutation jdbcOperation = sqlAstTranslatorFactory - .buildMutationTranslator( sessionFactory, updateStatement ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + return new TableDeleter( updateStatement, null, 0, entityDescriptor.getIdentifierMapping() ); + } - jdbcMutationExecutor.execute( - jdbcOperation, + @Override + public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext) { + final List idsAndFks = MatchingIdSelectionHelper.selectMatchingIds( + getMatchingIdsInterpretation(), jdbcParameterBindings, - this::prepareQueryStatement, - (integer, preparedStatement) -> {}, - executionContextAdapter + executionContext ); - } - private void executeDelete( - String targetTableExpression, - EntityMappingType entityDescriptor, - Supplier> tableKeyColumnsVisitationSupplier, - List idExpressions, - int valueIndex, - ModelPart valueModelPart, - JdbcParameterBindings jdbcParameterBindings, - DomainQueryExecutionContext executionContext) { - final NamedTableReference targetTableReference = new NamedTableReference( - targetTableExpression, - DeleteStatement.DEFAULT_ALIAS - ); + if ( idsAndFks == null || idsAndFks.isEmpty() ) { + return 0; + } + final List inListExpressions = getMatchingIdsPredicateProducer().produceIdExpressionList( idsAndFks, getEntityDescriptor() ); + final SharedSessionContractImplementor session = executionContext.getSession(); + final SessionFactoryImplementor sessionFactory = session.getFactory(); + final JdbcMutationExecutor jdbcMutationExecutor = sessionFactory.getJdbcServices().getJdbcMutationExecutor(); final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); - final Predicate matchingIdsPredicate = matchingIdsPredicateProducer.produceRestriction( - idExpressions, - entityDescriptor, - valueIndex, - valueModelPart, - targetTableReference, - tableKeyColumnsVisitationSupplier, - executionContextAdapter - ); - - final DeleteStatement deleteStatement = new DeleteStatement( targetTableReference, matchingIdsPredicate ); + for ( TableDeleter tableDeleter : tableDeleters ) { + jdbcMutationExecutor.execute( + createMutation( + tableDeleter, + inListExpressions, + executionContextAdapter + ), + JdbcParameterBindings.NO_BINDINGS, + sql -> session.getJdbcCoordinator().getStatementPreparer().prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContextAdapter + ); + } - final JdbcOperationQueryMutation jdbcOperation = sqlAstTranslatorFactory.buildMutationTranslator( sessionFactory, deleteStatement ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + return idsAndFks.size(); + } - jdbcMutationExecutor.execute( - jdbcOperation, - jdbcParameterBindings, - this::prepareQueryStatement, - (integer, preparedStatement) -> {}, - executionContextAdapter - ); + protected record TableDeleter( + AbstractUpdateOrDeleteStatement statement, + @Nullable Supplier> tableKeyColumnsVisitationSupplier, + int valueIndex, + @Nullable ModelPart valueModelPart + ) {} + + // For Hibernate Reactive + protected JdbcOperationQueryMutation createMutation(TableDeleter tableDeleter, List inListExpressions, ExecutionContext executionContext) { + final MutationStatement statement; + if ( tableDeleter.statement instanceof UpdateStatement updateStatement ) { + statement = new UpdateStatement( + updateStatement, + updateStatement.getTargetTable(), + updateStatement.getFromClause(), + updateStatement.getAssignments(), + Predicate.combinePredicates( + updateStatement.getRestriction(), + getMatchingIdsPredicateProducer().produceRestriction( + inListExpressions, + getEntityDescriptor(), + tableDeleter.valueIndex, + tableDeleter.valueModelPart, + updateStatement.getTargetTable(), + tableDeleter.tableKeyColumnsVisitationSupplier, + executionContext + ) + ), + updateStatement.getReturningColumns() + ); + } + else { + final DeleteStatement deleteStatement = (DeleteStatement) tableDeleter.statement; + statement = new DeleteStatement( + deleteStatement, + deleteStatement.getTargetTable(), + deleteStatement.getFromClause(), + Predicate.combinePredicates( + deleteStatement.getRestriction(), + getMatchingIdsPredicateProducer().produceRestriction( + inListExpressions, + getEntityDescriptor(), + tableDeleter.valueIndex, + tableDeleter.valueModelPart, + deleteStatement.getTargetTable(), + tableDeleter.tableKeyColumnsVisitationSupplier, + executionContext + ) + ), + deleteStatement.getReturningColumns() + ); + } + final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); + final SqlAstTranslatorFactory sqlAstTranslatorFactory = sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory(); + return sqlAstTranslatorFactory.buildMutationTranslator( sessionFactory, statement ) + .translate( JdbcParameterBindings.NO_BINDINGS, executionContext.getQueryOptions() ); } - private PreparedStatement prepareQueryStatement(String sql) { - return StatementCreatorHelper.prepareQueryStatement( sql, executionContext.getSession() ); + // For Hibernate Reactive + protected List getTableDeleters() { + return tableDeleters; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineMutationStrategy.java index ce4992b99eee..b07cd6fe4790 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineMutationStrategy.java @@ -7,12 +7,16 @@ import java.util.function.Function; import org.hibernate.dialect.Dialect; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * Support for multi-table SQM mutation operations which select the matching id values from the database back into @@ -39,31 +43,31 @@ public InlineMutationStrategy(Function,MatchingIdR } @Override - public int executeUpdate( - SqmUpdateStatement sqmUpdate, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - final InlineUpdateHandler handler = new InlineUpdateHandler( + public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = sqmStatement instanceof SqmDeleteStatement sqmDelete + ? buildHandler( sqmDelete, domainParameterXref, context, firstJdbcParameterBindings ) + : buildHandler( (SqmUpdateStatement) sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); + } + + public MultiTableHandler buildHandler(SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { + return new InlineUpdateHandler( matchingIdsStrategy.apply( sqmUpdate ), sqmUpdate, domainParameterXref, - context + context, + firstJdbcParameterBindingsConsumer ); - return handler.execute( context ); } - @Override - public int executeDelete( - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - final InlineDeleteHandler deleteHandler = new InlineDeleteHandler( + public MultiTableHandler buildHandler(SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { + return new InlineDeleteHandler( matchingIdsStrategy.apply( sqmDelete ), sqmDelete, domainParameterXref, - context + context, + firstJdbcParameterBindingsConsumer ); - - return deleteHandler.execute( context ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java index 834cf7441abe..233c32890c22 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java @@ -4,26 +4,20 @@ */ package org.hibernate.query.sqm.mutation.internal.inline; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; - +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.SemanticException; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; @@ -32,18 +26,21 @@ import org.hibernate.query.sqm.mutation.internal.UpdateHandler; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.sql.SqmTranslation; +import org.hibernate.query.sqm.sql.SqmTranslator; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.spi.SqlAliasBaseImpl; import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.MutationStatement; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupJoin; +import org.hibernate.sql.ast.tree.from.TableGroupProducer; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; import org.hibernate.sql.ast.tree.from.UnionTableReference; @@ -51,69 +48,64 @@ import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; import org.hibernate.sql.ast.tree.insert.Values; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; -import org.hibernate.sql.ast.tree.predicate.InListPredicate; import org.hibernate.sql.ast.tree.predicate.NullnessPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.ast.tree.select.SortSpecification; import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.ast.tree.update.UpdateStatement; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.SqlSelectionImpl; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static org.hibernate.internal.util.NullnessUtil.castNonNull; + /** * @author Steve Ebersole */ -public class InlineUpdateHandler implements UpdateHandler { - private final SqmUpdateStatement sqmUpdate; - private final DomainParameterXref domainParameterXref; - private final MatchingIdRestrictionProducer matchingIdsPredicateProducer; - private final SessionFactoryImplementor sessionFactory; +public class InlineUpdateHandler extends AbstractInlineHandler implements UpdateHandler { + + private final Map, Map, List>> jdbcParamsXref; + private final Map, MappingModelExpressible> resolvedParameterMappingModelTypes; + + private final List tableUpdaters; public InlineUpdateHandler( MatchingIdRestrictionProducer matchingIdsPredicateProducer, - SqmUpdateStatement sqmUpdate, + SqmUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - this.matchingIdsPredicateProducer = matchingIdsPredicateProducer; - this.domainParameterXref = domainParameterXref; - this.sqmUpdate = sqmUpdate; - this.sessionFactory = context.getSession().getFactory(); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - final List ids = MatchingIdSelectionHelper.selectMatchingIds( - sqmUpdate, - domainParameterXref, - executionContext - ); - - if ( ids == null || ids.isEmpty() ) { - return 0; - } + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindings) { + super( matchingIdsPredicateProducer, sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + // Clear expansions created by matching id select statement domainParameterXref.clearExpansions(); - final MappingMetamodel domainModel = sessionFactory.getMappingMetamodel(); - - final String mutatingEntityName = sqmUpdate.getTarget().getModel().getHibernateEntityName(); - final EntityPersister entityDescriptor = domainModel.getEntityDescriptor( mutatingEntityName ); - final List inListExpressions = matchingIdsPredicateProducer.produceIdExpressionList( ids, entityDescriptor ); + final SessionFactoryImplementor sessionFactory = context.getSession().getFactory(); + final SqmTranslator translator = sessionFactory.getQueryEngine() + .getSqmTranslatorFactory() + .createMutationTranslator( + sqmStatement, + context.getQueryOptions(), + domainParameterXref, + context.getQueryParameterBindings(), + context.getSession().getLoadQueryInfluencers(), + sessionFactory.getSqlTranslationEngine() + ); //noinspection unchecked - final SqmTranslation translation = (SqmTranslation) - sessionFactory.getQueryEngine().getSqmTranslatorFactory() - .createMutationTranslator( - sqmUpdate, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - sessionFactory.getSqlTranslationEngine() - ) - .translate(); + final SqmTranslation translation = (SqmTranslation) translator.translate(); final TableGroup updatingTableGroup = translation.getSqlAst().getFromClause().getRoots().get( 0 ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -126,21 +118,6 @@ public int execute(DomainQueryExecutionContext executionContext) { collectTableReference( updatingTableGroup.getTableReferenceJoins().get( i ), tableReferenceByAlias::put ); } - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - SqmUtil.generateJdbcParamsXref( domainParameterXref, translation::getJdbcParamsBySqmParam ), - new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) translation.getSqmParameterMappingModelTypeResolutions().get( parameter ); - } - }, - executionContext.getSession() - ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // segment the assignments by table-reference final Map> assignmentsByTable = new HashMap<>(); @@ -172,36 +149,110 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter tableUpdaters = new ArrayList<>(); + final ExecutionContext executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + getEntityDescriptor().visitConstraintOrderedTables( + (tableExpression, tableKeyColumnVisitationSupplier) -> { + final TableUpdater tableUpdater = createTableUpdater( + tableExpression, + tableKeyColumnVisitationSupplier, + getEntityDescriptor(), + updatingTableGroup, + assignmentsByTable, + executionContext + ); + if ( tableUpdater != null ) { + tableUpdaters.add( tableUpdater ); + } + } + ); + this.tableUpdaters = tableUpdaters; - final int rows = ids.size(); + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, translator ); + this.resolvedParameterMappingModelTypes = translation.getSqmParameterMappingModelTypeResolutions(); - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); - entityDescriptor.visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> updateTable( - tableExpression, - tableKeyColumnVisitationSupplier, - entityDescriptor, - updatingTableGroup, - assignmentsByTable, - inListExpressions, - rows, - jdbcParameterBindings, - executionContextAdapter - ) + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + firstJdbcParameterBindings.get().visitBindings( jdbcParameterBindings::addBinding ); + firstJdbcParameterBindings.set( jdbcParameterBindings ); + } + + @Override + public JdbcParameterBindings createJdbcParameterBindings(DomainQueryExecutionContext context) { + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + getDomainParameterXref(), + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override + @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + // super.createJdbcParameterBindings() is for the matching id select statement only, + // so combine the bindings for the update statement with the ones for the select statement + super.createJdbcParameterBindings( context ).visitBindings( jdbcParameterBindings::addBinding ); + return jdbcParameterBindings; + } + + @Override + public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext) { + final List ids = MatchingIdSelectionHelper.selectMatchingIds( + getMatchingIdsInterpretation(), + jdbcParameterBindings, + executionContext ); + if ( ids == null || ids.isEmpty() ) { + return 0; + } + + final List inListExpressions = getMatchingIdsPredicateProducer().produceIdExpressionList( ids, getEntityDescriptor() ); + final int rows = ids.size(); + + final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); + for ( TableUpdater tableUpdater : tableUpdaters ) { + updateTable( + tableUpdater, + inListExpressions, + rows, + jdbcParameterBindings, + executionContextAdapter + ); + } return rows; } - private void updateTable( + protected record TableUpdater( + UpdateStatement updateStatement, + @Nullable InsertSelectStatement nullableInsert, + Supplier> tableKeyColumnVisitationSupplier + ) {} + + // For Hibernate Reactive + protected List getTableUpdaters() { + return tableUpdaters; + } + + private TableUpdater createTableUpdater( String tableExpression, Supplier> tableKeyColumnVisitationSupplier, EntityPersister entityDescriptor, TableGroup updatingTableGroup, Map> assignmentsByTable, - List inListExpressions, - int expectedUpdateCount, - JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { final TableReference updatingTableReference = updatingTableGroup.getTableReference( updatingTableGroup.getNavigablePath(), @@ -212,7 +263,7 @@ private void updateTable( final List assignments = assignmentsByTable.get( updatingTableReference ); if ( assignments == null || assignments.isEmpty() ) { // no assignments for this table - skip it - return; + return null; } @@ -220,45 +271,39 @@ private void updateTable( // create the in-subquery predicate to restrict the updates to just // matching ids - final InListPredicate idListPredicate = (InListPredicate) matchingIdsPredicateProducer.produceRestriction( - inListExpressions, - entityDescriptor, - 0, - null, - updatingTableReference, - tableKeyColumnVisitationSupplier, - executionContext - ); - final Expression keyExpression = idListPredicate.getTestExpression(); + final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + final int idColumnCount = identifierMapping.getJdbcTypeCount(); + assert idColumnCount > 0; + final Expression keyExpression; + if ( idColumnCount == 1 ) { + final BasicValuedModelPart basicIdMapping = castNonNull( identifierMapping.asBasicValuedModelPart() ); + final String idColumn = basicIdMapping.getSelectionExpression(); + keyExpression = new ColumnReference( + updatingTableReference, + idColumn, + // id columns cannot be formulas and cannot have custom read and write expressions + false, + null, + basicIdMapping.getJdbcMapping() + ); + } + else { + final List columnReferences = new ArrayList<>( idColumnCount ); + tableKeyColumnVisitationSupplier.get().accept( (columnIndex, selection) -> columnReferences.add( + new ColumnReference( updatingTableReference, selection ) + ) ); + keyExpression = new SqlTuple( columnReferences, identifierMapping ); + } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Create the SQL AST and convert it into a JdbcOperation final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); - final UpdateStatement sqlAst = new UpdateStatement( dmlTableReference, assignments, idListPredicate ); + final UpdateStatement sqlAst = new UpdateStatement( dmlTableReference, assignments, null ); + final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - final JdbcOperationQueryMutation jdbcUpdate = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, sqlAst ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - - final int updateCount = jdbcServices.getJdbcMutationExecutor().execute( - jdbcUpdate, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> { - }, - executionContext - ); - if ( updateCount == expectedUpdateCount ) { - // We are done when the update count matches - return; - } // Otherwise we have to check if the table is nullable, and if so, insert into that table final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); boolean isNullable = false; @@ -268,20 +313,11 @@ private void updateTable( break; } } + final InsertSelectStatement insertSqlAst; if ( isNullable ) { // Copy the subquery contents into a root query final QuerySpec querySpec = new QuerySpec( true ); final NavigablePath valuesPath = new NavigablePath( "id" ); - final List valuesList = new ArrayList<>( inListExpressions.size() ); - for ( Expression inListExpression : inListExpressions ) { - if ( inListExpression instanceof SqlTuple ) { - //noinspection unchecked - valuesList.add( new Values( (List) ( (SqlTuple) inListExpression ).getExpressions() ) ); - } - else { - valuesList.add( new Values( Collections.singletonList( inListExpression ) ) ); - } - } final TableGroup rootTableGroup = entityDescriptor.createRootTableGroup( true, updatingTableGroup.getNavigablePath(), @@ -358,7 +394,7 @@ private void updateTable( final ValuesTableGroup valuesTableGroup = new ValuesTableGroup( valuesPath, null, - valuesList, + new ArrayList<>(), valuesPath.getLocalName(), columnNames, true, @@ -403,19 +439,155 @@ private void updateTable( ); } - final InsertSelectStatement insertSqlAst = new InsertSelectStatement( - dmlTableReference - ); + insertSqlAst = new InsertSelectStatement( dmlTableReference ); insertSqlAst.addTargetColumnReferences( targetColumnReferences.toArray( new ColumnReference[0] ) ); insertSqlAst.setSourceSelectStatement( querySpec ); + } + else { + insertSqlAst = null; + } + return new TableUpdater( sqlAst, insertSqlAst, tableKeyColumnVisitationSupplier ); + } + + // For Hibernate Reactive + protected JdbcOperationQueryMutation createTableUpdate( + TableUpdater tableUpdater, + List inListExpressions, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // create the in-subquery predicate to restrict the updates to just + // matching ids + + final UpdateStatement updateStatement = new UpdateStatement( + tableUpdater.updateStatement, + tableUpdater.updateStatement.getTargetTable(), + tableUpdater.updateStatement.getFromClause(), + tableUpdater.updateStatement.getAssignments(), + Predicate.combinePredicates( + tableUpdater.updateStatement.getRestriction(), + getMatchingIdsPredicateProducer().produceRestriction( + inListExpressions, + getEntityDescriptor(), + 0, + null, + tableUpdater.updateStatement.getTargetTable(), + tableUpdater.tableKeyColumnVisitationSupplier, + executionContext + ) + ), + tableUpdater.updateStatement.getReturningColumns() + ); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Create the SQL AST and convert it into a JdbcOperation + + final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); + final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); + return jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( sessionFactory, updateStatement ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + } + + // For Hibernate Reactive + protected JdbcOperationQueryMutation createTableInsert( + TableUpdater tableUpdater, + List inListExpressions, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); + final InsertSelectStatement insertStatement = new InsertSelectStatement( + tableUpdater.nullableInsert, + tableUpdater.nullableInsert.getTargetTable(), + tableUpdater.nullableInsert.getReturningColumns() + ); + final QuerySpec originalQuerySpec = (QuerySpec) tableUpdater.nullableInsert.getSourceSelectStatement(); + assert originalQuerySpec.getFromClause().getRoots().size() == 1; + + final QuerySpec querySpec = new QuerySpec( true, 1 ); + + // Copy over everything except the FromClause + querySpec.getSelectClause().makeDistinct( originalQuerySpec.getSelectClause().isDistinct() ); + for ( SqlSelection sqlSelection : originalQuerySpec.getSelectClause().getSqlSelections() ) { + querySpec.getSelectClause().addSqlSelection( sqlSelection ); + } + querySpec.applyPredicate( originalQuerySpec.getWhereClauseRestrictions() ); + querySpec.setGroupByClauseExpressions( originalQuerySpec.getGroupByClauseExpressions() ); + querySpec.setHavingClauseRestrictions( originalQuerySpec.getHavingClauseRestrictions() ); + for ( SortSpecification sortSpecification : originalQuerySpec.getSortSpecifications() ) { + querySpec.addSortSpecification( sortSpecification ); + } + querySpec.setOffsetClauseExpression( originalQuerySpec.getOffsetClauseExpression() ); + querySpec.setFetchClauseExpression( originalQuerySpec.getFetchClauseExpression(), + originalQuerySpec.getFetchClauseType() ); + + // Rebuild the from clause values list based on the inListExpressions + final List valuesList = new ArrayList<>( inListExpressions.size() ); + for ( Expression inListExpression : inListExpressions ) { + if ( inListExpression instanceof SqlTuple ) { + //noinspection unchecked + valuesList.add( new Values( (List) ((SqlTuple) inListExpression).getExpressions() ) ); + } + else { + valuesList.add( new Values( Collections.singletonList( inListExpression ) ) ); + } + } + + final ValuesTableGroup originalValuesTableGroup = + (ValuesTableGroup) originalQuerySpec.getFromClause().getRoots().get( 0 ); + final ValuesTableGroup valuesTableGroup = new ValuesTableGroup( + originalValuesTableGroup.getNavigablePath(), + (TableGroupProducer) originalValuesTableGroup.getModelPart(), + valuesList, + originalValuesTableGroup.getNavigablePath().getLocalName(), + originalValuesTableGroup.getPrimaryTableReference().getColumnNames(), + originalValuesTableGroup.canUseInnerJoins(), + sessionFactory + ); + valuesTableGroup.addNestedTableGroupJoin( originalValuesTableGroup.getNestedTableGroupJoins().get( 0 ) ); + querySpec.getFromClause().addRoot( valuesTableGroup ); + + insertStatement.addTargetColumnReferences( tableUpdater.nullableInsert.getTargetColumns() ); + insertStatement.setSourceSelectStatement( querySpec ); + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Create the SQL AST and convert it into a JdbcOperation + + final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); + return jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( sessionFactory, insertStatement ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + } - final JdbcOperationQueryMutation jdbcInsert = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, insertSqlAst ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + private void updateTable( + TableUpdater tableUpdater, + List inListExpressions, + int expectedUpdateCount, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); + final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); + final int updateCount = jdbcServices.getJdbcMutationExecutor().execute( + createTableUpdate( tableUpdater, inListExpressions, jdbcParameterBindings, executionContext ), + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + if ( updateCount == expectedUpdateCount ) { + // We are done when the update count matches + return; + } + if ( tableUpdater.nullableInsert != null ) { final int insertCount = jdbcServices.getJdbcMutationExecutor().execute( - jdbcInsert, + createTableInsert( tableUpdater, inListExpressions, jdbcParameterBindings, executionContext ), jdbcParameterBindings, sql -> executionContext.getSession() .getJdbcCoordinator() @@ -429,18 +601,6 @@ private void updateTable( } } - private Expression asExpression(SelectClause selectClause) { - final List sqlSelections = selectClause.getSqlSelections(); - if ( sqlSelections.size() == 1 ) { - return sqlSelections.get( 0 ).getExpression(); - } - final List expressions = new ArrayList<>( sqlSelections.size() ); - for ( SqlSelection sqlSelection : sqlSelections ) { - expressions.add( sqlSelection.getExpression() ); - } - return new SqlTuple( expressions, null ); - } - private void collectTableReference( TableReference tableReference, BiConsumer consumer) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/AbstractDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/AbstractDeleteExecutionDelegate.java deleted file mode 100644 index 20e27d00055d..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/AbstractDeleteExecutionDelegate.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.query.sqm.mutation.internal.temptable; - -import java.util.function.Function; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; -import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; - -/** - * @author Steve Ebersole - */ -public abstract class AbstractDeleteExecutionDelegate implements TableBasedDeleteHandler.ExecutionDelegate { - private final EntityMappingType entityDescriptor; - private final TemporaryTable idTable; - private final TemporaryTableStrategy temporaryTableStrategy; - private final boolean forceDropAfterUse; - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; - private final SessionFactoryImplementor sessionFactory; - private final Function sessionUidAccess; - - private final MultiTableSqmMutationConverter converter; - - public AbstractDeleteExecutionDelegate( - EntityMappingType entityDescriptor, - TemporaryTable idTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - QueryOptions queryOptions, - LoadQueryInfluencers loadQueryInfluencers, - QueryParameterBindings queryParameterBindings, - Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - this.entityDescriptor = entityDescriptor; - this.idTable = idTable; - this.temporaryTableStrategy = temporaryTableStrategy; - this.forceDropAfterUse = forceDropAfterUse; - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; - this.sessionFactory = sessionFactory; - this.sessionUidAccess = sessionUidAccess; - - this.converter = new MultiTableSqmMutationConverter( - entityDescriptor, - getSqmDelete(), - getSqmDelete().getTarget(), - getDomainParameterXref(), - queryOptions, - loadQueryInfluencers, - queryParameterBindings, - sessionFactory.getSqlTranslationEngine() - ); - } - - public EntityMappingType getEntityDescriptor() { - return entityDescriptor; - } - - public TemporaryTable getIdTable() { - return idTable; - } - - public TemporaryTableStrategy getTemporaryTableStrategy() { - return temporaryTableStrategy; - } - - public AfterUseAction getAfterUseAction() { - return forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(); - } - - public SqmDeleteStatement getSqmDelete() { - return sqmDelete; - } - - public DomainParameterXref getDomainParameterXref() { - return domainParameterXref; - } - - public SessionFactoryImplementor getSessionFactory() { - return sessionFactory; - } - - public Function getSessionUidAccess() { - return sessionUidAccess; - } - - public MultiTableSqmMutationConverter getConverter() { - return converter; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java index 1908c5b2a7d8..da2b5ec53728 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java @@ -18,10 +18,10 @@ import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.sqm.ComparisonOperator; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; @@ -29,7 +29,7 @@ import org.hibernate.sql.SimpleSelect; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.QueryLiteral; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.from.StandardTableGroup; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -40,12 +40,14 @@ import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.results.internal.SqlSelectionImpl; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Map; import java.util.UUID; import java.util.function.Function; @@ -59,11 +61,11 @@ public final class ExecuteWithTemporaryTableHelper { private ExecuteWithTemporaryTableHelper() { } - public static int saveMatchingIdsIntoIdTable( + public static CacheableSqmInterpretation createMatchingIdsIntoIdTableInsert( MultiTableSqmMutationConverter sqmConverter, Predicate suppliedPredicate, TemporaryTable idTable, - Function sessionUidAccess, + JdbcParameter sessionUidParameter, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup(); @@ -111,21 +113,90 @@ public static int saveMatchingIdsIntoIdTable( } ); + final SharedSessionContractImplementor session = executionContext.getSession(); if ( idTable.getSessionUidColumn() != null ) { final int jdbcPosition = matchingIdSelection.getSelectClause().getSqlSelections().size(); matchingIdSelection.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - jdbcPosition, - new QueryLiteral<>( - UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ), - (BasicValuedMapping) idTable.getSessionUidColumn().getJdbcMapping() - ) - ) + new SqlSelectionImpl( jdbcPosition, sessionUidParameter ) ); } matchingIdSelection.applyPredicate( suppliedPredicate ); - return saveIntoTemporaryTable( idTableInsert, jdbcParameterBindings, executionContext ); + + final var factory = session.getFactory(); + final JdbcEnvironment jdbcEnvironment = factory.getJdbcServices().getJdbcEnvironment(); + final LockOptions lockOptions = executionContext.getQueryOptions().getLockOptions(); + final LockMode lockMode = lockOptions.getLockMode(); + // Acquire a WRITE lock for the rows that are about to be modified + lockOptions.setLockMode( LockMode.WRITE ); + // Visit the table joins and reset the lock mode if we encounter OUTER joins that are not supported + final QueryPart sourceSelectStatement = idTableInsert.getSourceSelectStatement(); + if ( sourceSelectStatement != null + && !jdbcEnvironment.getDialect().supportsOuterJoinForUpdate() ) { + sourceSelectStatement.visitQuerySpecs( + querySpec -> { + querySpec.getFromClause().visitTableJoins( + tableJoin -> { + if ( tableJoin.isInitialized() + && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { + lockOptions.setLockMode( lockMode ); + } + } + ); + } + ); + } + final var jdbcInsert = jdbcEnvironment.getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, idTableInsert ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + lockOptions.setLockMode( lockMode ); + + return new CacheableSqmInterpretation<>( + idTableInsert, + jdbcInsert, + Map.of(), + Map.of() + ); + } + + public static CacheableSqmInterpretation createTemporaryTableInsert( + InsertSelectStatement temporaryTableInsert, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + final var factory = executionContext.getSession().getFactory(); + final JdbcServices jdbcServices = factory.getJdbcServices(); + final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); + final LockOptions lockOptions = executionContext.getQueryOptions().getLockOptions(); + final LockMode lockMode = lockOptions.getLockMode(); + // Acquire a WRITE lock for the rows that are about to be modified + lockOptions.setLockMode( LockMode.WRITE ); + // Visit the table joins and reset the lock mode if we encounter OUTER joins that are not supported + final QueryPart sourceSelectStatement = temporaryTableInsert.getSourceSelectStatement(); + if ( sourceSelectStatement != null + && !jdbcEnvironment.getDialect().supportsOuterJoinForUpdate() ) { + sourceSelectStatement.visitQuerySpecs( + querySpec -> { + querySpec.getFromClause().visitTableJoins( + tableJoin -> { + if ( tableJoin.isInitialized() + && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { + lockOptions.setLockMode( lockMode ); + } + } + ); + } + ); + } + final var jdbcInsert = jdbcEnvironment.getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, temporaryTableInsert ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + lockOptions.setLockMode( lockMode ); + return new CacheableSqmInterpretation<>( + temporaryTableInsert, + jdbcInsert, + Map.of(), + Map.of() + ); } public static int saveIntoTemporaryTable( @@ -142,13 +213,13 @@ public static int saveIntoTemporaryTable( // Visit the table joins and reset the lock mode if we encounter OUTER joins that are not supported final QueryPart sourceSelectStatement = temporaryTableInsert.getSourceSelectStatement(); if ( sourceSelectStatement != null - && !jdbcEnvironment.getDialect().supportsOuterJoinForUpdate() ) { + && !jdbcEnvironment.getDialect().supportsOuterJoinForUpdate() ) { sourceSelectStatement.visitQuerySpecs( querySpec -> { querySpec.getFromClause().visitTableJoins( tableJoin -> { if ( tableJoin.isInitialized() - && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { + && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { lockOptions.setLockMode( lockMode ); } } @@ -156,13 +227,18 @@ public static int saveIntoTemporaryTable( } ); } - final var jdbcInsert = - jdbcEnvironment.getSqlAstTranslatorFactory() + final var jdbcInsert = jdbcEnvironment.getSqlAstTranslatorFactory() .buildMutationTranslator( factory, temporaryTableInsert ) .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); lockOptions.setLockMode( lockMode ); + return saveIntoTemporaryTable( jdbcInsert, jdbcParameterBindings, executionContext ); + } - return jdbcServices.getJdbcMutationExecutor().execute( + public static int saveIntoTemporaryTable( + JdbcOperationQueryMutation jdbcInsert, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + return executionContext.getSession().getFactory().getJdbcServices().getJdbcMutationExecutor().execute( jdbcInsert, jdbcParameterBindings, sql -> executionContext.getSession().getJdbcCoordinator() @@ -174,16 +250,16 @@ public static int saveIntoTemporaryTable( public static QuerySpec createIdTableSelectQuerySpec( TemporaryTable idTable, - Function sessionUidAccess, + JdbcParameter sessionUidParameter, EntityMappingType entityDescriptor, ExecutionContext executionContext) { - return createIdTableSelectQuerySpec( idTable, null, sessionUidAccess, entityDescriptor, executionContext ); + return createIdTableSelectQuerySpec( idTable, null, sessionUidParameter, entityDescriptor, executionContext ); } public static QuerySpec createIdTableSelectQuerySpec( TemporaryTable idTable, ModelPart fkModelPart, - Function sessionUidAccess, + JdbcParameter sessionUidParameter, EntityMappingType entityDescriptor, ExecutionContext executionContext) { final QuerySpec querySpec = new QuerySpec( false ); @@ -206,7 +282,7 @@ public static QuerySpec createIdTableSelectQuerySpec( querySpec.getFromClause().addRoot( idTableGroup ); applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart, entityDescriptor ); - applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidAccess, executionContext ); + applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidParameter, executionContext ); return querySpec; } @@ -261,7 +337,7 @@ private static void applyIdTableRestrictions( QuerySpec querySpec, TableReference idTableReference, TemporaryTable idTable, - Function sessionUidAccess, + JdbcParameter sessionUidParameter, ExecutionContext executionContext) { if ( idTable.getSessionUidColumn() != null ) { querySpec.applyPredicate( @@ -274,10 +350,7 @@ private static void applyIdTableRestrictions( idTable.getSessionUidColumn().getJdbcMapping() ), ComparisonOperator.EQUAL, - new QueryLiteral<>( - UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ), - (BasicValuedMapping) idTable.getSessionUidColumn().getJdbcMapping() - ) + sessionUidParameter ) ); } @@ -336,21 +409,19 @@ public static int[] loadInsertedRowNumbers( Function sessionUidAccess, int rows, ExecutionContext executionContext) { - final TemporaryTableSessionUidColumn sessionUidColumn = temporaryTable.getSessionUidColumn(); - - final TemporaryTableColumn rowNumberColumn = temporaryTable.getColumns() - .get( temporaryTable.getColumns().size() - (sessionUidColumn == null ? 1 : 2 ) ); - assert rowNumberColumn != null; + final String sqlSelect = + createInsertedRowNumbersSelectSql( temporaryTable, sessionUidAccess, executionContext ); + return loadInsertedRowNumbers( sqlSelect, temporaryTable, sessionUidAccess, rows, executionContext ); + } + public static int[] loadInsertedRowNumbers( + String sqlSelect, + TemporaryTable temporaryTable, + Function sessionUidAccess, + int rows, + ExecutionContext executionContext) { + final TemporaryTableSessionUidColumn sessionUidColumn = temporaryTable.getSessionUidColumn(); final SharedSessionContractImplementor session = executionContext.getSession(); - final SimpleSelect simpleSelect = new SimpleSelect( session.getFactory() ) - .setTableName( temporaryTable.getQualifiedTableName() ) - .addColumn( rowNumberColumn.getColumnName() ); - if ( sessionUidColumn != null ) { - simpleSelect.addRestriction( sessionUidColumn.getColumnName() ); - } - final String sqlSelect = simpleSelect.toStatementString(); - final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); PreparedStatement preparedStatement = null; try { @@ -393,6 +464,26 @@ public static int[] loadInsertedRowNumbers( } } + public static String createInsertedRowNumbersSelectSql( + TemporaryTable temporaryTable, + Function sessionUidAccess, + ExecutionContext executionContext) { + final TemporaryTableSessionUidColumn sessionUidColumn = temporaryTable.getSessionUidColumn(); + + final TemporaryTableColumn rowNumberColumn = temporaryTable.getColumns() + .get( temporaryTable.getColumns().size() - (sessionUidColumn == null ? 1 : 2 ) ); + assert rowNumberColumn != null; + + final SharedSessionContractImplementor session = executionContext.getSession(); + final SimpleSelect simpleSelect = new SimpleSelect( session.getFactory() ) + .setTableName( temporaryTable.getQualifiedTableName() ) + .addColumn( rowNumberColumn.getColumnName() ); + if ( sessionUidColumn != null ) { + simpleSelect.addRestriction( sessionUidColumn.getColumnName() ); + } + return simpleSelect.toStatementString(); + } + public static void performAfterTemporaryTableUseActions( TemporaryTable temporaryTable, Function sessionUidAccess, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java index 5be44881bd5d..ccbebe2655f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableInsertStrategy.java @@ -8,12 +8,17 @@ import org.hibernate.dialect.temptable.TemporaryTableKind; import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.InsertHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + /** * Strategy based on ANSI SQL's definition of a "global temporary table". @@ -54,11 +59,9 @@ public GlobalTemporaryTableInsertStrategy( } @Override - public int executeInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new TableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final InsertHandler multiTableHandler = new TableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), @@ -67,8 +70,10 @@ public int executeInsert( // generally a global temp table should already track a Connection-specific uid, // but just in case a particular env needs it... session -> session.getSessionIdentifier().toString(), - getSessionFactory() - ).execute( context ); + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java index 0f9c136edebc..2dbd6b228bb1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/GlobalTemporaryTableMutationStrategy.java @@ -8,13 +8,20 @@ import org.hibernate.dialect.temptable.TemporaryTableKind; import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + /** * Strategy based on ANSI SQL's definition of a "global temporary table". @@ -55,10 +62,19 @@ public GlobalTemporaryTableMutationStrategy( } @Override - public int executeUpdate( + public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = sqmStatement instanceof SqmDeleteStatement sqmDelete + ? buildHandler( sqmDelete, domainParameterXref, context, firstJdbcParameterBindings ) + : buildHandler( (SqmUpdateStatement) sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); + } + + public MultiTableHandler buildHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { return new TableBasedUpdateHandler( sqmUpdate, domainParameterXref, @@ -68,25 +84,45 @@ public int executeUpdate( // generally a global temp table should already track a Connection-specific uid, // but just in case a particular env needs it... session -> session.getSessionIdentifier().toString(), - getSessionFactory() - ).execute( context ); + context, + firstJdbcParameterBindingsConsumer + ); } - @Override - public int executeDelete( + public MultiTableHandler buildHandler( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new TableBasedDeleteHandler( - sqmDelete, - domainParameterXref, - getTemporaryTable(), - getTemporaryTableStrategy(), - false, - // generally a global temp table should already track a Connection-specific uid, - // but just in case a particular env needs it... - session -> session.getSessionIdentifier().toString(), - getSessionFactory() - ).execute( context ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new TableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new TableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java deleted file mode 100644 index 800af04d2943..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java +++ /dev/null @@ -1,832 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.query.sqm.mutation.internal.temptable; - -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.function.Function; -import java.util.stream.IntStream; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableColumn; -import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.FetchTiming; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.generator.BeforeExecutionGenerator; -import org.hibernate.generator.Generator; -import org.hibernate.generator.values.GeneratedValues; -import org.hibernate.generator.values.GeneratedValuesMutationDelegate; -import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; -import org.hibernate.id.OptimizableGenerator; -import org.hibernate.id.enhanced.Optimizer; -import org.hibernate.id.insert.Binder; -import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; -import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.internal.util.collections.CollectionHelper; -import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; -import org.hibernate.metamodel.mapping.EntityIdentifierMapping; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.JdbcMapping; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.ModelPartContainer; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.SemanticException; -import org.hibernate.query.SortDirection; -import org.hibernate.query.results.internal.TableGroupImpl; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.ComparisonOperator; -import org.hibernate.query.common.FetchClauseType; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.expression.QueryLiteral; -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.from.UnionTableReference; -import org.hibernate.sql.ast.tree.insert.ConflictClause; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.ast.tree.select.SortSpecification; -import org.hibernate.sql.ast.tree.update.Assignable; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; -import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.internal.JdbcParameterImpl; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.graph.basic.BasicFetch; -import org.hibernate.sql.results.internal.SqlSelectionImpl; -import org.hibernate.sql.results.spi.ListResultsConsumer; -import org.hibernate.type.descriptor.ValueBinder; - -import static org.hibernate.generator.EventType.INSERT; - -/** - * @author Christian Beikov - * @author Steve Ebersole - */ -public class InsertExecutionDelegate implements TableBasedInsertHandler.ExecutionDelegate { - private final TemporaryTable entityTable; - private final TemporaryTableStrategy temporaryTableStrategy; - private final boolean forceDropAfterUse; - private final Function sessionUidAccess; - private final TableGroup updatingTableGroup; - private final InsertSelectStatement insertStatement; - private final ConflictClause conflictClause; - - private final EntityMappingType entityDescriptor; - - private final JdbcParameterBindings jdbcParameterBindings; - private final JdbcParameter sessionUidParameter; - - private final boolean assignsId; - private final Map> assignmentsByTable; - private final SessionFactoryImplementor sessionFactory; - - public InsertExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable entityTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup insertingTableGroup, - Map tableReferenceByAlias, - List assignments, - boolean assignsId, - InsertSelectStatement insertStatement, - ConflictClause conflictClause, - JdbcParameter sessionUidParameter, - DomainQueryExecutionContext executionContext) { - this.entityTable = entityTable; - this.temporaryTableStrategy = temporaryTableStrategy; - this.forceDropAfterUse = forceDropAfterUse; - this.sessionUidAccess = sessionUidAccess; - this.updatingTableGroup = insertingTableGroup; - this.conflictClause = conflictClause; - this.sessionUidParameter = sessionUidParameter; - this.insertStatement = insertStatement; - - this.sessionFactory = executionContext.getSession().getFactory(); - - final ModelPartContainer updatingModelPart = insertingTableGroup.getModelPart(); - assert updatingModelPart instanceof EntityMappingType; - - this.entityDescriptor = (EntityMappingType) updatingModelPart; - - this.assignmentsByTable = CollectionHelper.mapOfSize( insertingTableGroup.getTableReferenceJoins().size() + 1 ); - - jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter ); - } - } - , - executionContext.getSession() - ); - - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // segment the assignments by table-reference - - for ( int i = 0; i < assignments.size(); i++ ) { - final Assignment assignment = assignments.get( i ); - final Assignable assignable = assignment.getAssignable(); - final List assignmentColumnRefs = assignable.getColumnReferences(); - - TableReference assignmentTableReference = null; - - for ( int c = 0; c < assignmentColumnRefs.size(); c++ ) { - final ColumnReference columnReference = assignmentColumnRefs.get( c ); - final TableReference tableReference = resolveTableReference( columnReference, tableReferenceByAlias ); - - if ( assignmentTableReference != null && assignmentTableReference != tableReference ) { - throw new SemanticException( "Assignment referred to columns from multiple tables: " + i ); - } - assignmentTableReference = tableReference; - } - - assignmentsByTable.computeIfAbsent( - assignmentTableReference == null ? null : assignmentTableReference.getTableId(), - k -> new ArrayList<>() ).add( assignment ); - } - - this.assignsId = assignsId; - } - - private List getPrimaryKeyTableColumns(EntityPersister entityPersister, TemporaryTable entityTable) { - final boolean identityColumn = entityPersister.getGenerator().generatedOnExecution(); - final int startIndex = identityColumn ? 1 : 0; - final int endIndex = startIndex + entityPersister.getIdentifierMapping().getJdbcTypeCount(); - return entityTable.getColumns().subList( startIndex, endIndex ); - } - - @Override - public int execute(ExecutionContext executionContext) { - // NOTE: we could get rid of using a temporary table if the expressions in Values are "stable". - // But that is a non-trivial optimization that requires more effort - // as we need to split out individual inserts if we have a non-bulk capable optimizer - final boolean createdTable = ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( - entityTable, - temporaryTableStrategy, - executionContext - ); - - try { - if ( sessionUidParameter != null ) { - jdbcParameterBindings.addBinding( - sessionUidParameter, - new JdbcParameterBindingImpl( - entityTable.getSessionUidColumn().getJdbcMapping(), - UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ) - ) - ); - } - final int rows = ExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( - insertStatement, - jdbcParameterBindings, - executionContext - ); - - if ( rows != 0 ) { - final EntityPersister persister = entityDescriptor.getEntityPersister(); - final int tableSpan = persister.getTableSpan(); - final int insertedRows = insertRootTable( - persister.getTableName( 0 ), - rows, - createdTable, - persister.getKeyColumns( 0 ), - executionContext - ); - - if ( persister.hasDuplicateTables() ) { - final String[] insertedTables = new String[tableSpan]; - insertedTables[0] = persister.getTableName( 0 ); - for ( int i = 1; i < tableSpan; i++ ) { - if ( persister.isInverseTable( i ) ) { - continue; - } - final String tableName = persister.getTableName( i ); - insertedTables[i] = tableName; - if ( ArrayHelper.indexOf( insertedTables, i, tableName ) != -1 ) { - // Since secondary tables could appear multiple times, we have to skip duplicates - continue; - } - insertTable( - tableName, - persister.getKeyColumns( i ), - persister.isNullableTable( i ), - executionContext - ); - } - } - else { - for ( int i = 1; i < tableSpan; i++ ) { - insertTable( - persister.getTableName( i ), - persister.getKeyColumns( i ), - persister.isNullableTable( i ), - executionContext - ); - } - } - return insertedRows; - } - - return rows; - } - finally { - ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( - entityTable, - sessionUidAccess, - forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(), - executionContext - ); - } - } - - private TableReference resolveTableReference( - ColumnReference columnReference, - Map tableReferenceByAlias) { - if ( columnReference.getQualifier() == null ) { - // This happens only for the special row_number column - return null; - } - final TableReference tableReferenceByQualifier = tableReferenceByAlias.get( columnReference.getQualifier() ); - if ( tableReferenceByQualifier != null ) { - return tableReferenceByQualifier; - } - - throw new SemanticException( "Assignment referred to column of a joined association: " + columnReference ); - } - - private NamedTableReference resolveUnionTableReference(TableReference tableReference, String tableExpression) { - if ( tableReference instanceof UnionTableReference ) { - return new NamedTableReference( - tableExpression, - tableReference.getIdentificationVariable(), - tableReference.isOptional() - ); - } - else { - return (NamedTableReference) tableReference; - } - } - - private int insertRootTable( - String tableExpression, - int rows, - boolean rowNumberStartsAtOne, - String[] keyColumns, - ExecutionContext executionContext) { - final TableReference updatingTableReference = updatingTableGroup.getTableReference( - updatingTableGroup.getNavigablePath(), - tableExpression, - true - ); - - final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); - final Generator generator = entityPersister.getGenerator(); - final List assignments = assignmentsByTable.get( tableExpression ); - if ( !assignsId - && (assignments == null || assignments.isEmpty()) - && !generator.generatedOnExecution() - && (!(generator instanceof BulkInsertionCapableIdentifierGenerator) - || ((BulkInsertionCapableIdentifierGenerator) generator).supportsBulkInsertionIdentifierGeneration()) ) { - throw new IllegalStateException( "There must be at least a single root table assignment" ); - } - - final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Create the SQL AST and convert it into a JdbcOperation - final QuerySpec querySpec = new QuerySpec( true ); - final NamedTableReference temporaryTableReference = new NamedTableReference( - insertStatement.getTargetTable().getTableExpression(), - "hte_tmp" - ); - final TableGroupImpl temporaryTableGroup = new TableGroupImpl( - updatingTableGroup.getNavigablePath(), - null, - temporaryTableReference, - entityDescriptor - ); - querySpec.getFromClause().addRoot( temporaryTableGroup ); - if ( insertStatement.getValuesList().size() == 1 && conflictClause != null ) { - // Potentially apply a limit 1 to allow the use of the conflict clause emulation - querySpec.setFetchClauseExpression( - new QueryLiteral<>( - 1, - executionContext.getSession().getFactory().getQueryEngine().getCriteriaBuilder().getIntegerType() - ), - FetchClauseType.ROWS_ONLY - ); - } - final InsertSelectStatement insertStatement = new InsertSelectStatement( dmlTableReference ); - insertStatement.setConflictClause( conflictClause ); - insertStatement.setSourceSelectStatement( querySpec ); - applyAssignments( assignments, insertStatement, temporaryTableReference ); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - final Map entityTableToRootIdentity; - final SharedSessionContractImplementor session = executionContext.getSession(); - if ( !assignsId && generator.generatedOnExecution() ) { - final BasicEntityIdentifierMapping identifierMapping = - (BasicEntityIdentifierMapping) entityDescriptor.getIdentifierMapping(); - final QuerySpec idSelectQuerySpec = new QuerySpec( true ); - idSelectQuerySpec.getFromClause().addRoot( temporaryTableGroup ); - final ColumnReference columnReference = new ColumnReference( - (String) null, - TemporaryTable.ENTITY_TABLE_IDENTITY_COLUMN, - false, - null, - identifierMapping.getJdbcMapping() - ); - idSelectQuerySpec.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( 0, columnReference ) ); - idSelectQuerySpec.addSortSpecification( - new SortSpecification( - columnReference, - SortDirection.ASCENDING - ) - ); - if ( entityTable.getSessionUidColumn() != null ) { - final TemporaryTableColumn sessionUidColumn = entityTable.getSessionUidColumn(); - idSelectQuerySpec.applyPredicate( new ComparisonPredicate( - new ColumnReference( - temporaryTableReference, - sessionUidColumn.getColumnName(), - false, - null, - sessionUidColumn.getJdbcMapping() - ), - ComparisonOperator.EQUAL, - sessionUidParameter - ) ); - } - final SelectStatement selectStatement = new SelectStatement( - idSelectQuerySpec, - Collections.singletonList( - new BasicFetch<>( - 0, - null, - null, - identifierMapping, - FetchTiming.IMMEDIATE, - null, - false - ) - ) - ); - final JdbcOperationQuerySelect jdbcSelect = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( sessionFactory, selectStatement ) - .translate( null, executionContext.getQueryOptions() ); - final List list = jdbcServices.getJdbcSelectExecutor().list( - jdbcSelect, - jdbcParameterBindings, - executionContext, - null, - null, - ListResultsConsumer.UniqueSemantic.NONE, - rows - ); - entityTableToRootIdentity = new LinkedHashMap<>( list.size() ); - for ( Object o : list ) { - entityTableToRootIdentity.put( o, null ); - } - - querySpec.applyPredicate( - new ComparisonPredicate( - columnReference, - ComparisonOperator.EQUAL, - new JdbcParameterImpl( identifierMapping.getJdbcMapping() ) - ) - ); - } - else { - entityTableToRootIdentity = null; - // if the target paths don't already contain the id, and we need identifier generation, - // then we load update rows from the temporary table with the generated identifiers, - // to then insert into the target tables in once statement - if ( insertStatement.getTargetColumns().stream() - .noneMatch( c -> keyColumns[0].equals( c.getColumnExpression() ) ) ) { - final EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); - final List primaryKeyTableColumns = - getPrimaryKeyTableColumns( entityPersister, entityTable ); - - final TemporaryTableColumn sessionUidColumn; - final Predicate sessionUidPredicate; - if ( entityTable.getSessionUidColumn() == null ) { - sessionUidColumn = null; - sessionUidPredicate = null; - } - else { - sessionUidColumn = entityTable.getSessionUidColumn(); - sessionUidPredicate = new ComparisonPredicate( - new ColumnReference( - (String) null, - sessionUidColumn.getColumnName(), - false, - null, - sessionUidColumn.getJdbcMapping() - ), - ComparisonOperator.EQUAL, - sessionUidParameter - ); - } - if ( needsIdentifierGeneration( generator ) ) { - final BasicEntityIdentifierMapping basicIdentifierMapping = (BasicEntityIdentifierMapping) identifierMapping; - final JdbcParameter rootIdentity = new JdbcParameterImpl( basicIdentifierMapping.getJdbcMapping() ); - final List temporaryTableAssignments = new ArrayList<>( 1 ); - final ColumnReference idColumnReference = new ColumnReference( (String) null, basicIdentifierMapping ); - temporaryTableAssignments.add( new Assignment( idColumnReference, rootIdentity ) ); - - final JdbcParameter rowNumber = new JdbcParameterImpl( basicIdentifierMapping.getJdbcMapping() ); - final int rowNumberIndex = - entityTable.getColumns().size() - (entityTable.getSessionUidColumn() == null ? 1 : 2); - final TemporaryTableColumn rowNumberColumn = entityTable.getColumns().get( rowNumberIndex ); - - final UpdateStatement updateStatement = new UpdateStatement( - temporaryTableReference, - temporaryTableAssignments, - Predicate.combinePredicates( - new ComparisonPredicate( - new ColumnReference( - (String) null, - rowNumberColumn.getColumnName(), - false, - null, - rowNumberColumn.getJdbcMapping() - ), - ComparisonOperator.EQUAL, - rowNumber - ), - sessionUidPredicate - ) - ); - - final JdbcOperationQueryMutation jdbcUpdate = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, updateStatement ) - .translate( null, executionContext.getQueryOptions() ); - final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 2 ); - if ( sessionUidColumn != null ) { - updateBindings.addBinding( - sessionUidParameter, - new JdbcParameterBindingImpl( - sessionUidColumn.getJdbcMapping(), - UUID.fromString( sessionUidAccess.apply( session ) ) - ) - ); - } - - final BeforeExecutionGenerator beforeExecutionGenerator = (BeforeExecutionGenerator) generator; - final IntStream rowNumberStream = !rowNumberStartsAtOne - ? IntStream.of( ExecuteWithTemporaryTableHelper.loadInsertedRowNumbers( entityTable, sessionUidAccess, rows, executionContext ) ) - : IntStream.range( 1, rows + 1 ); - rowNumberStream.forEach( rowNumberValue -> { - updateBindings.addBinding( - rowNumber, - new JdbcParameterBindingImpl( - rowNumberColumn.getJdbcMapping(), - rowNumberValue - ) - ); - updateBindings.addBinding( - rootIdentity, - new JdbcParameterBindingImpl( - basicIdentifierMapping.getJdbcMapping(), - beforeExecutionGenerator.generate( session, null, null, INSERT ) - ) - ); - final int updateCount = jdbcServices.getJdbcMutationExecutor().execute( - jdbcUpdate, - updateBindings, - sql -> session - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> { - }, - executionContext - ); - assert updateCount == 1; - } ); - } - - identifierMapping.forEachSelectable( 0, (selectionIndex, selectableMapping) -> { - insertStatement.addTargetColumnReferences( - new ColumnReference( - (String) null, - keyColumns[selectionIndex], - false, - null, - selectableMapping.getJdbcMapping() - ) - ); - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - new ColumnReference( - temporaryTableReference.getIdentificationVariable(), - primaryKeyTableColumns.get( selectionIndex ).getColumnName(), - false, - null, - selectableMapping.getJdbcMapping() - ) - ) - ); - } ); - } - } - if ( entityTable.getSessionUidColumn() != null ) { - final TemporaryTableColumn sessionUidColumn = entityTable.getSessionUidColumn(); - querySpec.applyPredicate( new ComparisonPredicate( - new ColumnReference( - temporaryTableReference, - sessionUidColumn.getColumnName(), - false, - null, - sessionUidColumn.getJdbcMapping() - ), - ComparisonOperator.EQUAL, - sessionUidParameter - ) ); - } - - final JdbcOperationQueryMutation jdbcInsert = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, insertStatement ) - .translate( null, executionContext.getQueryOptions() ); - - if ( !assignsId && generator.generatedOnExecution() ) { - final GeneratedValuesMutationDelegate insertDelegate = entityDescriptor.getEntityPersister().getInsertDelegate(); - // todo 7.0 : InsertGeneratedIdentifierDelegate will be removed once we're going to handle - // generated values within the jdbc insert operaetion itself - final InsertGeneratedIdentifierDelegate identifierDelegate = (InsertGeneratedIdentifierDelegate) insertDelegate; - final String finalSql = identifierDelegate.prepareIdentifierGeneratingInsert( jdbcInsert.getSqlString() ); - final BasicEntityIdentifierMapping identifierMapping = - (BasicEntityIdentifierMapping) entityDescriptor.getIdentifierMapping(); - final ValueBinder jdbcValueBinder = identifierMapping.getJdbcMapping().getJdbcValueBinder(); - for ( Map.Entry entry : entityTableToRootIdentity.entrySet() ) { - final GeneratedValues generatedValues = identifierDelegate.performInsertReturning( - finalSql, - session, - new Binder() { - @Override - public void bindValues(PreparedStatement ps) throws SQLException { - jdbcValueBinder.bind( ps, entry.getKey(), 1, session ); - if ( sessionUidParameter != null ) { - sessionUidParameter.getParameterBinder() - .bindParameterValue( ps, 2, jdbcParameterBindings, executionContext ); - } - } - @Override - public Object getEntity() { - return null; - } - } - ); - final Object rootIdentity = generatedValues.getGeneratedValue( identifierMapping ); - entry.setValue( rootIdentity ); - } - - final List primaryKeyTableColumns = - getPrimaryKeyTableColumns( entityPersister, entityTable ); - assert primaryKeyTableColumns.size() == 1; - - final JdbcParameter entityIdentity = new JdbcParameterImpl( identifierMapping.getJdbcMapping() ); - final JdbcParameter rootIdentity = new JdbcParameterImpl( identifierMapping.getJdbcMapping() ); - final List temporaryTableAssignments = new ArrayList<>( 1 ); - temporaryTableAssignments.add( - new Assignment( - new ColumnReference( - (String) null, - primaryKeyTableColumns.get( 0 ).getColumnName(), - false, - null, - primaryKeyTableColumns.get( 0 ).getJdbcMapping() - ), - rootIdentity - ) - ); - final UpdateStatement updateStatement = new UpdateStatement( - temporaryTableReference, - temporaryTableAssignments, - new ComparisonPredicate( - new ColumnReference( - (String) null, - TemporaryTable.ENTITY_TABLE_IDENTITY_COLUMN, - false, - null, - identifierMapping.getJdbcMapping() - ), - ComparisonOperator.EQUAL, - entityIdentity - ) - ); - - final JdbcOperationQueryMutation jdbcUpdate = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, updateStatement ) - .translate( null, executionContext.getQueryOptions() ); - final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 2 ); - - for ( Map.Entry entry : entityTableToRootIdentity.entrySet() ) { - JdbcMapping jdbcMapping = identifierMapping.getJdbcMapping(); - updateBindings.addBinding( entityIdentity, new JdbcParameterBindingImpl( jdbcMapping, entry.getKey() ) ); - updateBindings.addBinding( rootIdentity, new JdbcParameterBindingImpl( jdbcMapping, entry.getValue() ) ); - jdbcServices.getJdbcMutationExecutor().execute( - jdbcUpdate, - updateBindings, - sql -> session - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> { - }, - executionContext - ); - } - - return entityTableToRootIdentity.size(); - } - else { - return jdbcServices.getJdbcMutationExecutor().execute( - jdbcInsert, - jdbcParameterBindings, - sql -> session - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> { - }, - executionContext - ); - } - } - - private boolean needsIdentifierGeneration(Generator identifierGenerator) { - if ( !assignsId && identifierGenerator instanceof OptimizableGenerator ) { - // If the generator uses an optimizer or is not bulk insertion capable, - // we have to generate identifiers for the new rows, as that couldn't - // have been done via a SQL expression - final Optimizer optimizer = ( (OptimizableGenerator) identifierGenerator ).getOptimizer(); - return optimizer != null && optimizer.getIncrementSize() > 1 - || identifierGenerator instanceof BulkInsertionCapableIdentifierGenerator - && !( (BulkInsertionCapableIdentifierGenerator) identifierGenerator ) - .supportsBulkInsertionIdentifierGeneration(); - } - else { - return false; - } - } - - private void insertTable( - String tableExpression, - String[] keyColumns, - boolean nullableTable, - ExecutionContext executionContext) { - final TableReference updatingTableReference = updatingTableGroup.getTableReference( - updatingTableGroup.getNavigablePath(), - tableExpression, - true - ); - - final List assignments = assignmentsByTable.get( tableExpression ); - if ( nullableTable && ( assignments == null || assignments.isEmpty() ) ) { - // no assignments for this table - skip it - return; - } - final NamedTableReference dmlTargetTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); - - final QuerySpec querySpec = new QuerySpec( true ); - final NamedTableReference temporaryTableReference = new NamedTableReference( - insertStatement.getTargetTable().getTableExpression(), - "hte_tmp" - ); - final TableGroupImpl temporaryTableGroup = new TableGroupImpl( - updatingTableGroup.getNavigablePath(), - null, - temporaryTableReference, - entityDescriptor - ); - querySpec.getFromClause().addRoot( temporaryTableGroup ); - final InsertSelectStatement insertStatement = new InsertSelectStatement( dmlTargetTableReference ); - insertStatement.setSourceSelectStatement( querySpec ); - applyAssignments( assignments, insertStatement, temporaryTableReference ); - if ( insertStatement.getTargetColumns() - .stream() - .noneMatch( c -> keyColumns[0].equals( c.getColumnExpression() ) ) ) { - final List primaryKeyTableColumns = - getPrimaryKeyTableColumns( entityDescriptor.getEntityPersister(), entityTable ); - entityDescriptor.getIdentifierMapping().forEachSelectable( 0, (selectionIndex, selectableMapping) -> { - insertStatement.addTargetColumnReferences( - new ColumnReference( - (String) null, - keyColumns[selectionIndex], - false, - null, - selectableMapping.getJdbcMapping() - ) - ); - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - new ColumnReference( - temporaryTableReference.getIdentificationVariable(), - primaryKeyTableColumns.get( selectionIndex ).getColumnName(), - false, - null, - selectableMapping.getJdbcMapping() - ) - ) - ); - } ); - } - - if ( entityTable.getSessionUidColumn() != null ) { - final TemporaryTableColumn sessionUidColumn = entityTable.getSessionUidColumn(); - querySpec.applyPredicate( new ComparisonPredicate( - new ColumnReference( - temporaryTableReference, - sessionUidColumn.getColumnName(), - false, - null, - sessionUidColumn.getJdbcMapping() - ), - ComparisonOperator.EQUAL, - sessionUidParameter - ) ); - } - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - final JdbcOperationQueryMutation jdbcInsert = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, insertStatement ) - .translate( null, executionContext.getQueryOptions() ); - - jdbcServices.getJdbcMutationExecutor().execute( - jdbcInsert, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> { - }, - executionContext - ); - } - - private void applyAssignments(List assignments, InsertSelectStatement insertStatement, NamedTableReference temporaryTableReference) { - if ( assignments != null && !assignments.isEmpty() ) { - for ( Assignment assignment : assignments ) { - final Assignable assignable = assignment.getAssignable(); - insertStatement.addTargetColumnReferences( assignable.getColumnReferences() ); - final List columns = entityTable.findTemporaryTableColumns( - entityDescriptor.getEntityPersister(), - ((SqmPathInterpretation) assignable).getExpressionType() - ); - for ( TemporaryTableColumn temporaryTableColumn : columns ) { - insertStatement.getSourceSelectStatement().getFirstQuerySpec().getSelectClause().addSqlSelection( - new SqlSelectionImpl( - new ColumnReference( - temporaryTableReference.getIdentificationVariable(), - temporaryTableColumn.getColumnName(), - false, - null, - temporaryTableColumn.getJdbcMapping() - ) - ) - ); - } - } - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java index f9241d38cd04..6353f1a51b8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableInsertStrategy.java @@ -8,12 +8,17 @@ import org.hibernate.dialect.temptable.TemporaryTableKind; import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + /** * Strategy based on ANSI SQL's definition of a "local temporary table" (local to each db session). @@ -54,11 +59,9 @@ public LocalTemporaryTableInsertStrategy( } @Override - public int executeInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new TableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = new TableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), @@ -67,7 +70,9 @@ public int executeInsert( session -> { throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); }, - getSessionFactory() - ).execute( context ); + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java index d8818f66da52..92420f3609b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/LocalTemporaryTableMutationStrategy.java @@ -8,13 +8,20 @@ import org.hibernate.dialect.temptable.TemporaryTableKind; import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + /** * Strategy based on ANSI SQL's definition of a "local temporary table" (local to each db session). @@ -55,10 +62,19 @@ public LocalTemporaryTableMutationStrategy( } @Override - public int executeUpdate( + public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = sqmStatement instanceof SqmDeleteStatement sqmDelete + ? buildHandler( sqmDelete, domainParameterXref, context, firstJdbcParameterBindings ) + : buildHandler( (SqmUpdateStatement) sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); + } + + public MultiTableHandler buildHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { return new TableBasedUpdateHandler( sqmUpdate, domainParameterXref, @@ -68,27 +84,46 @@ public int executeUpdate( session -> { throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); }, - getSessionFactory() - ).execute( context ); + context, + firstJdbcParameterBindingsConsumer + ); } - @Override - public int executeDelete( + public MultiTableHandler buildHandler( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - final TableBasedDeleteHandler deleteHandler = new TableBasedDeleteHandler( - sqmDelete, - domainParameterXref, - getTemporaryTable(), - getTemporaryTableStrategy(), - isDropIdTables(), - session -> { - throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); - }, - getSessionFactory() - ); - return deleteHandler.execute( context ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new TableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new TableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java index 2c980a2887aa..a8f769b66182 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableInsertStrategy.java @@ -8,12 +8,17 @@ import org.hibernate.dialect.temptable.TemporaryTableKind; import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + /** @@ -57,18 +62,18 @@ public PersistentTableInsertStrategy( } @Override - public int executeInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new TableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = new TableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), getTemporaryTableStrategy(), false, session -> session.getSessionIdentifier().toString(), - getSessionFactory() - ).execute( context ); + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java index 7c1d8312fc03..e89cbf0571bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/PersistentTableMutationStrategy.java @@ -8,13 +8,20 @@ import org.hibernate.dialect.temptable.TemporaryTableKind; import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + /** @@ -58,10 +65,19 @@ public PersistentTableMutationStrategy( } @Override - public int executeUpdate( + public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = sqmStatement instanceof SqmDeleteStatement sqmDelete + ? buildHandler( sqmDelete, domainParameterXref, context, firstJdbcParameterBindings ) + : buildHandler( (SqmUpdateStatement) sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); + } + + public MultiTableHandler buildHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { return new TableBasedUpdateHandler( sqmUpdate, domainParameterXref, @@ -69,23 +85,41 @@ public int executeUpdate( getTemporaryTableStrategy(), false, session -> session.getSessionIdentifier().toString(), - getSessionFactory() - ).execute( context ); + context, + firstJdbcParameterBindingsConsumer + ); } - @Override - public int executeDelete( + public MultiTableHandler buildHandler( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new TableBasedDeleteHandler( - sqmDelete, - domainParameterXref, - getTemporaryTable(), - getTemporaryTableStrategy(), - false, - session -> session.getSessionIdentifier().toString(), - getSessionFactory() - ).execute( context ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new TableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new TableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java deleted file mode 100644 index 268c3015276c..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/RestrictedDeleteExecutionDelegate.java +++ /dev/null @@ -1,576 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.query.sqm.mutation.internal.temptable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.MutableInteger; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SelectableConsumer; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; -import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.spi.SqlExpressionResolver; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.expression.SqlTuple; -import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; -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.from.UnionTableReference; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.predicate.PredicateCollector; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; - -import static org.hibernate.query.sqm.mutation.internal.MutationQueryLogging.MUTATION_QUERY_LOGGER; - -/** - * @author Steve Ebersole - */ -public class RestrictedDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate { - public RestrictedDeleteExecutionDelegate( - EntityMappingType entityDescriptor, - TemporaryTable idTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - QueryOptions queryOptions, - LoadQueryInfluencers loadQueryInfluencers, - QueryParameterBindings queryParameterBindings, - Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( - entityDescriptor, - idTable, - temporaryTableStrategy, - forceDropAfterUse, - sqmDelete, - domainParameterXref, - queryOptions, - loadQueryInfluencers, - queryParameterBindings, - sessionUidAccess, - sessionFactory - ); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - final EntityPersister entityDescriptor = - getSessionFactory().getMappingMetamodel() - .getEntityDescriptor( getSqmDelete().getTarget().getEntityName() ); - final String hierarchyRootTableName = entityDescriptor.getTableName(); - - final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup(); - - final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( - deletingTableGroup.getNavigablePath(), - hierarchyRootTableName - ); - assert hierarchyRootTableReference != null; - - // Use the converter to interpret the where-clause. We do this for 2 reasons: - // 1) the resolved Predicate is ultimately the base for applying restriction to the deletes - // 2) we also inspect each ColumnReference that is part of the where-clause to see which - // table it comes from. if all of the referenced columns (if any at all) are from the root table - // we can perform all of the deletes without using an id-table - final Predicate specifiedRestriction = getConverter().visitWhereClause( getSqmDelete().getWhereClause() ); - - final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); - entityDescriptor.applyBaseRestrictions( - predicateCollector, - deletingTableGroup, - true, - executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), - false, - null, - getConverter() - ); - - getConverter().pruneTableGroupJoins(); - final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( - hierarchyRootTableReference.getIdentificationVariable() - ); - if ( predicateCollector.getPredicate() != null ) { - predicateCollector.getPredicate().accept( walker ); - } - - // We need an id table if we want to delete from an intermediate table to avoid FK violations - // The intermediate table has a FK to the root table, so we can't delete from the root table first - // Deleting from the intermediate table first also isn't possible, - // because that is the source for deletion in other tables, hence we need an id table - final boolean needsIdTable = !walker.isAllColumnReferencesFromIdentificationVariable() - || entityDescriptor != entityDescriptor.getRootEntityDescriptor(); - - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); - - if ( needsIdTable ) { - return executeWithIdTable( - predicateCollector.getPredicate(), - getConverter().getJdbcParamsBySqmParam(), - getConverter().getSqmParameterMappingModelExpressibleResolutions(), - executionContextAdapter - ); - } - else { - return executeWithoutIdTable( - predicateCollector.getPredicate(), - deletingTableGroup, - getConverter().getJdbcParamsBySqmParam(), - getConverter().getSqmParameterMappingModelExpressibleResolutions(), - getConverter().getSqlExpressionResolver(), - executionContextAdapter - ); - } - } - - private int executeWithoutIdTable( - Predicate suppliedPredicate, - TableGroup tableGroup, - Map, List>> restrictionSqmParameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, - SqlExpressionResolver sqlExpressionResolver, - ExecutionContext executionContext) { - assert getEntityDescriptor() == getEntityDescriptor().getRootEntityDescriptor(); - - final EntityPersister rootEntityPersister = getEntityDescriptor().getEntityPersister(); - final String rootTableName = rootEntityPersister.getTableName(); - final NamedTableReference rootTableReference = (NamedTableReference) tableGroup.resolveTableReference( - tableGroup.getNavigablePath(), - rootTableName - ); - - final QuerySpec matchingIdSubQuerySpec = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( - tableGroup.getNavigablePath(), - rootTableReference, - suppliedPredicate, - rootEntityPersister, - sqlExpressionResolver, - getSessionFactory() - ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( - getDomainParameterXref(), - () -> restrictionSqmParameterResolutions - ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - executionContext.getSession() - ); - - SqmMutationStrategyHelper.cleanUpCollectionTables( - getEntityDescriptor(), - (tableReference, attributeMapping) -> { - // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup - if ( suppliedPredicate == null ) { - return null; - } - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idSelectFkSubQuery; - // todo (6.0): based on the location of the attribute mapping, we could prune the table group of the subquery - if ( fkDescriptor.getTargetPart().isEntityIdentifierMapping() ) { - idSelectFkSubQuery = matchingIdSubQuerySpec; - } - else { - idSelectFkSubQuery = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( - tableGroup.getNavigablePath(), - rootTableReference, - suppliedPredicate, - rootEntityPersister, - sqlExpressionResolver, - getSessionFactory() - ); - } - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - getSessionFactory() - ), - idSelectFkSubQuery, - false - ); - - }, - jdbcParameterBindings, - executionContext - ); - - if ( rootTableReference instanceof UnionTableReference ) { - final MutableInteger rows = new MutableInteger(); - getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> { - final NamedTableReference tableReference = new NamedTableReference( - tableExpression, - tableGroup.getPrimaryTableReference().getIdentificationVariable() - ); - final QuerySpec idMatchingSubQuerySpec; - // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup - if ( suppliedPredicate == null ) { - idMatchingSubQuerySpec = null; - } - else { - idMatchingSubQuerySpec = matchingIdSubQuerySpec; - } - rows.plus( - deleteFromNonRootTableWithoutIdTable( - tableReference, - tableKeyColumnVisitationSupplier, - sqlExpressionResolver, - tableGroup, - idMatchingSubQuerySpec, - jdbcParameterBindings, - executionContext - ) - ); - } - ); - return rows.get(); - } - else { - getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> { - if ( !tableExpression.equals( rootTableName ) ) { - final NamedTableReference tableReference = (NamedTableReference) tableGroup.getTableReference( - tableGroup.getNavigablePath(), - tableExpression, - true - ); - final QuerySpec idMatchingSubQuerySpec; - // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup - if ( suppliedPredicate == null ) { - idMatchingSubQuerySpec = null; - } - else { - idMatchingSubQuerySpec = matchingIdSubQuerySpec; - } - deleteFromNonRootTableWithoutIdTable( - tableReference, - tableKeyColumnVisitationSupplier, - sqlExpressionResolver, - tableGroup, - idMatchingSubQuerySpec, - jdbcParameterBindings, - executionContext - ); - } - } - ); - - return deleteFromRootTableWithoutIdTable( - rootTableReference, - suppliedPredicate, - jdbcParameterBindings, - executionContext - ); - } - } - - private int deleteFromRootTableWithoutIdTable( - NamedTableReference rootTableReference, - Predicate predicate, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - return executeSqlDelete( - new DeleteStatement( rootTableReference, predicate ), - jdbcParameterBindings, - executionContext - ); - } - - private int deleteFromNonRootTableWithoutIdTable( - NamedTableReference targetTableReference, - Supplier> tableKeyColumnVisitationSupplier, - SqlExpressionResolver sqlExpressionResolver, - TableGroup rootTableGroup, - QuerySpec matchingIdSubQuerySpec, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - assert targetTableReference != null; - MUTATION_QUERY_LOGGER.tracef( "deleteFromNonRootTable - %s", targetTableReference.getTableExpression() ); - - final NamedTableReference deleteTableReference = new NamedTableReference( - targetTableReference.getTableExpression(), - DeleteStatement.DEFAULT_ALIAS, - true - ); - final Predicate tableDeletePredicate; - if ( matchingIdSubQuerySpec == null ) { - tableDeletePredicate = null; - } - else { - /* - * delete from sub_table - * where sub_id in ( - * select root_id from root_table - * where {predicate} - * ) - */ - - /* - * Create the `sub_id` reference as the LHS of the in-subquery predicate - */ - final List deletingTableColumnRefs = new ArrayList<>(); - tableKeyColumnVisitationSupplier.get().accept( - (columnIndex, selection) -> { - assert deleteTableReference.getTableReference( selection.getContainingTableExpression() ) != null; - - final Expression expression = sqlExpressionResolver.resolveSqlExpression( - deleteTableReference, - selection - ); - - deletingTableColumnRefs.add( (ColumnReference) expression ); - } - ); - - final Expression deletingTableColumnRefsExpression; - if ( deletingTableColumnRefs.size() == 1 ) { - deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 ); - } - else { - deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, getEntityDescriptor().getIdentifierMapping() ); - } - - tableDeletePredicate = new InSubQueryPredicate( - deletingTableColumnRefsExpression, - matchingIdSubQuerySpec, - false - ); - } - - final DeleteStatement sqlAstDelete = new DeleteStatement( deleteTableReference, tableDeletePredicate ); - final int rows = executeSqlDelete( - sqlAstDelete, - jdbcParameterBindings, - executionContext - ); - MUTATION_QUERY_LOGGER.tracef( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows ); - return rows; - } - - - private static int executeSqlDelete( - DeleteStatement sqlAst, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - - final JdbcServices jdbcServices = factory.getJdbcServices(); - - final JdbcOperationQueryMutation jdbcDelete = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqlAst ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - - return jdbcServices.getJdbcMutationExecutor().execute( - jdbcDelete, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContext - ); - } - - private int executeWithIdTable( - Predicate predicate, - Map, List>> restrictionSqmParameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, - ExecutionContext executionContext) { - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( - getDomainParameterXref(), - () -> restrictionSqmParameterResolutions - ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - executionContext.getSession() - ); - - ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( - getIdTable(), - getTemporaryTableStrategy(), - executionContext - ); - - try { - return executeUsingIdTable( predicate, executionContext, jdbcParameterBindings ); - } - finally { - ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( - getIdTable(), - getSessionUidAccess(), - getAfterUseAction(), - executionContext - ); - } - } - - private int executeUsingIdTable( - Predicate predicate, - ExecutionContext executionContext, - JdbcParameterBindings jdbcParameterBindings) { - final int rows = ExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable( - getConverter(), - predicate, - getIdTable(), - getSessionUidAccess(), - jdbcParameterBindings, - executionContext - ); - - final QuerySpec idTableIdentifierSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( - getIdTable(), - getSessionUidAccess(), - getEntityDescriptor(), - executionContext - ); - - SqmMutationStrategyHelper.cleanUpCollectionTables( - getEntityDescriptor(), - (tableReference, attributeMapping) -> { - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idTableFkSubQuery; - if ( fkDescriptor.getTargetPart().isEntityIdentifierMapping() ) { - idTableFkSubQuery = idTableIdentifierSubQuery; - } - else { - idTableFkSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( - getIdTable(), - fkDescriptor.getTargetPart(), - getSessionUidAccess(), - getEntityDescriptor(), - executionContext - ); - } - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - getSessionFactory() - ), - idTableFkSubQuery, - false - ); - - }, - JdbcParameterBindings.NO_BINDINGS, - executionContext - ); - - getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable( - tableExpression, - tableKeyColumnVisitationSupplier, - idTableIdentifierSubQuery, - executionContext - ) - ); - - return rows; - } - - private void deleteFromTableUsingIdTable( - String tableExpression, - Supplier> tableKeyColumnVisitationSupplier, - QuerySpec idTableSubQuery, - ExecutionContext executionContext) { - MUTATION_QUERY_LOGGER.tracef( "deleteFromTableUsingIdTable - %s", tableExpression ); - - final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() ); - final NamedTableReference targetTable = new NamedTableReference( - tableExpression, - DeleteStatement.DEFAULT_ALIAS, - true - ); - - tableKeyColumnVisitationSupplier.get().accept( - (columnIndex, selection) -> { - assert selection.getContainingTableExpression().equals( tableExpression ); - assert ! selection.isFormula(); - assert selection.getCustomReadExpression() == null; - assert selection.getCustomWriteExpression() == null; - - keyColumnCollector.apply( - new ColumnReference( - targetTable, - selection - ) - ); - } - ); - - final InSubQueryPredicate predicate = new InSubQueryPredicate( - keyColumnCollector.buildKeyExpression(), - idTableSubQuery, - false - ); - - executeSqlDelete( - new DeleteStatement( targetTable, predicate ), - JdbcParameterBindings.NO_BINDINGS, - executionContext - ); - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/SoftDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/SoftDeleteExecutionDelegate.java deleted file mode 100644 index e93d808670fc..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/SoftDeleteExecutionDelegate.java +++ /dev/null @@ -1,390 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.query.sqm.mutation.internal.temptable; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.TableDetails; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.SqlTuple; -import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.predicate.PredicateCollector; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.internal.SqlSelectionImpl; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; - -import static java.util.Collections.singletonList; -import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.omittingLockingAndPaging; - -/** - * @author Steve Ebersole - */ -public class SoftDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate { - public SoftDeleteExecutionDelegate( - EntityMappingType entityDescriptor, - TemporaryTable idTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - QueryOptions queryOptions, - LoadQueryInfluencers loadQueryInfluencers, - QueryParameterBindings queryParameterBindings, - Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( - entityDescriptor, - idTable, - temporaryTableStrategy, - forceDropAfterUse, - sqmDelete, - domainParameterXref, - queryOptions, - loadQueryInfluencers, - queryParameterBindings, - sessionUidAccess, - sessionFactory - ); - } - - @Override - public int execute(DomainQueryExecutionContext domainQueryExecutionContext) { - final String targetEntityName = getSqmDelete().getTarget().getEntityName(); - final EntityPersister targetEntityDescriptor = - getSessionFactory().getMappingMetamodel() - .getEntityDescriptor( targetEntityName ); - - final EntityMappingType rootEntityDescriptor = targetEntityDescriptor.getRootEntityDescriptor(); - - // determine if we need to use a sub-query for matching ids - - // 1. if the target is not the root we will - // 2. if the supplied predicate (if any) refers to columns from a table - // other than the identifier table we will - final SqmJdbcExecutionContextAdapter executionContext = omittingLockingAndPaging( domainQueryExecutionContext ); - - final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup(); - final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails(); - final NamedTableReference rootTableReference = (NamedTableReference) deletingTableGroup.resolveTableReference( - deletingTableGroup.getNavigablePath(), - softDeleteTable.getTableName() - ); - assert rootTableReference != null; - - // NOTE : `converter.visitWhereClause` already applies the soft-delete restriction - final Predicate specifiedRestriction = getConverter().visitWhereClause( getSqmDelete().getWhereClause() ); - - final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); - targetEntityDescriptor.applyBaseRestrictions( - predicateCollector, - deletingTableGroup, - true, - executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), - false, - null, - getConverter() - ); - - getConverter().pruneTableGroupJoins(); - final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( - rootTableReference.getIdentificationVariable() - ); - if ( predicateCollector.getPredicate() != null ) { - predicateCollector.getPredicate().accept( walker ); - } - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), getConverter() ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) getConverter().getSqmParameterMappingModelExpressibleResolutions().get(parameter); - } - }, - executionContext.getSession() - ); - - final boolean needsSubQuery = !walker.isAllColumnReferencesFromIdentificationVariable() - || targetEntityDescriptor != rootEntityDescriptor; - if ( needsSubQuery ) { - if ( getSessionFactory().getJdbcServices().getDialect().supportsSubqueryOnMutatingTable() ) { - return performDeleteWithSubQuery( - rootEntityDescriptor, - deletingTableGroup, - rootTableReference, - predicateCollector, - jdbcParameterBindings, - getConverter(), - executionContext - ); - } - else { - return performDeleteWithIdTable( - rootEntityDescriptor, - rootTableReference, - predicateCollector, - jdbcParameterBindings, - executionContext - ); - } - } - else { - return performDirectDelete( - rootEntityDescriptor, - rootTableReference, - predicateCollector, - jdbcParameterBindings, - executionContext - ); - } - } - - private int performDeleteWithIdTable( - EntityMappingType rootEntityDescriptor, - NamedTableReference targetTableReference, - PredicateCollector predicateCollector, - JdbcParameterBindings jdbcParameterBindings, - SqmJdbcExecutionContextAdapter executionContext) { - ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( - getIdTable(), - getTemporaryTableStrategy(), - executionContext - ); - - try { - return deleteUsingIdTable( - rootEntityDescriptor, - targetTableReference, - predicateCollector, - jdbcParameterBindings, - executionContext - ); - } - finally { - ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( - getIdTable(), - getSessionUidAccess(), - getAfterUseAction(), - executionContext - ); - } - } - - private int deleteUsingIdTable( - EntityMappingType rootEntityDescriptor, - NamedTableReference targetTableReference, - PredicateCollector predicateCollector, - JdbcParameterBindings jdbcParameterBindings, - SqmJdbcExecutionContextAdapter executionContext) { - final int rows = ExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable( - getConverter(), - predicateCollector.getPredicate(), - getIdTable(), - getSessionUidAccess(), - jdbcParameterBindings, - executionContext - ); - - final QuerySpec idTableIdentifierSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( - getIdTable(), - getSessionUidAccess(), - getEntityDescriptor(), - executionContext - ); - - SqmMutationStrategyHelper.cleanUpCollectionTables( - getEntityDescriptor(), - (tableReference, attributeMapping) -> { - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idTableFkSubQuery; - if ( fkDescriptor.getTargetPart().isEntityIdentifierMapping() ) { - idTableFkSubQuery = idTableIdentifierSubQuery; - } - else { - idTableFkSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( - getIdTable(), - fkDescriptor.getTargetPart(), - getSessionUidAccess(), - getEntityDescriptor(), - executionContext - ); - } - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - getSessionFactory() - ), - idTableFkSubQuery, - false - ); - - }, - JdbcParameterBindings.NO_BINDINGS, - executionContext - ); - - final Assignment softDeleteAssignment = rootEntityDescriptor - .getSoftDeleteMapping() - .createSoftDeleteAssignment( targetTableReference ); - final Expression idExpression = createIdExpression( rootEntityDescriptor, targetTableReference ); - final UpdateStatement updateStatement = new UpdateStatement( - targetTableReference, - singletonList( softDeleteAssignment ), - new InSubQueryPredicate( idExpression, idTableIdentifierSubQuery, false ) - ); - - executeUpdate( updateStatement, jdbcParameterBindings, executionContext ); - - return rows; - } - - private static Expression createIdExpression(EntityMappingType rootEntityDescriptor, NamedTableReference targetTableReference) { - final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails(); - final TableDetails.KeyDetails keyDetails = softDeleteTable.getKeyDetails(); - final List idExpressions = new ArrayList<>( keyDetails.getColumnCount() ); - keyDetails.forEachKeyColumn( (position, column) -> idExpressions.add( - new ColumnReference( targetTableReference, column ) - ) ); - final Expression idExpression = idExpressions.size() == 1 - ? idExpressions.get( 0 ) - : new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() ); - return idExpression; - } - - private int performDeleteWithSubQuery( - EntityMappingType rootEntityDescriptor, - TableGroup deletingTableGroup, - NamedTableReference rootTableReference, - PredicateCollector predicateCollector, - JdbcParameterBindings jdbcParameterBindings, - MultiTableSqmMutationConverter converter, - SqmJdbcExecutionContextAdapter executionContext) { - final QuerySpec matchingIdSubQuery = new QuerySpec( false, 1 ); - matchingIdSubQuery.getFromClause().addRoot( deletingTableGroup ); - - final TableDetails identifierTableDetails = rootEntityDescriptor.getIdentifierTableDetails(); - final TableDetails.KeyDetails keyDetails = identifierTableDetails.getKeyDetails(); - - final NamedTableReference targetTable = new NamedTableReference( - identifierTableDetails.getTableName(), - DeleteStatement.DEFAULT_ALIAS, - false - ); - - final List idExpressions = new ArrayList<>( keyDetails.getColumnCount() ); - keyDetails.forEachKeyColumn( (position, column) -> { - final Expression columnReference = converter.getSqlExpressionResolver().resolveSqlExpression( - rootTableReference, - column - ); - matchingIdSubQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( position, columnReference ) - ); - idExpressions.add( new ColumnReference( targetTable, column ) ); - } ); - - matchingIdSubQuery.applyPredicate( predicateCollector.getPredicate() ); - final Expression idExpression = idExpressions.size() == 1 - ? idExpressions.get( 0 ) - : new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() ); - - final Assignment softDeleteAssignment = rootEntityDescriptor - .getSoftDeleteMapping() - .createSoftDeleteAssignment( targetTable ); - - final UpdateStatement updateStatement = new UpdateStatement( - targetTable, - singletonList( softDeleteAssignment ), - new InSubQueryPredicate( idExpression, matchingIdSubQuery, false ) - ); - - return executeUpdate( updateStatement, jdbcParameterBindings, executionContext ); - } - - private int performDirectDelete( - EntityMappingType rootEntityDescriptor, - NamedTableReference rootTableReference, - PredicateCollector predicateCollector, - JdbcParameterBindings jdbcParameterBindings, - SqmJdbcExecutionContextAdapter executionContext) { - final Assignment softDeleteAssignment = rootEntityDescriptor - .getSoftDeleteMapping() - .createSoftDeleteAssignment( rootTableReference ); - - final UpdateStatement updateStatement = new UpdateStatement( - rootTableReference, - singletonList( softDeleteAssignment ), - predicateCollector.getPredicate() - ); - - return executeUpdate( updateStatement, jdbcParameterBindings, executionContext ); - } - - private int executeUpdate( - UpdateStatement updateStatement, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final JdbcServices jdbcServices = factory.getJdbcServices(); - - final JdbcOperationQueryMutation jdbcUpdate = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, updateStatement ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - - return jdbcServices.getJdbcMutationExecutor().execute( - jdbcUpdate, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContext - ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedDeleteHandler.java index 0745d197f29c..188887c45c9e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedDeleteHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedDeleteHandler.java @@ -4,21 +4,71 @@ */ package org.hibernate.query.sqm.mutation.internal.temptable; -import java.util.function.Function; - +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn; import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.SelectableConsumer; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.UnionSubclassEntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.AbstractMutationHandler; import org.hibernate.query.sqm.mutation.internal.DeleteHandler; -import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; +import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper; +import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; - +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; +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.from.UnionTableReference; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.predicate.PredicateCollector; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.internal.JdbcParameterImpl; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcMutationExecutor; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; import org.jboss.logging.Logger; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + /** * @author Steve Ebersole */ @@ -27,105 +77,679 @@ public class TableBasedDeleteHandler implements DeleteHandler { private static final Logger log = Logger.getLogger( TableBasedDeleteHandler.class ); - public interface ExecutionDelegate { - int execute(DomainQueryExecutionContext executionContext); - } - private final TemporaryTable idTable; private final TemporaryTableStrategy temporaryTableStrategy; private final boolean forceDropAfterUse; private final Function sessionUidAccess; private final DomainParameterXref domainParameterXref; + private final Map, Map, List>> jdbcParamsXref; + private final Map, MappingModelExpressible> resolvedParameterMappingModelTypes; + private final @Nullable JdbcParameter sessionUidParameter; + private final @Nullable CacheableSqmInterpretation idTableInsert; + private final ArrayList deletes; + private final ArrayList collectionTableDeletes; public TableBasedDeleteHandler( - SqmDeleteStatement sqmDeleteStatement, + SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, TemporaryTable idTable, TemporaryTableStrategy temporaryTableStrategy, boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmDeleteStatement, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmDelete, context.getSession().getSessionFactory() ); this.idTable = idTable; - this.domainParameterXref = domainParameterXref; this.temporaryTableStrategy = temporaryTableStrategy; this.forceDropAfterUse = forceDropAfterUse; this.sessionUidAccess = sessionUidAccess; + + final TemporaryTableSessionUidColumn sessionUidColumn = idTable.getSessionUidColumn(); + if ( sessionUidColumn == null ) { + this.sessionUidParameter = null; + } + else { + this.sessionUidParameter = new JdbcParameterImpl( sessionUidColumn.getJdbcMapping() ); + } + + final MultiTableSqmMutationConverter converter = new MultiTableSqmMutationConverter( + getEntityDescriptor(), + sqmDelete, + sqmDelete.getTarget(), + domainParameterXref, + context.getQueryOptions(), + context.getSession().getLoadQueryInfluencers(), + context.getQueryParameterBindings(), + getSessionFactory().getSqlTranslationEngine() + ); + + final EntityPersister entityDescriptor = + getSessionFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getTarget().getEntityName() ); + final String hierarchyRootTableName = entityDescriptor.getTableName(); + + final TableGroup deletingTableGroup = converter.getMutatingTableGroup(); + + final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( + deletingTableGroup.getNavigablePath(), + hierarchyRootTableName + ); + assert hierarchyRootTableReference != null; + + // Use the converter to interpret the where-clause. We do this for 2 reasons: + // 1) the resolved Predicate is ultimately the base for applying restriction to the deletes + // 2) we also inspect each ColumnReference that is part of the where-clause to see which + // table it comes from. if all of the referenced columns (if any at all) are from the root table + // we can perform all of the deletes without using an id-table + final Predicate specifiedRestriction = converter.visitWhereClause( sqmDelete.getWhereClause() ); + + final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); + entityDescriptor.applyBaseRestrictions( + predicateCollector, + deletingTableGroup, + true, + context.getSession().getLoadQueryInfluencers().getEnabledFilters(), + false, + null, + converter + ); + + converter.pruneTableGroupJoins(); + final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( + hierarchyRootTableReference.getIdentificationVariable() + ); + if ( predicateCollector.getPredicate() != null ) { + predicateCollector.getPredicate().accept( walker ); + } + + // We need an id table if we want to delete from an intermediate table to avoid FK violations + // The intermediate table has a FK to the root table, so we can't delete from the root table first + // Deleting from the intermediate table first also isn't possible, + // because that is the source for deletion in other tables, hence we need an id table + final boolean needsIdTable = !walker.isAllColumnReferencesFromIdentificationVariable() + || entityDescriptor != entityDescriptor.getRootEntityDescriptor(); + + final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + + this.domainParameterXref = domainParameterXref; + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, converter ); + this.resolvedParameterMappingModelTypes = converter.getSqmParameterMappingModelExpressibleResolutions(); + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + if ( sessionUidParameter != null ) { + jdbcParameterBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + idTable.getSessionUidColumn().getJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( context.getSession() ) ) + ) + ); + } + + this.idTableInsert = !needsIdTable ? null : ExecuteWithTemporaryTableHelper.createMatchingIdsIntoIdTableInsert( + converter, + predicateCollector.getPredicate(), + idTable, + sessionUidParameter, + jdbcParameterBindings, + executionContextAdapter + ); + + final ArrayList deletes = new ArrayList<>(); + final ArrayList collectionTableDeletes = new ArrayList<>(); + if ( needsIdTable ) { + final QuerySpec idTableIdentifierSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( + idTable, + sessionUidParameter, + getEntityDescriptor(), + executionContextAdapter + ); + + SqmMutationStrategyHelper.visitCollectionTableDeletes( + getEntityDescriptor(), + (tableReference, attributeMapping) -> { + final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); + final QuerySpec idTableFkSubQuery; + if ( fkDescriptor.getTargetPart().isEntityIdentifierMapping() ) { + idTableFkSubQuery = idTableIdentifierSubQuery; + } + else { + idTableFkSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( + idTable, + fkDescriptor.getTargetPart(), + sessionUidParameter, + getEntityDescriptor(), + executionContextAdapter + ); + } + return new InSubQueryPredicate( + MappingModelCreationHelper.buildColumnReferenceExpression( + new MutatingTableReferenceGroupWrapper( + new NavigablePath( attributeMapping.getRootPathName() ), + attributeMapping, + (NamedTableReference) tableReference + ), + fkDescriptor, + null, + getSessionFactory() + ), + idTableFkSubQuery, + false + ); + + }, + JdbcParameterBindings.NO_BINDINGS, + executionContextAdapter.getQueryOptions(), + collectionTableDeletes::add + ); + + getEntityDescriptor().visitConstraintOrderedTables( + (tableExpression, tableKeyColumnVisitationSupplier) -> deletes.add( createTableDeleteUsingIdTable( + tableExpression, + tableKeyColumnVisitationSupplier, + idTableIdentifierSubQuery, + executionContextAdapter + ) ) + ); + } + else { + final EntityPersister rootEntityPersister = getEntityDescriptor().getEntityPersister(); + final String rootTableName = rootEntityPersister.getTableName(); + final NamedTableReference rootTableReference = (NamedTableReference) deletingTableGroup.resolveTableReference( + deletingTableGroup.getNavigablePath(), + rootTableName + ); + + final QuerySpec matchingIdSubQuerySpec = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( + deletingTableGroup.getNavigablePath(), + rootTableReference, + predicateCollector.getPredicate(), + rootEntityPersister, + converter.getSqlExpressionResolver(), + getSessionFactory() + ); + + SqmMutationStrategyHelper.visitCollectionTableDeletes( + getEntityDescriptor(), + (tableReference, attributeMapping) -> { + // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup + if ( predicateCollector.getPredicate() == null ) { + return null; + } + final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); + final QuerySpec idSelectFkSubQuery; + // todo (6.0): based on the location of the attribute mapping, we could prune the table group of the subquery + if ( fkDescriptor.getTargetPart().isEntityIdentifierMapping() ) { + idSelectFkSubQuery = matchingIdSubQuerySpec; + } + else { + idSelectFkSubQuery = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( + deletingTableGroup.getNavigablePath(), + rootTableReference, + predicateCollector.getPredicate(), + rootEntityPersister, + converter.getSqlExpressionResolver(), + getSessionFactory() + ); + } + return new InSubQueryPredicate( + MappingModelCreationHelper.buildColumnReferenceExpression( + new MutatingTableReferenceGroupWrapper( + new NavigablePath( attributeMapping.getRootPathName() ), + attributeMapping, + (NamedTableReference) tableReference + ), + fkDescriptor, + null, + getSessionFactory() + ), + idSelectFkSubQuery, + false + ); + + }, + jdbcParameterBindings, + executionContextAdapter.getQueryOptions(), + collectionTableDeletes::add + ); + + if ( rootTableReference instanceof UnionTableReference ) { + getEntityDescriptor().visitConstraintOrderedTables( + (tableExpression, tableKeyColumnVisitationSupplier) -> { + final NamedTableReference tableReference = new NamedTableReference( + tableExpression, + deletingTableGroup.getPrimaryTableReference().getIdentificationVariable() + ); + final QuerySpec idMatchingSubQuerySpec; + // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup + if ( predicateCollector.getPredicate() == null ) { + idMatchingSubQuerySpec = null; + } + else { + idMatchingSubQuerySpec = matchingIdSubQuerySpec; + } + deletes.add( createNonRootTableDeleteWithoutIdTable( + tableReference, + tableKeyColumnVisitationSupplier, + converter.getSqlExpressionResolver(), + deletingTableGroup, + idMatchingSubQuerySpec, + jdbcParameterBindings, + executionContextAdapter + ) ); + } + ); + } + else { + getEntityDescriptor().visitConstraintOrderedTables( + (tableExpression, tableKeyColumnVisitationSupplier) -> { + if ( !tableExpression.equals( rootTableName ) ) { + final NamedTableReference tableReference = (NamedTableReference) deletingTableGroup.getTableReference( + deletingTableGroup.getNavigablePath(), + tableExpression, + true + ); + final QuerySpec idMatchingSubQuerySpec; + // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup + if ( predicateCollector.getPredicate() == null ) { + idMatchingSubQuerySpec = null; + } + else { + idMatchingSubQuerySpec = matchingIdSubQuerySpec; + } + deletes.add( createNonRootTableDeleteWithoutIdTable( + tableReference, + tableKeyColumnVisitationSupplier, + converter.getSqlExpressionResolver(), + deletingTableGroup, + idMatchingSubQuerySpec, + jdbcParameterBindings, + executionContextAdapter + ) ); + } + } + ); + + deletes.add( createRootTableDeleteWithoutIdTable( + rootTableReference, + predicateCollector.getPredicate(), + jdbcParameterBindings, + executionContextAdapter + ) ); + } + } + this.deletes = deletes; + this.collectionTableDeletes = collectionTableDeletes; + firstJdbcParameterBindingsConsumer.set( jdbcParameterBindings ); + } + + private JdbcOperationQueryMutation createTableDeleteUsingIdTable( + String tableExpression, + Supplier> tableKeyColumnVisitationSupplier, + QuerySpec idTableSubQuery, + ExecutionContext executionContext) { + final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() ); + final NamedTableReference targetTable = new NamedTableReference( + tableExpression, + DeleteStatement.DEFAULT_ALIAS, + true + ); + + tableKeyColumnVisitationSupplier.get().accept( + (columnIndex, selection) -> { + assert selection.getContainingTableExpression().equals( tableExpression ); + assert ! selection.isFormula(); + assert selection.getCustomReadExpression() == null; + assert selection.getCustomWriteExpression() == null; + + keyColumnCollector.apply( + new ColumnReference( + targetTable, + selection + ) + ); + } + ); + + final InSubQueryPredicate predicate = new InSubQueryPredicate( + keyColumnCollector.buildKeyExpression(), + idTableSubQuery, + false + ); + + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + + final JdbcServices jdbcServices = factory.getJdbcServices(); + + return jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, new DeleteStatement( targetTable, predicate ) ) + .translate( JdbcParameterBindings.NO_BINDINGS, executionContext.getQueryOptions() ); + } + + + private JdbcOperationQueryMutation createRootTableDeleteWithoutIdTable( + NamedTableReference rootTableReference, + Predicate predicate, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + + final JdbcServices jdbcServices = factory.getJdbcServices(); + + return jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, new DeleteStatement( rootTableReference, predicate ) ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + } + + private JdbcOperationQueryMutation createNonRootTableDeleteWithoutIdTable( + NamedTableReference targetTableReference, + Supplier> tableKeyColumnVisitationSupplier, + SqlExpressionResolver sqlExpressionResolver, + TableGroup rootTableGroup, + QuerySpec matchingIdSubQuerySpec, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + assert targetTableReference != null; + + final NamedTableReference deleteTableReference = new NamedTableReference( + targetTableReference.getTableExpression(), + DeleteStatement.DEFAULT_ALIAS, + true + ); + final Predicate tableDeletePredicate; + if ( matchingIdSubQuerySpec == null ) { + tableDeletePredicate = null; + } + else { + /* + * delete from sub_table + * where sub_id in ( + * select root_id from root_table + * where {predicate} + * ) + */ + + /* + * Create the `sub_id` reference as the LHS of the in-subquery predicate + */ + final List deletingTableColumnRefs = new ArrayList<>(); + tableKeyColumnVisitationSupplier.get().accept( + (columnIndex, selection) -> { + assert deleteTableReference.getTableReference( selection.getContainingTableExpression() ) != null; + + final Expression expression = sqlExpressionResolver.resolveSqlExpression( + deleteTableReference, + selection + ); + + deletingTableColumnRefs.add( (ColumnReference) expression ); + } + ); + + final Expression deletingTableColumnRefsExpression; + if ( deletingTableColumnRefs.size() == 1 ) { + deletingTableColumnRefsExpression = deletingTableColumnRefs.get( 0 ); + } + else { + deletingTableColumnRefsExpression = new SqlTuple( deletingTableColumnRefs, getEntityDescriptor().getIdentifierMapping() ); + } + + tableDeletePredicate = new InSubQueryPredicate( + deletingTableColumnRefsExpression, + matchingIdSubQuerySpec, + false + ); + } + + final DeleteStatement sqlAstDelete = new DeleteStatement( deleteTableReference, tableDeletePredicate ); + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + + final JdbcServices jdbcServices = factory.getJdbcServices(); + + return jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, sqlAstDelete ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + } + + @Override + public JdbcParameterBindings createJdbcParameterBindings(DomainQueryExecutionContext context) { + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override + @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + if ( sessionUidParameter != null ) { + jdbcParameterBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + idTable.getSessionUidColumn().getJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( context.getSession() ) ) + ) + ); + } + return jdbcParameterBindings; + } + + @Override + public boolean dependsOnParameterBindings() { + if ( idTableInsert != null && idTableInsert.jdbcOperation().dependsOnParameterBindings() ) { + return true; + } + for ( JdbcOperationQueryMutation delete : deletes ) { + if ( delete.dependsOnParameterBindings() ) { + return true; + } + } + for ( JdbcOperationQueryMutation delete : collectionTableDeletes ) { + if ( delete.dependsOnParameterBindings() ) { + return true; + } + } + return false; + } + + @Override + public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + if ( idTableInsert != null + && !idTableInsert.jdbcOperation().isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + for ( JdbcOperationQueryMutation delete : deletes ) { + if ( !delete.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + } + for ( JdbcOperationQueryMutation delete : collectionTableDeletes ) { + if ( !delete.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + } + return true; } @Override - public int execute(DomainQueryExecutionContext executionContext) { + public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { if ( log.isTraceEnabled() ) { log.tracef( "Starting multi-table delete execution - %s", - getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() + getSqmStatement().getTarget().getModel().getName() ); } - return resolveDelegate( executionContext ).execute( executionContext ); - } - - protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - if ( getEntityDescriptor().getSoftDeleteMapping() != null ) { - return new SoftDeleteExecutionDelegate( - getEntityDescriptor(), + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + if ( idTableInsert != null ) { + ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( idTable, temporaryTableStrategy, - forceDropAfterUse, - getSqmDeleteOrUpdateStatement(), - domainParameterXref, - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - sessionUidAccess, - getSessionFactory() + executionContext ); + + try { + final int rows = ExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + idTableInsert.jdbcOperation(), + jdbcParameterBindings, + executionContext + ); + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ) + ) + ); + } + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final JdbcMutationExecutor jdbcMutationExecutor = factory.getJdbcServices().getJdbcMutationExecutor(); + for ( JdbcOperationQueryMutation delete : collectionTableDeletes ) { + jdbcMutationExecutor.execute( + delete, + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + for ( JdbcOperationQueryMutation delete : deletes ) { + jdbcMutationExecutor.execute( + delete, + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + return rows; + } + finally { + ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( + idTable, + sessionUidAccess, + getAfterUseAction(), + executionContext + ); + } } + else { + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final JdbcMutationExecutor jdbcMutationExecutor = factory.getJdbcServices().getJdbcMutationExecutor(); + for ( JdbcOperationQueryMutation delete : collectionTableDeletes ) { + jdbcMutationExecutor.execute( + delete, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } - return new RestrictedDeleteExecutionDelegate( - getEntityDescriptor(), - idTable, - temporaryTableStrategy, - forceDropAfterUse, - getSqmDeleteOrUpdateStatement(), - domainParameterXref, - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - sessionUidAccess, - getSessionFactory() - ); + if ( getEntityDescriptor() instanceof UnionSubclassEntityPersister ) { + int rows = 0; + for ( JdbcOperationQueryMutation delete : deletes ) { + rows += jdbcMutationExecutor.execute( + delete, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + return rows; + } + else { + int rows = 0; + for ( JdbcOperationQueryMutation delete : deletes ) { + rows = jdbcMutationExecutor.execute( + delete, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + return rows; + } + } } - // Getters for Hibernate Reactive - protected TemporaryTable getIdTable() { - return idTable; + // For Hibernate Reactive + public @Nullable CacheableSqmInterpretation getIdTableInsert() { + return idTableInsert; } + // For Hibernate Reactive + protected ArrayList getDeletes() { + return deletes; + } + + // For Hibernate Reactive + protected ArrayList getCollectionTableDeletes() { + return collectionTableDeletes; + } + + // For Hibernate Reactive protected AfterUseAction getAfterUseAction() { return forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(); } - protected TemporaryTableStrategy getTemporaryTableStrategy() { - return temporaryTableStrategy; + // For Hibernate Reactive + protected TemporaryTable getIdTable() { + return idTable; } - protected boolean isForceDropAfterUse() { - return forceDropAfterUse; + // For Hibernate Reactive + protected TemporaryTableStrategy getTemporaryTableStrategy() { + return temporaryTableStrategy; } + // For Hibernate Reactive protected Function getSessionUidAccess() { return sessionUidAccess; } - protected DomainParameterXref getDomainParameterXref() { - return domainParameterXref; - } - - @Override - public SqmDeleteStatement getSqmDeleteOrUpdateStatement() { - return (SqmDeleteStatement) super.getSqmDeleteOrUpdateStatement(); + // For Hibernate Reactive + protected @Nullable JdbcParameter getSessionUidParameter() { + return sessionUidParameter; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java index bef567667987..d3c9c788733c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java @@ -4,29 +4,67 @@ */ package org.hibernate.query.sqm.mutation.internal.temptable; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.stream.IntStream; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableColumn; import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn; import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.FetchTiming; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.Generator; import org.hibernate.generator.OnExecutionGenerator; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; +import org.hibernate.id.OptimizableGenerator; +import org.hibernate.id.enhanced.Optimizer; +import org.hibernate.id.insert.Binder; +import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.SemanticException; +import org.hibernate.query.SortDirection; +import org.hibernate.query.common.FetchClauseType; +import org.hibernate.query.results.internal.TableGroupImpl; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.ComparisonOperator; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.AbstractMutationHandler; import org.hibernate.query.sqm.mutation.internal.InsertHandler; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.SqmInsertStrategyHelper; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; @@ -39,39 +77,57 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; +import org.hibernate.sql.ast.tree.from.UnionTableReference; import org.hibernate.sql.ast.tree.insert.ConflictClause; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; import org.hibernate.sql.ast.tree.insert.Values; +import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.ast.tree.select.SortSpecification; +import org.hibernate.sql.ast.tree.update.Assignable; import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcParameterImpl; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBinder; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.sql.results.spi.ListResultsConsumer; import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.ValueBinder; import org.jboss.logging.Logger; +import static org.hibernate.generator.EventType.INSERT; import static org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper.isId; /** * @author Christian Beikov */ -public class TableBasedInsertHandler implements InsertHandler { +public class TableBasedInsertHandler extends AbstractMutationHandler implements InsertHandler { private static final Logger log = Logger.getLogger( TableBasedInsertHandler.class ); - public interface ExecutionDelegate { - int execute(ExecutionContext executionContext); - } - - private final SqmInsertStatement sqmInsertStatement; - private final SessionFactoryImplementor sessionFactory; - private final TemporaryTable entityTable; private final TemporaryTableStrategy temporaryTableStrategy; private final boolean forceDropAfterUse; private final Function sessionUidAccess; private final DomainParameterXref domainParameterXref; - private final JdbcParameter sessionUidParameter; + private final Map, Map, List>> jdbcParamsXref; + private final Map, MappingModelExpressible> resolvedParameterMappingModelTypes; + private final @Nullable JdbcParameter sessionUidParameter; + + private final CacheableSqmInterpretation temporaryTableInsert; + private final RootTableInserter rootTableInserter; + private final List nonRootTableInserts; public TableBasedInsertHandler( SqmInsertStatement sqmInsert, @@ -80,14 +136,13 @@ public TableBasedInsertHandler( TemporaryTableStrategy temporaryTableStrategy, boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - this.sqmInsertStatement = sqmInsert; + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmInsert, context.getSession().getSessionFactory() ); this.temporaryTableStrategy = temporaryTableStrategy; this.forceDropAfterUse = forceDropAfterUse; - this.sessionFactory = sessionFactory; this.entityTable = entityTable; this.sessionUidAccess = sessionUidAccess; - this.domainParameterXref = domainParameterXref; final TemporaryTableSessionUidColumn sessionUidColumn = entityTable.getSessionUidColumn(); if ( sessionUidColumn == null ) { @@ -96,42 +151,19 @@ public TableBasedInsertHandler( else { this.sessionUidParameter = new JdbcParameterImpl( sessionUidColumn.getJdbcMapping() ); } - } - - public SqmInsertStatement getSqmInsertStatement() { - return sqmInsertStatement; - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - if ( log.isTraceEnabled() ) { - log.tracef( - "Starting multi-table insert execution - %s", - getSqmInsertStatement().getTarget().getModel().getName() - ); - } - - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); - return resolveDelegate( executionContext ).execute( executionContextAdapter ); - } - - protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - final EntityPersister entityDescriptor = - sessionFactory.getMappingMetamodel() - .getEntityDescriptor( getSqmInsertStatement().getTarget().getEntityName() ); - - final MultiTableSqmMutationConverter converterDelegate = new MultiTableSqmMutationConverter( - entityDescriptor, - getSqmInsertStatement(), - getSqmInsertStatement().getTarget(), + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( + getEntityDescriptor(), + sqmInsert, + sqmInsert.getTarget(), domainParameterXref, executionContext.getQueryOptions(), executionContext.getSession().getLoadQueryInfluencers(), executionContext.getQueryParameterBindings(), - sessionFactory.getSqlTranslationEngine() + getSessionFactory().getSqlTranslationEngine() ); - final TableGroup insertingTableGroup = converterDelegate.getMutatingTableGroup(); + final TableGroup insertingTableGroup = sqmConverter.getMutatingTableGroup(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // visit the insertion target using our special converter, collecting @@ -145,11 +177,11 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio ); final InsertSelectStatement insertStatement = new InsertSelectStatement( entityTableReference ); - final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = converterDelegate.visitInsertionTargetPaths( + final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = sqmConverter.visitInsertionTargetPaths( (assignable, columnReferences) -> { final SqmPathInterpretation pathInterpretation = (SqmPathInterpretation) assignable; final List columns = - entityTable.findTemporaryTableColumns( entityDescriptor, pathInterpretation.getExpressionType() ); + entityTable.findTemporaryTableColumns( getEntityDescriptor(), pathInterpretation.getExpressionType() ); for ( TemporaryTableColumn column : columns ) { insertStatement.addTargetColumnReference( new ColumnReference( entityTableReference, @@ -159,8 +191,8 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio } targetPathColumns.add( new Assignment( assignable, (Expression) assignable ) ); }, - sqmInsertStatement, - entityDescriptor, + sqmInsert, + getEntityDescriptor(), insertingTableGroup ); @@ -168,12 +200,11 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio // visit the where-clause using our special converter, collecting information // about the restrictions - final TemporaryTableSessionUidColumn sessionUidColumn = entityTable.getSessionUidColumn(); - if ( sqmInsertStatement instanceof SqmInsertSelectStatement ) { - final QueryPart queryPart = converterDelegate.visitQueryPart( ( (SqmInsertSelectStatement) sqmInsertStatement ).getSelectQueryPart() ); + if ( sqmInsert instanceof SqmInsertSelectStatement ) { + final QueryPart queryPart = sqmConverter.visitQueryPart( ( (SqmInsertSelectStatement) sqmInsert ).getSelectQueryPart() ); queryPart.visitQuerySpecs( querySpec -> { - if ( additionalInsertValues.applySelections( querySpec, sessionFactory ) ) { + if ( additionalInsertValues.applySelections( querySpec, getSessionFactory() ) ) { final TemporaryTableColumn rowNumberColumn = entityTable.getColumns() .get( entityTable.getColumns().size() - ( sessionUidColumn == null ? 1 : 2 ) ); final ColumnReference columnReference = new ColumnReference( @@ -192,7 +223,7 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio new Assignment( columnReference, columnReference ) ); } - else if ( !(entityDescriptor.getGenerator() instanceof OnExecutionGenerator generator + else if ( !(getEntityDescriptor().getGenerator() instanceof OnExecutionGenerator generator && generator.generatedOnExecution()) && !entityTable.isRowNumberGenerated() ) { final TemporaryTableColumn rowNumberColumn = entityTable.getColumns() @@ -211,7 +242,7 @@ else if ( !(entityDescriptor.getGenerator() instanceof OnExecutionGenerator gene 0, SqmInsertStrategyHelper.createRowNumberingExpression( querySpec, - sessionFactory + getSessionFactory() ) ) ); @@ -238,7 +269,7 @@ else if ( !(entityDescriptor.getGenerator() instanceof OnExecutionGenerator gene else { // Add the row number column if there is one final BasicType rowNumberType; - if ( !(entityDescriptor.getGenerator() instanceof OnExecutionGenerator generator + if ( !(getEntityDescriptor().getGenerator() instanceof OnExecutionGenerator generator && generator.generatedOnExecution()) && !entityTable.isRowNumberGenerated() ) { final TemporaryTableColumn rowNumberColumn = entityTable.getColumns() @@ -268,16 +299,16 @@ else if ( !(entityDescriptor.getGenerator() instanceof OnExecutionGenerator gene insertStatement.getTargetColumns().add( sessionUidColumnReference ); targetPathColumns.add( new Assignment( sessionUidColumnReference, sessionUidParameter ) ); } - final List sqmValuesList = ( (SqmInsertValuesStatement) sqmInsertStatement ).getValuesList(); + final List sqmValuesList = ( (SqmInsertValuesStatement) sqmInsert ).getValuesList(); final List valuesList = new ArrayList<>( sqmValuesList.size() ); for ( int i = 0; i < sqmValuesList.size(); i++ ) { - final Values values = converterDelegate.visitValues( sqmValuesList.get( i ) ); + final Values values = sqmConverter.visitValues( sqmValuesList.get( i ) ); additionalInsertValues.applyValues( values ); if ( rowNumberType != null ) { values.getExpressions().add( new QueryLiteral<>( rowNumberType.getJavaTypeDescriptor() - .wrap( i + 1, sessionFactory.getWrapperOptions() ), + .wrap( i + 1, getSessionFactory().getWrapperOptions() ), rowNumberType ) ); @@ -289,8 +320,8 @@ else if ( !(entityDescriptor.getGenerator() instanceof OnExecutionGenerator gene } insertStatement.setValuesList( valuesList ); } - final ConflictClause conflictClause = converterDelegate.visitConflictClause( sqmInsertStatement.getConflictClause() ); - converterDelegate.pruneTableGroupJoins(); + final ConflictClause conflictClause = sqmConverter.visitConflictClause( sqmInsert.getConflictClause() ); + sqmConverter.pruneTableGroupJoins(); boolean assignsId = false; for ( Assignment assignment : targetPathColumns ) { @@ -308,62 +339,129 @@ else if ( !(entityDescriptor.getGenerator() instanceof OnExecutionGenerator gene collectTableReference( insertingTableGroup.getTableReferenceJoins().get( i ), tableReferenceByAlias::put ); } - return buildExecutionDelegate( - sqmInsertStatement, - converterDelegate, - entityTable, - temporaryTableStrategy, - forceDropAfterUse, - sessionUidAccess, + final ModelPartContainer updatingModelPart = insertingTableGroup.getModelPart(); + assert updatingModelPart instanceof EntityMappingType; + + final Map> assignmentsByTable = + CollectionHelper.mapOfSize( insertingTableGroup.getTableReferenceJoins().size() + 1 ); + + this.domainParameterXref = domainParameterXref; + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ); + this.resolvedParameterMappingModelTypes = sqmConverter.getSqmParameterMappingModelExpressibleResolutions(); + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), domainParameterXref, - insertingTableGroup, - tableReferenceByAlias, - targetPathColumns, - assignsId, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + if ( sessionUidParameter != null ) { + jdbcParameterBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + entityTable.getSessionUidColumn().getJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( context.getSession() ) ) + ) + ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // segment the assignments by table-reference + + for ( int i = 0; i < targetPathColumns.size(); i++ ) { + final Assignment assignment = targetPathColumns.get( i ); + final Assignable assignable = assignment.getAssignable(); + final List assignmentColumnRefs = assignable.getColumnReferences(); + + TableReference assignmentTableReference = null; + + for ( int c = 0; c < assignmentColumnRefs.size(); c++ ) { + final ColumnReference columnReference = assignmentColumnRefs.get( c ); + final TableReference tableReference = resolveTableReference( columnReference, tableReferenceByAlias ); + + if ( assignmentTableReference != null && assignmentTableReference != tableReference ) { + throw new SemanticException( "Assignment referred to columns from multiple tables: " + i ); + } + assignmentTableReference = tableReference; + } + + assignmentsByTable.computeIfAbsent( + assignmentTableReference == null ? null : assignmentTableReference.getTableId(), + k -> new ArrayList<>() ).add( assignment ); + } + + this.temporaryTableInsert = ExecuteWithTemporaryTableHelper.createTemporaryTableInsert( insertStatement, - conflictClause, - sessionUidParameter, + jdbcParameterBindings, executionContext ); - } - /** - * For Hibernate Reactive - */ - protected ExecutionDelegate buildExecutionDelegate( - SqmInsertStatement sqmInsert, - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable entityTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup insertingTableGroup, - Map tableReferenceByAlias, - List assignments, - boolean assignsId, - InsertSelectStatement insertStatement, - ConflictClause conflictClause, - JdbcParameter sessionUidParameter, - DomainQueryExecutionContext executionContext) { - return new InsertExecutionDelegate( - sqmConverter, - entityTable, - temporaryTableStrategy, - forceDropAfterUse, - sessionUidAccess, - domainParameterXref, - insertingTableGroup, - tableReferenceByAlias, - assignments, - assignsId, + final int tableSpan = getEntityDescriptor().getTableSpan(); + this.rootTableInserter = createRootTableInserter( insertStatement, + insertingTableGroup, conflictClause, - sessionUidParameter, + assignsId, + getEntityDescriptor().getTableName( 0 ), + getEntityDescriptor().getKeyColumns( 0 ), + assignmentsByTable, executionContext ); - } + final ArrayList nonRootTableInserts = new ArrayList<>( tableSpan ); + if ( getEntityDescriptor().hasDuplicateTables() ) { + final String[] insertedTables = new String[tableSpan]; + insertedTables[0] = getEntityDescriptor().getTableName( 0 ); + for ( int i = 1; i < tableSpan; i++ ) { + if ( getEntityDescriptor().isInverseTable( i ) ) { + continue; + } + final String tableName = getEntityDescriptor().getTableName( i ); + insertedTables[i] = tableName; + if ( ArrayHelper.indexOf( insertedTables, i, tableName ) != -1 ) { + // Since secondary tables could appear multiple times, we have to skip duplicates + continue; + } + final JdbcOperationQueryMutation insert = createTableInsert( + insertStatement, + insertingTableGroup, + tableName, + getEntityDescriptor().getKeyColumns( i ), + getEntityDescriptor().isNullableTable( i ), + assignmentsByTable, + executionContext + ); + if ( insert != null ) { + nonRootTableInserts.add( insert ); + } + } + } + else { + for ( int i = 1; i < tableSpan; i++ ) { + final JdbcOperationQueryMutation insert = createTableInsert( + insertStatement, + insertingTableGroup, + getEntityDescriptor().getTableName( i ), + getEntityDescriptor().getKeyColumns( i ), + getEntityDescriptor().isNullableTable( i ), + assignmentsByTable, + executionContext + ); + if ( insert != null ) { + nonRootTableInserts.add( insert ); + } + } + } + this.nonRootTableInserts = nonRootTableInserts; + firstJdbcParameterBindingsConsumer.set( jdbcParameterBindings ); + } private void collectTableReference( TableReference tableReference, @@ -377,5 +475,858 @@ private void collectTableReference( collectTableReference( tableReferenceJoin.getJoinedTableReference(), consumer ); } + protected record RootTableInserter( + @Nullable JdbcOperationQuerySelect temporaryTableIdentitySelect, + @Nullable JdbcOperationQueryMutation temporaryTableIdUpdate, + @Nullable String temporaryTableRowNumberSelectSql, + JdbcOperationQueryMutation rootTableInsert, + @Nullable String rootTableInsertWithReturningSql, + @Nullable JdbcOperationQueryMutation temporaryTableIdentityUpdate + ) {} + + private RootTableInserter createRootTableInserter( + InsertSelectStatement translatedInsertStatement, + TableGroup updatingTableGroup, + ConflictClause conflictClause, + boolean assignsId, + String tableExpression, + String[] keyColumns, + Map> assignmentsByTable, + ExecutionContext executionContext) { + final TableReference updatingTableReference = updatingTableGroup.getTableReference( + updatingTableGroup.getNavigablePath(), + tableExpression, + true + ); + + final Generator generator = getEntityDescriptor().getGenerator(); + final List assignments = assignmentsByTable.get( tableExpression ); + if ( !assignsId + && (assignments == null || assignments.isEmpty()) + && !generator.generatedOnExecution() + && (!(generator instanceof BulkInsertionCapableIdentifierGenerator) + || ((BulkInsertionCapableIdentifierGenerator) generator).supportsBulkInsertionIdentifierGeneration()) ) { + throw new IllegalStateException( "There must be at least a single root table assignment" ); + } + + final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Create the SQL AST and convert it into a JdbcOperation + final QuerySpec querySpec = new QuerySpec( true ); + final NamedTableReference temporaryTableReference = new NamedTableReference( + translatedInsertStatement.getTargetTable().getTableExpression(), + "hte_tmp" + ); + final TableGroupImpl temporaryTableGroup = new TableGroupImpl( + updatingTableGroup.getNavigablePath(), + null, + temporaryTableReference, + getEntityDescriptor() + ); + querySpec.getFromClause().addRoot( temporaryTableGroup ); + if ( translatedInsertStatement.getValuesList().size() == 1 && conflictClause != null ) { + // Potentially apply a limit 1 to allow the use of the conflict clause emulation + querySpec.setFetchClauseExpression( + new QueryLiteral<>( + 1, + executionContext.getSession().getFactory().getQueryEngine().getCriteriaBuilder().getIntegerType() + ), + FetchClauseType.ROWS_ONLY + ); + } + final InsertSelectStatement insertStatement = new InsertSelectStatement( dmlTableReference ); + insertStatement.setConflictClause( conflictClause ); + insertStatement.setSourceSelectStatement( querySpec ); + applyAssignments( assignments, insertStatement, temporaryTableReference, getEntityDescriptor() ); + final JdbcServices jdbcServices = getSessionFactory().getJdbcServices(); + final SharedSessionContractImplementor session = executionContext.getSession(); + final JdbcOperationQuerySelect temporaryTableIdentitySelect; + final JdbcOperationQueryMutation temporaryTableIdUpdate; + final String temporaryTableRowNumberSelectSql; + final JdbcOperationQueryMutation rootTableInsert; + final JdbcOperationQueryMutation temporaryTableIdentityUpdate; + final String rootTableInsertWithReturningSql; + if ( !assignsId && generator.generatedOnExecution() ) { + final BasicEntityIdentifierMapping identifierMapping = + (BasicEntityIdentifierMapping) getEntityDescriptor().getIdentifierMapping(); + final QuerySpec idSelectQuerySpec = new QuerySpec( true ); + idSelectQuerySpec.getFromClause().addRoot( temporaryTableGroup ); + final ColumnReference columnReference = new ColumnReference( + (String) null, + TemporaryTable.ENTITY_TABLE_IDENTITY_COLUMN, + false, + null, + identifierMapping.getJdbcMapping() + ); + idSelectQuerySpec.getSelectClause() + .addSqlSelection( new SqlSelectionImpl( 0, columnReference ) ); + idSelectQuerySpec.addSortSpecification( + new SortSpecification( + columnReference, + SortDirection.ASCENDING + ) + ); + if ( entityTable.getSessionUidColumn() != null ) { + final TemporaryTableColumn sessionUidColumn = entityTable.getSessionUidColumn(); + idSelectQuerySpec.applyPredicate( new ComparisonPredicate( + new ColumnReference( + temporaryTableReference, + sessionUidColumn.getColumnName(), + false, + null, + sessionUidColumn.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + sessionUidParameter + ) ); + } + final SelectStatement selectStatement = new SelectStatement( + idSelectQuerySpec, + Collections.singletonList( + new BasicFetch<>( + 0, + null, + null, + identifierMapping, + FetchTiming.IMMEDIATE, + null, + false + ) + ) + ); + temporaryTableIdentitySelect = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildSelectTranslator( getSessionFactory(), selectStatement ) + .translate( null, executionContext.getQueryOptions() ); + temporaryTableIdUpdate = null; + temporaryTableRowNumberSelectSql = null; + + querySpec.applyPredicate( + new ComparisonPredicate( + columnReference, + ComparisonOperator.EQUAL, + new JdbcParameterImpl( identifierMapping.getJdbcMapping() ) + ) + ); + } + else { + temporaryTableIdentitySelect = null; + // if the target paths don't already contain the id, and we need identifier generation, + // then we load update rows from the temporary table with the generated identifiers, + // to then insert into the target tables in once statement + if ( insertStatement.getTargetColumns().stream() + .noneMatch( c -> keyColumns[0].equals( c.getColumnExpression() ) ) ) { + final EntityIdentifierMapping identifierMapping = getEntityDescriptor().getIdentifierMapping(); + final List primaryKeyTableColumns = + getPrimaryKeyTableColumns( getEntityDescriptor(), entityTable ); + + final TemporaryTableColumn sessionUidColumn; + final Predicate sessionUidPredicate; + if ( entityTable.getSessionUidColumn() == null ) { + sessionUidColumn = null; + sessionUidPredicate = null; + } + else { + sessionUidColumn = entityTable.getSessionUidColumn(); + sessionUidPredicate = new ComparisonPredicate( + new ColumnReference( + (String) null, + sessionUidColumn.getColumnName(), + false, + null, + sessionUidColumn.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + sessionUidParameter + ); + } + if ( needsIdentifierGeneration( generator, assignsId ) ) { + final BasicEntityIdentifierMapping basicIdentifierMapping = (BasicEntityIdentifierMapping) identifierMapping; + final JdbcParameter rootIdentity = new JdbcParameterImpl( basicIdentifierMapping.getJdbcMapping() ); + final List temporaryTableAssignments = new ArrayList<>( 1 ); + final ColumnReference idColumnReference = new ColumnReference( (String) null, basicIdentifierMapping ); + temporaryTableAssignments.add( new Assignment( idColumnReference, rootIdentity ) ); + + final int rowNumberIndex = + entityTable.getColumns().size() - (entityTable.getSessionUidColumn() == null ? 1 : 2); + final TemporaryTableColumn rowNumberColumn = entityTable.getColumns().get( rowNumberIndex ); + final JdbcParameter rowNumber = new JdbcParameterImpl( rowNumberColumn.getJdbcMapping() ); + + final UpdateStatement updateStatement = new UpdateStatement( + temporaryTableReference, + temporaryTableAssignments, + Predicate.combinePredicates( + new ComparisonPredicate( + new ColumnReference( + (String) null, + rowNumberColumn.getColumnName(), + false, + null, + rowNumberColumn.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + rowNumber + ), + sessionUidPredicate + ) + ); + + temporaryTableIdUpdate = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( getSessionFactory(), updateStatement ) + .translate( null, executionContext.getQueryOptions() ); + temporaryTableRowNumberSelectSql = ExecuteWithTemporaryTableHelper.createInsertedRowNumbersSelectSql( + entityTable, + sessionUidAccess, + executionContext + ); + } + else { + temporaryTableIdUpdate = null; + temporaryTableRowNumberSelectSql = null; + } + + identifierMapping.forEachSelectable( 0, (selectionIndex, selectableMapping) -> { + insertStatement.addTargetColumnReferences( + new ColumnReference( + (String) null, + keyColumns[selectionIndex], + false, + null, + selectableMapping.getJdbcMapping() + ) + ); + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + new ColumnReference( + temporaryTableReference.getIdentificationVariable(), + primaryKeyTableColumns.get( selectionIndex ).getColumnName(), + false, + null, + selectableMapping.getJdbcMapping() + ) + ) + ); + } ); + } + else { + temporaryTableIdUpdate = null; + temporaryTableRowNumberSelectSql = null; + } + } + if ( entityTable.getSessionUidColumn() != null ) { + final TemporaryTableColumn sessionUidColumn = entityTable.getSessionUidColumn(); + querySpec.applyPredicate( new ComparisonPredicate( + new ColumnReference( + temporaryTableReference, + sessionUidColumn.getColumnName(), + false, + null, + sessionUidColumn.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + sessionUidParameter + ) ); + } + + rootTableInsert = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( getSessionFactory(), insertStatement ) + .translate( null, executionContext.getQueryOptions() ); + + if ( !assignsId && generator.generatedOnExecution() ) { + final GeneratedValuesMutationDelegate insertDelegate = getEntityDescriptor().getInsertDelegate(); + // todo 7.0 : InsertGeneratedIdentifierDelegate will be removed once we're going to handle + // generated values within the jdbc insert operaetion itself + final InsertGeneratedIdentifierDelegate identifierDelegate = (InsertGeneratedIdentifierDelegate) insertDelegate; + rootTableInsertWithReturningSql = identifierDelegate.prepareIdentifierGeneratingInsert( rootTableInsert.getSqlString() ); + final BasicEntityIdentifierMapping identifierMapping = + (BasicEntityIdentifierMapping) getEntityDescriptor().getIdentifierMapping(); + + final List primaryKeyTableColumns = + getPrimaryKeyTableColumns( getEntityDescriptor(), entityTable ); + assert primaryKeyTableColumns.size() == 1; + + final JdbcParameter entityIdentity = new JdbcParameterImpl( identifierMapping.getJdbcMapping() ); + final JdbcParameter rootIdentity = new JdbcParameterImpl( identifierMapping.getJdbcMapping() ); + final List temporaryTableAssignments = new ArrayList<>( 1 ); + temporaryTableAssignments.add( + new Assignment( + new ColumnReference( + (String) null, + primaryKeyTableColumns.get( 0 ).getColumnName(), + false, + null, + primaryKeyTableColumns.get( 0 ).getJdbcMapping() + ), + rootIdentity + ) + ); + final UpdateStatement updateStatement = new UpdateStatement( + temporaryTableReference, + temporaryTableAssignments, + new ComparisonPredicate( + new ColumnReference( + (String) null, + TemporaryTable.ENTITY_TABLE_IDENTITY_COLUMN, + false, + null, + identifierMapping.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + entityIdentity + ) + ); + + temporaryTableIdentityUpdate = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( getSessionFactory(), updateStatement ) + .translate( null, executionContext.getQueryOptions() ); + } + else { + rootTableInsertWithReturningSql = null; + temporaryTableIdentityUpdate = null; + } + return new RootTableInserter( + temporaryTableIdentitySelect, + temporaryTableIdUpdate, + temporaryTableRowNumberSelectSql, + rootTableInsert, + rootTableInsertWithReturningSql, + temporaryTableIdentityUpdate + ); + } + + private boolean needsIdentifierGeneration(Generator identifierGenerator, boolean assignsId) { + if ( !assignsId && identifierGenerator instanceof OptimizableGenerator ) { + // If the generator uses an optimizer or is not bulk insertion capable, + // we have to generate identifiers for the new rows, as that couldn't + // have been done via a SQL expression + final Optimizer optimizer = ( (OptimizableGenerator) identifierGenerator ).getOptimizer(); + return optimizer != null && optimizer.getIncrementSize() > 1 + || identifierGenerator instanceof BulkInsertionCapableIdentifierGenerator + && !( (BulkInsertionCapableIdentifierGenerator) identifierGenerator ) + .supportsBulkInsertionIdentifierGeneration(); + } + else { + return false; + } + } + + private JdbcOperationQueryMutation createTableInsert( + InsertSelectStatement translatedInsertStatement, + TableGroup updatingTableGroup, + String tableExpression, + String[] keyColumns, + boolean nullableTable, + Map> assignmentsByTable, + ExecutionContext executionContext) { + final TableReference updatingTableReference = updatingTableGroup.getTableReference( + updatingTableGroup.getNavigablePath(), + tableExpression, + true + ); + + final List assignments = assignmentsByTable.get( tableExpression ); + if ( nullableTable && ( assignments == null || assignments.isEmpty() ) ) { + // no assignments for this table - skip it + return null; + } + final NamedTableReference dmlTargetTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); + + final QuerySpec querySpec = new QuerySpec( true ); + final NamedTableReference temporaryTableReference = new NamedTableReference( + translatedInsertStatement.getTargetTable().getTableExpression(), + "hte_tmp" + ); + final TableGroupImpl temporaryTableGroup = new TableGroupImpl( + updatingTableGroup.getNavigablePath(), + null, + temporaryTableReference, + getEntityDescriptor() + ); + querySpec.getFromClause().addRoot( temporaryTableGroup ); + final InsertSelectStatement insertStatement = new InsertSelectStatement( dmlTargetTableReference ); + insertStatement.setSourceSelectStatement( querySpec ); + applyAssignments( assignments, insertStatement, temporaryTableReference, getEntityDescriptor() ); + if ( insertStatement.getTargetColumns() + .stream() + .noneMatch( c -> keyColumns[0].equals( c.getColumnExpression() ) ) ) { + final List primaryKeyTableColumns = + getPrimaryKeyTableColumns( getEntityDescriptor().getEntityPersister(), entityTable ); + getEntityDescriptor().getIdentifierMapping().forEachSelectable( 0, (selectionIndex, selectableMapping) -> { + insertStatement.addTargetColumnReferences( + new ColumnReference( + (String) null, + keyColumns[selectionIndex], + false, + null, + selectableMapping.getJdbcMapping() + ) + ); + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + new ColumnReference( + temporaryTableReference.getIdentificationVariable(), + primaryKeyTableColumns.get( selectionIndex ).getColumnName(), + false, + null, + selectableMapping.getJdbcMapping() + ) + ) + ); + } ); + } + + if ( entityTable.getSessionUidColumn() != null ) { + final TemporaryTableColumn sessionUidColumn = entityTable.getSessionUidColumn(); + querySpec.applyPredicate( new ComparisonPredicate( + new ColumnReference( + temporaryTableReference, + sessionUidColumn.getColumnName(), + false, + null, + sessionUidColumn.getJdbcMapping() + ), + ComparisonOperator.EQUAL, + sessionUidParameter + ) ); + } + final JdbcServices jdbcServices = getSessionFactory().getJdbcServices(); + return jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( getSessionFactory(), insertStatement ) + .translate( null, executionContext.getQueryOptions() ); + } + + private List getPrimaryKeyTableColumns(EntityPersister entityPersister, TemporaryTable entityTable) { + final boolean identityColumn = entityPersister.getGenerator().generatedOnExecution(); + final int startIndex = identityColumn ? 1 : 0; + final int endIndex = startIndex + entityPersister.getIdentifierMapping().getJdbcTypeCount(); + return entityTable.getColumns().subList( startIndex, endIndex ); + } + + private void applyAssignments(List assignments, InsertSelectStatement insertStatement, NamedTableReference temporaryTableReference, EntityPersister entityDescriptor) { + if ( assignments != null && !assignments.isEmpty() ) { + for ( Assignment assignment : assignments ) { + final Assignable assignable = assignment.getAssignable(); + insertStatement.addTargetColumnReferences( assignable.getColumnReferences() ); + final List columns = entityTable.findTemporaryTableColumns( + getEntityDescriptor().getEntityPersister(), + ((SqmPathInterpretation) assignable).getExpressionType() + ); + for ( TemporaryTableColumn temporaryTableColumn : columns ) { + insertStatement.getSourceSelectStatement().getFirstQuerySpec().getSelectClause().addSqlSelection( + new SqlSelectionImpl( + new ColumnReference( + temporaryTableReference.getIdentificationVariable(), + temporaryTableColumn.getColumnName(), + false, + null, + temporaryTableColumn.getJdbcMapping() + ) + ) + ); + } + } + } + } + + @Override + public JdbcParameterBindings createJdbcParameterBindings(DomainQueryExecutionContext context) { + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override + @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + if ( sessionUidParameter != null ) { + jdbcParameterBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + entityTable.getSessionUidColumn().getJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( context.getSession() ) ) + ) + ); + } + return jdbcParameterBindings; + } + + @Override + public boolean dependsOnParameterBindings() { + if ( temporaryTableInsert.jdbcOperation().dependsOnParameterBindings() ) { + return true; + } + if ( rootTableInserter.temporaryTableIdentitySelect != null + && rootTableInserter.temporaryTableIdentitySelect.dependsOnParameterBindings() ) { + return true; + } + if ( rootTableInserter.temporaryTableIdUpdate != null + && rootTableInserter.temporaryTableIdUpdate.dependsOnParameterBindings() ) { + return true; + } + if ( rootTableInserter.rootTableInsert.dependsOnParameterBindings() ) { + return true; + } + if ( rootTableInserter.temporaryTableIdentityUpdate != null + && rootTableInserter.temporaryTableIdentityUpdate.dependsOnParameterBindings() ) { + return true; + } + for ( JdbcOperationQueryMutation delete : nonRootTableInserts ) { + if ( delete.dependsOnParameterBindings() ) { + return true; + } + } + return false; + } + + @Override + public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + if ( !temporaryTableInsert.jdbcOperation().isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + if ( rootTableInserter.temporaryTableIdentitySelect != null + && rootTableInserter.temporaryTableIdentitySelect.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return true; + } + if ( rootTableInserter.temporaryTableIdUpdate != null + && rootTableInserter.temporaryTableIdUpdate.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return true; + } + if ( rootTableInserter.rootTableInsert.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return true; + } + if ( rootTableInserter.temporaryTableIdentityUpdate != null + && rootTableInserter.temporaryTableIdentityUpdate.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return true; + } + for ( JdbcOperationQueryMutation delete : nonRootTableInserts ) { + if ( !delete.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + } + return true; + } + + private TableReference resolveTableReference( + ColumnReference columnReference, + Map tableReferenceByAlias) { + if ( columnReference.getQualifier() == null ) { + // This happens only for the special row_number column + return null; + } + final TableReference tableReferenceByQualifier = tableReferenceByAlias.get( columnReference.getQualifier() ); + if ( tableReferenceByQualifier != null ) { + return tableReferenceByQualifier; + } + + throw new SemanticException( "Assignment referred to column of a joined association: " + columnReference ); + } + + private NamedTableReference resolveUnionTableReference(TableReference tableReference, String tableExpression) { + if ( tableReference instanceof UnionTableReference ) { + return new NamedTableReference( + tableExpression, + tableReference.getIdentificationVariable(), + tableReference.isOptional() + ); + } + else { + return (NamedTableReference) tableReference; + } + } + + public SqmInsertStatement getSqmInsertStatement() { + return (SqmInsertStatement) getSqmStatement(); + } + + @Override + public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { + if ( log.isTraceEnabled() ) { + log.tracef( + "Starting multi-table insert execution - %s", + getSqmStatement().getTarget().getModel().getName() + ); + } + + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + // NOTE: we could get rid of using a temporary table if the expressions in Values are "stable". + // But that is a non-trivial optimization that requires more effort + // as we need to split out individual inserts if we have a non-bulk capable optimizer + final boolean createdTable = ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + entityTable, + temporaryTableStrategy, + executionContext + ); + + try { + final int rows = ExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + temporaryTableInsert.jdbcOperation(), + jdbcParameterBindings, + executionContext + ); + + if ( rows != 0 ) { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ) + ) + ); + } + + final int insertedRows = insertRootTable( + rows, + createdTable, + sessionUidBindings, + executionContext + ); + + for ( JdbcOperationQueryMutation nonRootTableInsert : nonRootTableInserts ) { + insertTable( + nonRootTableInsert, + sessionUidBindings, + executionContext + ); + } + return insertedRows; + } + + return rows; + } + finally { + ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( + entityTable, + sessionUidAccess, + getAfterUseAction(), + executionContext + ); + } + } + + private int insertRootTable( + int rows, + boolean rowNumberStartsAtOne, + JdbcParameterBindings sessionUidBindings, + SqmJdbcExecutionContextAdapter executionContext) { + final EntityPersister entityPersister = getEntityDescriptor().getEntityPersister(); + final Generator generator = entityPersister.getGenerator(); + final EntityIdentifierMapping identifierMapping = entityPersister.getIdentifierMapping(); + + final SharedSessionContractImplementor session = executionContext.getSession(); + final JdbcServices jdbcServices = session.getFactory().getJdbcServices(); + + final Map entityTableToRootIdentity; + if ( rootTableInserter.temporaryTableIdentitySelect != null ) { + final List list = jdbcServices.getJdbcSelectExecutor().list( + rootTableInserter.temporaryTableIdentitySelect, + sessionUidBindings, + executionContext, + null, + null, + ListResultsConsumer.UniqueSemantic.NONE, + rows + ); + entityTableToRootIdentity = new LinkedHashMap<>( list.size() ); + for ( Object o : list ) { + entityTableToRootIdentity.put( o, null ); + } + } + else { + entityTableToRootIdentity = null; + + if ( rootTableInserter.temporaryTableIdUpdate != null ) { + final BeforeExecutionGenerator beforeExecutionGenerator = (BeforeExecutionGenerator) generator; + final IntStream rowNumberStream = !rowNumberStartsAtOne + ? IntStream.of( ExecuteWithTemporaryTableHelper.loadInsertedRowNumbers( + rootTableInserter.temporaryTableRowNumberSelectSql, entityTable, sessionUidAccess, rows, + executionContext ) ) + : IntStream.range( 1, rows + 1 ); + final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 3 ); + if ( sessionUidParameter != null ) { + updateBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( session ) ) + ) + ); + } + final List parameterBinders = rootTableInserter.temporaryTableIdUpdate.getParameterBinders(); + final JdbcParameter rootIdentity = (JdbcParameter) parameterBinders.get( 0 ); + final JdbcParameter rowNumber = (JdbcParameter) parameterBinders.get( 1 ); + final BasicEntityIdentifierMapping basicIdentifierMapping = (BasicEntityIdentifierMapping) identifierMapping; + rowNumberStream.forEach( rowNumberValue -> { + updateBindings.addBinding( + rowNumber, + new JdbcParameterBindingImpl( + rowNumber.getExpressionType().getSingleJdbcMapping(), + rowNumberValue + ) + ); + updateBindings.addBinding( + rootIdentity, + new JdbcParameterBindingImpl( + basicIdentifierMapping.getJdbcMapping(), + beforeExecutionGenerator.generate( session, null, null, INSERT ) + ) + ); + final int updateCount = jdbcServices.getJdbcMutationExecutor().execute( + rootTableInserter.temporaryTableIdUpdate, + updateBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + assert updateCount == 1; + } ); + } + } + + if ( rootTableInserter.rootTableInsertWithReturningSql != null ) { + final GeneratedValuesMutationDelegate insertDelegate = entityPersister.getEntityPersister().getInsertDelegate(); + final BasicEntityIdentifierMapping basicIdentifierMapping = (BasicEntityIdentifierMapping) identifierMapping; + // todo 7.0 : InsertGeneratedIdentifierDelegate will be removed once we're going to handle + // generated values within the jdbc insert operaetion itself + final InsertGeneratedIdentifierDelegate identifierDelegate = (InsertGeneratedIdentifierDelegate) insertDelegate; + final ValueBinder jdbcValueBinder = basicIdentifierMapping.getJdbcMapping().getJdbcValueBinder(); + for ( Map.Entry entry : entityTableToRootIdentity.entrySet() ) { + final GeneratedValues generatedValues = identifierDelegate.performInsertReturning( + rootTableInserter.rootTableInsertWithReturningSql, + session, + new Binder() { + @Override + public void bindValues(PreparedStatement ps) throws SQLException { + jdbcValueBinder.bind( ps, entry.getKey(), 1, session ); + if ( sessionUidParameter != null ) { + sessionUidParameter.getParameterBinder().bindParameterValue( + ps, + 2, + sessionUidBindings, + executionContext + ); + } + } + + @Override + public Object getEntity() { + return null; + } + } + ); + final Object rootIdentity = generatedValues.getGeneratedValue( identifierMapping ); + entry.setValue( rootIdentity ); + } + final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 2 ); + + final List parameterBinders = rootTableInserter.temporaryTableIdentityUpdate.getParameterBinders(); + final JdbcParameter rootIdentity = (JdbcParameter) parameterBinders.get( 0 ); + final JdbcParameter entityIdentity = (JdbcParameter) parameterBinders.get( 1 ); + for ( Map.Entry entry : entityTableToRootIdentity.entrySet() ) { + JdbcMapping jdbcMapping = basicIdentifierMapping.getJdbcMapping(); + updateBindings.addBinding( entityIdentity, new JdbcParameterBindingImpl( jdbcMapping, entry.getKey() ) ); + updateBindings.addBinding( rootIdentity, new JdbcParameterBindingImpl( jdbcMapping, entry.getValue() ) ); + jdbcServices.getJdbcMutationExecutor().execute( + rootTableInserter.temporaryTableIdentityUpdate, + updateBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } + + return entityTableToRootIdentity.size(); + } + else { + return jdbcServices.getJdbcMutationExecutor().execute( + rootTableInserter.rootTableInsert, + sessionUidBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } + } + + private void insertTable( + JdbcOperationQueryMutation nonRootTableInsert, + JdbcParameterBindings sessionUidBindings, + ExecutionContext executionContext) { + final SharedSessionContractImplementor session = executionContext.getSession(); + final JdbcServices jdbcServices = session.getFactory().getJdbcServices(); + jdbcServices.getJdbcMutationExecutor().execute( + nonRootTableInsert, + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } + + // For Hibernate Reactive + protected CacheableSqmInterpretation getTemporaryTableInsert() { + return temporaryTableInsert; + } + + // For Hibernate Reactive + protected RootTableInserter getRootTableInserter() { + return rootTableInserter; + } + + // For Hibernate Reactive + protected List getNonRootTableInserts() { + return nonRootTableInserts; + } + + // For Hibernate Reactive + protected TemporaryTable getEntityTable() { + return entityTable; + } + + // For Hibernate Reactive + protected TemporaryTableStrategy getTemporaryTableStrategy() { + return temporaryTableStrategy; + } + + // For Hibernate Reactive + protected Function getSessionUidAccess() { + return sessionUidAccess; + } + + // For Hibernate Reactive + protected AfterUseAction getAfterUseAction() { + return forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(); + } + + // For Hibernate Reactive + protected @Nullable JdbcParameter getSessionUidParameter() { + return sessionUidParameter; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedSoftDeleteHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedSoftDeleteHandler.java new file mode 100644 index 000000000000..5b29d20b3fe6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedSoftDeleteHandler.java @@ -0,0 +1,520 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.sqm.mutation.internal.temptable; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.TableDetails; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.AbstractMutationHandler; +import org.hibernate.query.sqm.mutation.internal.DeleteHandler; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.from.NamedTableReference; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.predicate.PredicateCollector; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.internal.JdbcParameterImpl; +import org.hibernate.sql.exec.spi.JdbcMutationExecutor; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.jboss.logging.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; + +import static java.util.Collections.singletonList; +import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.omittingLockingAndPaging; + +/** +* @author Steve Ebersole +*/ +public class TableBasedSoftDeleteHandler + extends AbstractMutationHandler + implements DeleteHandler { + private static final Logger log = Logger.getLogger( TableBasedSoftDeleteHandler.class ); + + private final TemporaryTable idTable; + private final TemporaryTableStrategy temporaryTableStrategy; + private final boolean forceDropAfterUse; + private final Function sessionUidAccess; + private final DomainParameterXref domainParameterXref; + private final Map, Map, List>> jdbcParamsXref; + private final Map, MappingModelExpressible> resolvedParameterMappingModelTypes; + private final @Nullable JdbcParameter sessionUidParameter; + + private final @Nullable CacheableSqmInterpretation idTableInsert; + private final JdbcOperationQueryMutation softDelete; + + public TableBasedSoftDeleteHandler( + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + TemporaryTable idTable, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, + Function sessionUidAccess, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmDelete, context.getSession().getSessionFactory() ); + this.idTable = idTable; + + this.temporaryTableStrategy = temporaryTableStrategy; + this.forceDropAfterUse = forceDropAfterUse; + + this.sessionUidAccess = sessionUidAccess; + + final TemporaryTableSessionUidColumn sessionUidColumn = idTable.getSessionUidColumn(); + if ( sessionUidColumn == null ) { + this.sessionUidParameter = null; + } + else { + this.sessionUidParameter = new JdbcParameterImpl( sessionUidColumn.getJdbcMapping() ); + } + + final MultiTableSqmMutationConverter converter = new MultiTableSqmMutationConverter( + getEntityDescriptor(), + sqmDelete, + sqmDelete.getTarget(), + domainParameterXref, + context.getQueryOptions(), + context.getSession().getLoadQueryInfluencers(), + context.getQueryParameterBindings(), + getSessionFactory().getSqlTranslationEngine() + ); + + + final String targetEntityName = sqmDelete.getTarget().getEntityName(); + final EntityPersister targetEntityDescriptor = + getSessionFactory().getMappingMetamodel() + .getEntityDescriptor( targetEntityName ); + + final EntityMappingType rootEntityDescriptor = targetEntityDescriptor.getRootEntityDescriptor(); + + // determine if we need to use a sub-query for matching ids - + // 1. if the target is not the root we will + // 2. if the supplied predicate (if any) refers to columns from a table + // other than the identifier table we will + final SqmJdbcExecutionContextAdapter executionContext = omittingLockingAndPaging( context ); + + final TableGroup deletingTableGroup = converter.getMutatingTableGroup(); + final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails(); + final NamedTableReference rootTableReference = (NamedTableReference) deletingTableGroup.resolveTableReference( + deletingTableGroup.getNavigablePath(), + softDeleteTable.getTableName() + ); + assert rootTableReference != null; + + // NOTE : `converter.visitWhereClause` already applies the soft-delete restriction + final Predicate specifiedRestriction = converter.visitWhereClause( sqmDelete.getWhereClause() ); + + final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); + targetEntityDescriptor.applyBaseRestrictions( + predicateCollector, + deletingTableGroup, + true, + executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), + false, + null, + converter + ); + + converter.pruneTableGroupJoins(); + final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( + rootTableReference.getIdentificationVariable() + ); + if ( predicateCollector.getPredicate() != null ) { + predicateCollector.getPredicate().accept( walker ); + } + + + this.domainParameterXref = domainParameterXref; + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, converter ); + this.resolvedParameterMappingModelTypes = converter.getSqmParameterMappingModelExpressibleResolutions(); + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + if ( sessionUidParameter != null ) { + jdbcParameterBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + idTable.getSessionUidColumn().getJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( context.getSession() ) ) + ) + ); + } + + final boolean needsSubQuery = !walker.isAllColumnReferencesFromIdentificationVariable() + || targetEntityDescriptor != rootEntityDescriptor; + if ( needsSubQuery ) { + if ( getSessionFactory().getJdbcServices().getDialect().supportsSubqueryOnMutatingTable() ) { + this.idTableInsert = null; + this.softDelete = createDeleteWithSubQuery( + rootEntityDescriptor, + deletingTableGroup, + rootTableReference, + predicateCollector, + jdbcParameterBindings, + converter, + executionContext + ); + } + else { + this.idTableInsert = ExecuteWithTemporaryTableHelper.createMatchingIdsIntoIdTableInsert( + converter, + predicateCollector.getPredicate(), + idTable, + sessionUidParameter, + jdbcParameterBindings, + executionContext + ); + this.softDelete = createDeleteUsingIdTable( + rootEntityDescriptor, + rootTableReference, + predicateCollector, + executionContext + ); + } + } + else { + this.idTableInsert = null; + this.softDelete = createDirectDelete( + rootEntityDescriptor, + rootTableReference, + predicateCollector, + jdbcParameterBindings, + executionContext + ); + } + + firstJdbcParameterBindingsConsumer.set( jdbcParameterBindings ); + } + + private JdbcOperationQueryMutation createDeleteUsingIdTable( + EntityMappingType rootEntityDescriptor, + NamedTableReference targetTableReference, + PredicateCollector predicateCollector, + SqmJdbcExecutionContextAdapter executionContext) { + final QuerySpec idTableIdentifierSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( + getIdTable(), + sessionUidParameter, + getEntityDescriptor(), + executionContext + ); + + final Assignment softDeleteAssignment = rootEntityDescriptor + .getSoftDeleteMapping() + .createSoftDeleteAssignment( targetTableReference ); + final Expression idExpression = createIdExpression( rootEntityDescriptor, targetTableReference ); + final UpdateStatement updateStatement = new UpdateStatement( + targetTableReference, + singletonList( softDeleteAssignment ), + new InSubQueryPredicate( idExpression, idTableIdentifierSubQuery, false ) + ); + + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + + final JdbcServices jdbcServices = factory.getJdbcServices(); + + return jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, updateStatement ) + .translate( JdbcParameterBindings.NO_BINDINGS, executionContext.getQueryOptions() ); + } + + private static Expression createIdExpression(EntityMappingType rootEntityDescriptor, NamedTableReference targetTableReference) { + final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails(); + final TableDetails.KeyDetails keyDetails = softDeleteTable.getKeyDetails(); + final List idExpressions = new ArrayList<>( keyDetails.getColumnCount() ); + keyDetails.forEachKeyColumn( (position, column) -> idExpressions.add( + new ColumnReference( targetTableReference, column ) + ) ); + final Expression idExpression = idExpressions.size() == 1 + ? idExpressions.get( 0 ) + : new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() ); + return idExpression; + } + + private JdbcOperationQueryMutation createDeleteWithSubQuery( + EntityMappingType rootEntityDescriptor, + TableGroup deletingTableGroup, + NamedTableReference rootTableReference, + PredicateCollector predicateCollector, + JdbcParameterBindings jdbcParameterBindings, + MultiTableSqmMutationConverter converter, + SqmJdbcExecutionContextAdapter executionContext) { + final QuerySpec matchingIdSubQuery = new QuerySpec( false, 1 ); + matchingIdSubQuery.getFromClause().addRoot( deletingTableGroup ); + + final TableDetails identifierTableDetails = rootEntityDescriptor.getIdentifierTableDetails(); + final TableDetails.KeyDetails keyDetails = identifierTableDetails.getKeyDetails(); + + final NamedTableReference targetTable = new NamedTableReference( + identifierTableDetails.getTableName(), + DeleteStatement.DEFAULT_ALIAS, + false + ); + + final List idExpressions = new ArrayList<>( keyDetails.getColumnCount() ); + keyDetails.forEachKeyColumn( (position, column) -> { + final Expression columnReference = converter.getSqlExpressionResolver().resolveSqlExpression( + rootTableReference, + column + ); + matchingIdSubQuery.getSelectClause().addSqlSelection( + new SqlSelectionImpl( position, columnReference ) + ); + idExpressions.add( new ColumnReference( targetTable, column ) ); + } ); + + matchingIdSubQuery.applyPredicate( predicateCollector.getPredicate() ); + final Expression idExpression = idExpressions.size() == 1 + ? idExpressions.get( 0 ) + : new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() ); + + final Assignment softDeleteAssignment = rootEntityDescriptor + .getSoftDeleteMapping() + .createSoftDeleteAssignment( targetTable ); + + final UpdateStatement updateStatement = new UpdateStatement( + targetTable, + singletonList( softDeleteAssignment ), + new InSubQueryPredicate( idExpression, matchingIdSubQuery, false ) + ); + + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + + final JdbcServices jdbcServices = factory.getJdbcServices(); + + return jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, updateStatement ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + } + + private JdbcOperationQueryMutation createDirectDelete( + EntityMappingType rootEntityDescriptor, + NamedTableReference rootTableReference, + PredicateCollector predicateCollector, + JdbcParameterBindings jdbcParameterBindings, + SqmJdbcExecutionContextAdapter executionContext) { + final Assignment softDeleteAssignment = rootEntityDescriptor + .getSoftDeleteMapping() + .createSoftDeleteAssignment( rootTableReference ); + + final UpdateStatement updateStatement = new UpdateStatement( + rootTableReference, + singletonList( softDeleteAssignment ), + predicateCollector.getPredicate() + ); + + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + + final JdbcServices jdbcServices = factory.getJdbcServices(); + + return jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, updateStatement ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + } + + @Override + public JdbcParameterBindings createJdbcParameterBindings(DomainQueryExecutionContext context) { + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override + @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + if ( sessionUidParameter != null ) { + jdbcParameterBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + idTable.getSessionUidColumn().getJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( context.getSession() ) ) + ) + ); + } + return jdbcParameterBindings; + } + + @Override + public boolean dependsOnParameterBindings() { + if ( idTableInsert != null && idTableInsert.jdbcOperation().dependsOnParameterBindings() ) { + return true; + } + if ( softDelete.dependsOnParameterBindings() ) { + return true; + } + return false; + } + + @Override + public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + if ( idTableInsert != null + && !idTableInsert.jdbcOperation().isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + if ( !softDelete.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + return true; + } + + @Override + public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { + if ( log.isTraceEnabled() ) { + log.tracef( + "Starting multi-table delete execution - %s", + getSqmStatement().getTarget().getModel().getName() + ); + } + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final JdbcMutationExecutor jdbcMutationExecutor = factory.getJdbcServices().getJdbcMutationExecutor(); + if ( idTableInsert != null ) { + ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + idTable, + temporaryTableStrategy, + executionContext + ); + + try { + final int rows = ExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + idTableInsert.jdbcOperation(), + jdbcParameterBindings, + executionContext + ); + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ) + ) + ); + } + jdbcMutationExecutor.execute( + softDelete, + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + return rows; + } + finally { + ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( + idTable, + sessionUidAccess, + getAfterUseAction(), + executionContext + ); + } + } + else { + return jdbcMutationExecutor.execute( + softDelete, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + } + + // For Hibernate Reactive + public @Nullable CacheableSqmInterpretation getIdTableInsert() { + return idTableInsert; + } + + // For Hibernate Reactive + protected JdbcOperationQueryMutation getSoftDelete() { + return softDelete; + } + + // For Hibernate Reactive + protected AfterUseAction getAfterUseAction() { + return forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(); + } + + // For Hibernate Reactive + protected TemporaryTable getIdTable() { + return idTable; + } + + // For Hibernate Reactive + protected TemporaryTableStrategy getTemporaryTableStrategy() { + return temporaryTableStrategy; + } + + // For Hibernate Reactive + protected Function getSessionUidAccess() { + return sessionUidAccess; + } + + // For Hibernate Reactive + protected @Nullable JdbcParameter getSessionUidParameter() { + return sessionUidParameter; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedUpdateHandler.java index 533dd49954dd..db108c4c61ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedUpdateHandler.java @@ -4,33 +4,80 @@ */ package org.hibernate.query.sqm.mutation.internal.temptable; +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn; import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.MappingMetamodel; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.SelectableConsumer; +import org.hibernate.metamodel.mapping.SoftDeleteMapping; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.SemanticException; +import org.hibernate.query.results.internal.TableGroupImpl; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.ComparisonOperator; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.AbstractMutationHandler; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; +import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; import org.hibernate.query.sqm.mutation.internal.UpdateHandler; -import org.hibernate.query.sqm.mutation.spi.AbstractMutationHandler; +import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; +import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.expression.QueryLiteral; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +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.from.TableReferenceJoin; +import org.hibernate.sql.ast.tree.from.UnionTableReference; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; +import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.predicate.PredicateCollector; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterImpl; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.jboss.logging.Logger; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; + +import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize; /** * @author Steve Ebersole @@ -40,15 +87,17 @@ public class TableBasedUpdateHandler implements UpdateHandler { private static final Logger log = Logger.getLogger( TableBasedUpdateHandler.class ); - public interface ExecutionDelegate { - int execute(ExecutionContext executionContext); - } - private final TemporaryTable idTable; private final TemporaryTableStrategy temporaryTableStrategy; private final boolean forceDropAfterUse; private final Function sessionUidAccess; private final DomainParameterXref domainParameterXref; + private final Map, Map, List>> jdbcParamsXref; + private final Map, MappingModelExpressible> resolvedParameterMappingModelTypes; + private final @Nullable JdbcParameter sessionUidParameter; + + private final CacheableSqmInterpretation matchingIdsIntoIdTableInsert; + private final List tableUpdaters; public TableBasedUpdateHandler( SqmUpdateStatement sqmUpdate, @@ -57,43 +106,25 @@ public TableBasedUpdateHandler( TemporaryTableStrategy temporaryTableStrategy, boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmUpdate, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmUpdate, context.getSession().getSessionFactory() ); this.idTable = idTable; this.temporaryTableStrategy = temporaryTableStrategy; this.forceDropAfterUse = forceDropAfterUse; this.sessionUidAccess = sessionUidAccess; - this.domainParameterXref = domainParameterXref; - } - - protected SqmUpdateStatement getSqmUpdate() { - return getSqmDeleteOrUpdateStatement(); - } - - @Override - public SqmUpdateStatement getSqmDeleteOrUpdateStatement() { - return (SqmUpdateStatement) super.getSqmDeleteOrUpdateStatement(); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - if ( log.isTraceEnabled() ) { - log.tracef( - "Starting multi-table update execution - %s", - getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() - ); + final TemporaryTableSessionUidColumn sessionUidColumn = idTable.getSessionUidColumn(); + if ( sessionUidColumn == null ) { + this.sessionUidParameter = null; + } + else { + this.sessionUidParameter = new JdbcParameterImpl( sessionUidColumn.getJdbcMapping() ); } - - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); - return resolveDelegate( executionContext ).execute( executionContextAdapter ); - } - - protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { final SessionFactoryImplementor sessionFactory = getSessionFactory(); final MappingMetamodel domainModel = sessionFactory.getMappingMetamodel(); final EntityPersister entityDescriptor = - domainModel.getEntityDescriptor( getSqmDeleteOrUpdateStatement().getTarget().getEntityName() ); + domainModel.getEntityDescriptor( sqmUpdate.getTarget().getEntityName() ); final String rootEntityName = entityDescriptor.getRootEntityName(); final EntityPersister rootEntityDescriptor = domainModel.getEntityDescriptor( rootEntityName ); @@ -102,12 +133,12 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio final MultiTableSqmMutationConverter converterDelegate = new MultiTableSqmMutationConverter( entityDescriptor, - getSqmDeleteOrUpdateStatement(), - getSqmDeleteOrUpdateStatement().getTarget(), + sqmUpdate, + sqmUpdate.getTarget(), domainParameterXref, - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), + context.getQueryOptions(), + context.getSession().getLoadQueryInfluencers(), + context.getQueryParameterBindings(), sessionFactory.getSqlTranslationEngine() ); @@ -123,22 +154,22 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio // visit the set-clause using our special converter, collecting // information about the assignments - final List assignments = converterDelegate.visitSetClause( getSqmDeleteOrUpdateStatement().getSetClause() ); - converterDelegate.addVersionedAssignment( assignments::add, getSqmDeleteOrUpdateStatement() ); + final List assignments = converterDelegate.visitSetClause( sqmUpdate.getSetClause() ); + converterDelegate.addVersionedAssignment( assignments::add, sqmUpdate ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // visit the where-clause using our special converter, collecting information // about the restrictions final PredicateCollector predicateCollector = new PredicateCollector( - converterDelegate.visitWhereClause( getSqmUpdate().getWhereClause() ) + converterDelegate.visitWhereClause( sqmUpdate.getWhereClause() ) ); entityDescriptor.applyBaseRestrictions( predicateCollector::applyPredicate, updatingTableGroup, true, - executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), + context.getSession().getLoadQueryInfluencers().getEnabledFilters(), false, null, converterDelegate @@ -156,46 +187,428 @@ protected ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executio collectTableReference( updatingTableGroup.getTableReferenceJoins().get( i ), tableReferenceByAlias::put ); } - return buildExecutionDelegate( + final SoftDeleteMapping softDeleteMapping = entityDescriptor.getSoftDeleteMapping(); + final Predicate suppliedPredicate; + if ( softDeleteMapping != null ) { + final NamedTableReference rootTableReference = (NamedTableReference) updatingTableGroup.resolveTableReference( + updatingTableGroup.getNavigablePath(), + entityDescriptor.getIdentifierTableDetails().getTableName() + ); + suppliedPredicate = Predicate.combinePredicates( + predicateCollector.getPredicate(), + softDeleteMapping.createNonDeletedRestriction( rootTableReference ) + ); + } + else { + suppliedPredicate = predicateCollector.getPredicate(); + } + + Map> assignmentsByTable = mapOfSize( updatingTableGroup.getTableReferenceJoins().size() + 1 ); + + + this.domainParameterXref = domainParameterXref; + this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, converterDelegate ); + this.resolvedParameterMappingModelTypes = converterDelegate.getSqmParameterMappingModelExpressibleResolutions(); + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + if ( sessionUidParameter != null ) { + jdbcParameterBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + idTable.getSessionUidColumn().getJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( context.getSession() ) ) + ) + ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // segment the assignments by table-reference + + for ( int i = 0; i < assignments.size(); i++ ) { + final Assignment assignment = assignments.get( i ); + final List assignmentColumnRefs = assignment.getAssignable().getColumnReferences(); + + TableReference assignmentTableReference = null; + + for ( int c = 0; c < assignmentColumnRefs.size(); c++ ) { + final ColumnReference columnReference = assignmentColumnRefs.get( c ); + final TableReference tableReference = resolveTableReference( + columnReference, + tableReferenceByAlias + ); + + if ( assignmentTableReference != null && assignmentTableReference != tableReference ) { + throw new SemanticException( "Assignment referred to columns from multiple tables: " + assignment.getAssignable() ); + } + + assignmentTableReference = tableReference; + } + + List assignmentsForTable = assignmentsByTable.get( assignmentTableReference ); + if ( assignmentsForTable == null ) { + assignmentsForTable = new ArrayList<>(); + assignmentsByTable.put( assignmentTableReference, assignmentsForTable ); + } + assignmentsForTable.add( assignment ); + } + + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + this.matchingIdsIntoIdTableInsert = ExecuteWithTemporaryTableHelper.createMatchingIdsIntoIdTableInsert( converterDelegate, + suppliedPredicate, idTable, - temporaryTableStrategy, - forceDropAfterUse, - sessionUidAccess, - domainParameterXref, - updatingTableGroup, - tableReferenceByAlias, - assignments, - predicateCollector.getPredicate(), + sessionUidParameter, + jdbcParameterBindings, + executionContext + ); + + final QuerySpec idTableSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( + idTable, + sessionUidParameter, + entityDescriptor, executionContext ); + final ArrayList tableUpdaters = new ArrayList<>(); + entityDescriptor.visitConstraintOrderedTables( + (tableExpression, tableKeyColumnVisitationSupplier) -> tableUpdaters.add( + createTableUpdater( + updatingTableGroup, + tableExpression, + tableKeyColumnVisitationSupplier, + assignmentsByTable, + idTableSubQuery, + jdbcParameterBindings, + executionContext + ) + ) + ); + this.tableUpdaters = tableUpdaters; + firstJdbcParameterBindingsConsumer.set( jdbcParameterBindings ); } - protected UpdateExecutionDelegate buildExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable idTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, + @Override + public JdbcParameterBindings createJdbcParameterBindings(DomainQueryExecutionContext context) { + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + context.getQueryParameterBindings(), + domainParameterXref, + jdbcParamsXref, + new SqmParameterMappingModelResolutionAccess() { + @Override + @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) resolvedParameterMappingModelTypes.get( parameter ); + } + }, + context.getSession() + ); + if ( sessionUidParameter != null ) { + jdbcParameterBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + idTable.getSessionUidColumn().getJdbcMapping(), + UUID.fromString( sessionUidAccess.apply( context.getSession() ) ) + ) + ); + } + return jdbcParameterBindings; + } + + @Override + public boolean dependsOnParameterBindings() { + if ( matchingIdsIntoIdTableInsert.jdbcOperation().dependsOnParameterBindings() ) { + return true; + } + for ( TableUpdater updater : tableUpdaters ) { + if ( updater.jdbcUpdate.dependsOnParameterBindings() ) { + return true; + } + if ( updater.jdbcInsert != null && updater.jdbcInsert.dependsOnParameterBindings() ) { + return true; + } + } + return false; + } + + @Override + public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions) { + if ( !matchingIdsIntoIdTableInsert.jdbcOperation().isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + for ( TableUpdater updater : tableUpdaters ) { + if ( !updater.jdbcUpdate.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + if ( updater.jdbcInsert != null + && !updater.jdbcInsert.isCompatibleWith( jdbcParameterBindings, queryOptions ) ) { + return false; + } + } + return true; + } + + protected TableReference resolveTableReference( + ColumnReference columnReference, + Map tableReferenceByAlias) { + final TableReference tableReferenceByQualifier = tableReferenceByAlias.get( columnReference.getQualifier() ); + if ( tableReferenceByQualifier != null ) { + return tableReferenceByQualifier; + } + + throw new SemanticException( "Assignment referred to column of a joined association: " + columnReference ); + } + + protected NamedTableReference resolveUnionTableReference( + TableReference tableReference, + String tableExpression) { + if ( tableReference instanceof UnionTableReference ) { + return new NamedTableReference( + tableExpression, + tableReference.getIdentificationVariable(), + tableReference.isOptional() + ); + } + return (NamedTableReference) tableReference; + } + + private TableUpdater createTableUpdater( TableGroup updatingTableGroup, - Map tableReferenceByAlias, + String tableExpression, + Supplier> tableKeyColumnVisitationSupplier, + Map> assignmentsByTable, + QuerySpec idTableSubQuery, + JdbcParameterBindings firstJdbcParameterBindings, + ExecutionContext executionContext) { + + // update `updatingTableReference` + // set ... + // where `keyExpression` in ( `idTableSubQuery` ) + + final TableReference updatingTableReference = updatingTableGroup.getTableReference( + updatingTableGroup.getNavigablePath(), + tableExpression, + true + ); + + final List assignments = assignmentsByTable.get( updatingTableReference ); + if ( assignments == null || assignments.isEmpty() ) { + // no assignments for this table - skip it + return null; + } + + final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); + final JdbcServices jdbcServices = executionContext.getSession().getFactory().getJdbcServices(); + final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcServices.getJdbcEnvironment().getSqlAstTranslatorFactory(); + + final Expression keyExpression = + resolveMutatingTableKeyExpression( tableExpression, tableKeyColumnVisitationSupplier ); + return new TableUpdater( + createTableUpdate( + idTableSubQuery, + executionContext, + assignments, + dmlTableReference, + sqlAstTranslatorFactory, + firstJdbcParameterBindings, + keyExpression + ), + isTableOptional( tableExpression ) ? createTableInsert( + tableExpression, + dmlTableReference, + keyExpression, + tableKeyColumnVisitationSupplier, + idTableSubQuery, + assignments, + sqlAstTranslatorFactory, + firstJdbcParameterBindings, + executionContext + ) : null + ); + } + + protected boolean isTableOptional(String tableExpression) { + final EntityPersister entityPersister = getEntityDescriptor().getEntityPersister(); + for ( int i = 0; i < entityPersister.getTableSpan(); i++ ) { + if ( tableExpression.equals( entityPersister.getTableName( i ) ) + && entityPersister.isNullableTable( i ) ) { + return true; + } + } + return false; + } + + private JdbcOperationQueryMutation createTableInsert( + String targetTableExpression, + NamedTableReference targetTableReference, + Expression targetTableKeyExpression, + Supplier> tableKeyColumnVisitationSupplier, + QuerySpec idTableSubQuery, List assignments, - Predicate suppliedPredicate, - DomainQueryExecutionContext executionContext) { - return new UpdateExecutionDelegate( - sqmConverter, - idTable, - temporaryTableStrategy, - forceDropAfterUse, - sessionUidAccess, - domainParameterXref, - updatingTableGroup, - tableReferenceByAlias, + SqlAstTranslatorFactory sqlAstTranslatorFactory, + JdbcParameterBindings firstJdbcParameterBindings, + ExecutionContext executionContext) { + + final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); + // Execute a query in the form - + // + // insert into (...) + // select ... + // from temptable_ + // where not exists ( + // select 1 + // from dml_ + // where dml_. = temptable_. + // ) + + // Create a new QuerySpec for the "insert source" select query. This + // is mostly a copy of the incoming `idTableSubQuery` along with the + // NOT-EXISTS predicate + final QuerySpec insertSourceSelectQuerySpec = makeInsertSourceSelectQuerySpec( idTableSubQuery ); + + // create the `select 1 ...` sub-query and apply the not-exists predicate + final QuerySpec existsSubQuerySpec = createExistsSubQuerySpec( targetTableExpression, tableKeyColumnVisitationSupplier, idTableSubQuery, sessionFactory ); + insertSourceSelectQuerySpec.applyPredicate( + new ExistsPredicate( + existsSubQuerySpec, + true, + sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Boolean.class ) + ) + ); + + // Collect the target column references from the key expressions + final List targetColumnReferences = new ArrayList<>(); + if ( targetTableKeyExpression instanceof SqlTuple ) { + //noinspection unchecked + targetColumnReferences.addAll( (Collection) ( (SqlTuple) targetTableKeyExpression ).getExpressions() ); + } + else { + targetColumnReferences.add( (ColumnReference) targetTableKeyExpression ); + } + + // And transform assignments to target column references and selections + for ( Assignment assignment : assignments ) { + targetColumnReferences.addAll( assignment.getAssignable().getColumnReferences() ); + insertSourceSelectQuerySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( assignment.getAssignedValue() ) + ); + } + + final InsertSelectStatement insertSqlAst = new InsertSelectStatement( targetTableReference ); + insertSqlAst.addTargetColumnReferences( targetColumnReferences.toArray( new ColumnReference[0] ) ); + insertSqlAst.setSourceSelectStatement( insertSourceSelectQuerySpec ); + + return sqlAstTranslatorFactory + .buildMutationTranslator( sessionFactory, insertSqlAst ) + .translate( firstJdbcParameterBindings, executionContext.getQueryOptions() ); + } + + protected QuerySpec createExistsSubQuerySpec( + String targetTableExpression, + Supplier> tableKeyColumnVisitationSupplier, + QuerySpec idTableSubQuery, + SessionFactoryImplementor sessionFactory) { + final NamedTableReference existsTableReference = new NamedTableReference( + targetTableExpression, + "dml_" + ); + + // Prepare a not exists sub-query to avoid violating constraints + final QuerySpec existsSubQuerySpec = new QuerySpec( false ); + existsSubQuerySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + new QueryLiteral<>( + 1, + sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Integer.class ) + ) + ) + ); + existsSubQuerySpec.getFromClause().addRoot( new TableGroupImpl( + null, + null, + existsTableReference, + getEntityDescriptor() + ) ); + + final TableKeyExpressionCollector existsKeyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() ); + tableKeyColumnVisitationSupplier.get().accept( (columnIndex, selection) -> { + assert selection.getContainingTableExpression().equals( targetTableExpression ); + existsKeyColumnCollector.apply( new ColumnReference( existsTableReference, selection ) ); + } ); + existsSubQuerySpec.applyPredicate( + new ComparisonPredicate( + existsKeyColumnCollector.buildKeyExpression(), + ComparisonOperator.EQUAL, + asExpression( idTableSubQuery.getSelectClause()) + ) + ); + return existsSubQuerySpec; + } + + protected static QuerySpec makeInsertSourceSelectQuerySpec(QuerySpec idTableSubQuery) { + final QuerySpec idTableQuerySpec = new QuerySpec( true ); + for ( TableGroup root : idTableSubQuery.getFromClause().getRoots() ) { + idTableQuerySpec.getFromClause().addRoot( root ); + } + for ( SqlSelection sqlSelection : idTableSubQuery.getSelectClause().getSqlSelections() ) { + idTableQuerySpec.getSelectClause().addSqlSelection( sqlSelection ); + } + idTableQuerySpec.applyPredicate( idTableSubQuery.getWhereClauseRestrictions() ); + return idTableQuerySpec; + } + + private JdbcOperationQueryMutation createTableUpdate( + QuerySpec idTableSubQuery, + ExecutionContext executionContext, + List assignments, + NamedTableReference dmlTableReference, + SqlAstTranslatorFactory sqlAstTranslatorFactory, + JdbcParameterBindings firstJdbcParameterBindings, + Expression keyExpression) { + final UpdateStatement sqlAst = new UpdateStatement( + dmlTableReference, assignments, - suppliedPredicate, - executionContext + new InSubQueryPredicate( keyExpression, idTableSubQuery, false ) + ); + + return sqlAstTranslatorFactory + .buildMutationTranslator( executionContext.getSession().getFactory(), sqlAst ) + .translate( firstJdbcParameterBindings, executionContext.getQueryOptions() ); + } + + protected Expression resolveMutatingTableKeyExpression(String tableExpression, Supplier> tableKeyColumnVisitationSupplier) { + final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() ); + + tableKeyColumnVisitationSupplier.get().accept( + (columnIndex, selection) -> { + assert selection.getContainingTableExpression().equals( tableExpression ); + keyColumnCollector.apply( new ColumnReference( (String) null, selection ) ); + } ); + + return keyColumnCollector.buildKeyExpression(); + } + + protected Expression asExpression(SelectClause selectClause) { + final List sqlSelections = selectClause.getSqlSelections(); + if ( sqlSelections.size() == 1 ) { + return sqlSelections.get( 0 ).getExpression(); + } + final List expressions = new ArrayList<>( sqlSelections.size() ); + for ( SqlSelection sqlSelection : sqlSelections ) { + expressions.add( sqlSelection.getExpression() ); + } + return new SqlTuple( expressions, null ); } protected void collectTableReference( @@ -209,4 +622,123 @@ protected void collectTableReference( BiConsumer consumer) { collectTableReference( tableReferenceJoin.getJoinedTableReference(), consumer ); } + + @Override + public int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { + if ( log.isTraceEnabled() ) { + log.tracef( + "Starting multi-table update execution - %s", + getSqmStatement().getTarget().getModel().getName() + ); + } + + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + idTable, + temporaryTableStrategy, + executionContext + ); + + try { + final int rows = ExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + matchingIdsIntoIdTableInsert.jdbcOperation(), + jdbcParameterBindings, + executionContext + ); + for ( TableUpdater tableUpdater : tableUpdaters ) { + updateTable( + tableUpdater, + rows, + jdbcParameterBindings, + executionContext + ); + } + return rows; + } + finally { + ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( + idTable, + sessionUidAccess, + getAfterUseAction(), + executionContext + ); + } + } + + private void updateTable( + TableUpdater tableUpdater, + int expectedUpdateCount, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + if ( tableUpdater == null ) { + // no assignments for this table - skip it + return; + } + final int updateCount = executeMutation( tableUpdater.jdbcUpdate, jdbcParameterBindings, executionContext ); + + // We are done when the update count matches + if ( updateCount == expectedUpdateCount ) { + return; + } + + // If the table is optional, execute an insert + if ( tableUpdater.jdbcInsert != null ) { + final int insertCount = executeMutation( tableUpdater.jdbcInsert, jdbcParameterBindings, executionContext ); + assert insertCount + updateCount == expectedUpdateCount; + } + } + + private int executeMutation(JdbcOperationQueryMutation jdbcUpdate, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + return executionContext.getSession().getFactory().getJdbcServices().getJdbcMutationExecutor().execute( + jdbcUpdate, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } + + protected record TableUpdater( + JdbcOperationQueryMutation jdbcUpdate, + @Nullable JdbcOperationQueryMutation jdbcInsert) { + } + + // For Hibernate reactive + protected List getTableUpdaters() { + return tableUpdaters; + } + + // For Hibernate reactive + protected AfterUseAction getAfterUseAction() { + return forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(); + } + + // For Hibernate reactive + protected TemporaryTable getIdTable() { + return idTable; + } + + // For Hibernate reactive + protected TemporaryTableStrategy getTemporaryTableStrategy() { + return temporaryTableStrategy; + } + + // For Hibernate reactive + protected CacheableSqmInterpretation getMatchingIdsIntoIdTableInsert() { + return matchingIdsIntoIdTableInsert; + } + + // For Hibernate reactive + protected Function getSessionUidAccess() { + return sessionUidAccess; + } + + // For Hibernate reactive + protected @Nullable JdbcParameter getSessionUidParameter() { + return sessionUidParameter; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java deleted file mode 100644 index 2c8212295278..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/UpdateExecutionDelegate.java +++ /dev/null @@ -1,532 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.query.sqm.mutation.internal.temptable; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableStrategy; -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.MappingModelExpressible; -import org.hibernate.metamodel.mapping.ModelPartContainer; -import org.hibernate.metamodel.mapping.SelectableConsumer; -import org.hibernate.metamodel.mapping.SoftDeleteMapping; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.SemanticException; -import org.hibernate.query.results.internal.TableGroupImpl; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.ComparisonOperator; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.spi.SqlSelection; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.QueryLiteral; -import org.hibernate.sql.ast.tree.expression.SqlTuple; -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.from.UnionTableReference; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; -import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectClause; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcMutationExecutor; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.internal.SqlSelectionImpl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize; - -/** - * @author Steve Ebersole - */ -public class UpdateExecutionDelegate implements TableBasedUpdateHandler.ExecutionDelegate { - private final MultiTableSqmMutationConverter sqmConverter; - private final TemporaryTable idTable; - private final TemporaryTableStrategy temporaryTableStrategy; - private final boolean forceDropAfterUse; - private final Function sessionUidAccess; - private final TableGroup updatingTableGroup; - private final Predicate suppliedPredicate; - - private final EntityMappingType entityDescriptor; - - private final JdbcParameterBindings jdbcParameterBindings; - - private final Map> assignmentsByTable; - private final SessionFactoryImplementor sessionFactory; - - public UpdateExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable idTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup updatingTableGroup, - Map tableReferenceByAlias, - List assignments, - Predicate suppliedPredicate, - DomainQueryExecutionContext executionContext) { - this.sqmConverter = sqmConverter; - this.idTable = idTable; - this.temporaryTableStrategy = temporaryTableStrategy; - this.forceDropAfterUse = forceDropAfterUse; - this.sessionUidAccess = sessionUidAccess; - this.updatingTableGroup = updatingTableGroup; - this.sessionFactory = executionContext.getSession().getFactory(); - - final ModelPartContainer updatingModelPart = updatingTableGroup.getModelPart(); - assert updatingModelPart instanceof EntityMappingType; - this.entityDescriptor = (EntityMappingType) updatingModelPart; - - final SoftDeleteMapping softDeleteMapping = entityDescriptor.getSoftDeleteMapping(); - if ( softDeleteMapping != null ) { - final NamedTableReference rootTableReference = (NamedTableReference) updatingTableGroup.resolveTableReference( - updatingTableGroup.getNavigablePath(), - entityDescriptor.getIdentifierTableDetails().getTableName() - ); - this.suppliedPredicate = Predicate.combinePredicates( - suppliedPredicate, - softDeleteMapping.createNonDeletedRestriction( rootTableReference ) - ); - } - else { - this.suppliedPredicate = suppliedPredicate; - } - - - - this.assignmentsByTable = mapOfSize( updatingTableGroup.getTableReferenceJoins().size() + 1 ); - - jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - SqmUtil.generateJdbcParamsXref( domainParameterXref, sqmConverter ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get(parameter); - } - }, - executionContext.getSession() - ); - - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // segment the assignments by table-reference - - for ( int i = 0; i < assignments.size(); i++ ) { - final Assignment assignment = assignments.get( i ); - final List assignmentColumnRefs = assignment.getAssignable().getColumnReferences(); - - TableReference assignmentTableReference = null; - - for ( int c = 0; c < assignmentColumnRefs.size(); c++ ) { - final ColumnReference columnReference = assignmentColumnRefs.get( c ); - final TableReference tableReference = resolveTableReference( - columnReference, - tableReferenceByAlias - ); - - if ( assignmentTableReference != null && assignmentTableReference != tableReference ) { - throw new SemanticException( "Assignment referred to columns from multiple tables: " + assignment.getAssignable() ); - } - - assignmentTableReference = tableReference; - } - - List assignmentsForTable = assignmentsByTable.get( assignmentTableReference ); - if ( assignmentsForTable == null ) { - assignmentsForTable = new ArrayList<>(); - assignmentsByTable.put( assignmentTableReference, assignmentsForTable ); - } - assignmentsForTable.add( assignment ); - } - } - - @Override - public int execute(ExecutionContext executionContext) { - ExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( - idTable, - temporaryTableStrategy, - executionContext - ); - - try { - final int rows = ExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable( - sqmConverter, - suppliedPredicate, - idTable, - sessionUidAccess, - jdbcParameterBindings, - executionContext - ); - - final QuerySpec idTableSubQuery = ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( - idTable, - sessionUidAccess, - entityDescriptor, - executionContext - ); - - entityDescriptor.visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> updateTable( - tableExpression, - tableKeyColumnVisitationSupplier, - rows, - idTableSubQuery, - executionContext - ) - ); - - return rows; - } - finally { - ExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( - idTable, - sessionUidAccess, - getAfterUseAction(), - executionContext - ); - } - } - - protected TableReference resolveTableReference( - ColumnReference columnReference, - Map tableReferenceByAlias) { - final TableReference tableReferenceByQualifier = tableReferenceByAlias.get( columnReference.getQualifier() ); - if ( tableReferenceByQualifier != null ) { - return tableReferenceByQualifier; - } - - throw new SemanticException( "Assignment referred to column of a joined association: " + columnReference ); - } - - protected NamedTableReference resolveUnionTableReference( - TableReference tableReference, - String tableExpression) { - if ( tableReference instanceof UnionTableReference ) { - return new NamedTableReference( - tableExpression, - tableReference.getIdentificationVariable(), - tableReference.isOptional() - ); - } - return (NamedTableReference) tableReference; - } - - private void updateTable( - String tableExpression, - Supplier> tableKeyColumnVisitationSupplier, - int expectedUpdateCount, - QuerySpec idTableSubQuery, - ExecutionContext executionContext) { - - // update `updatingTableReference` - // set ... - // where `keyExpression` in ( `idTableSubQuery` ) - - final TableReference updatingTableReference = updatingTableGroup.getTableReference( - updatingTableGroup.getNavigablePath(), - tableExpression, - true - ); - - final List assignments = assignmentsByTable.get( updatingTableReference ); - if ( assignments == null || assignments.isEmpty() ) { - // no assignments for this table - skip it - return; - } - - final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcServices.getJdbcEnvironment().getSqlAstTranslatorFactory(); - final JdbcMutationExecutor jdbcMutationExecutor = jdbcServices.getJdbcMutationExecutor(); - - final Expression keyExpression = resolveMutatingTableKeyExpression( tableExpression, tableKeyColumnVisitationSupplier ); - - final int updateCount = executeUpdate( idTableSubQuery, executionContext, assignments, dmlTableReference, sqlAstTranslatorFactory, jdbcMutationExecutor, keyExpression ); - - // We are done when the update count matches - if ( updateCount == expectedUpdateCount ) { - return; - } - - // If the table is optional, execute an insert - if ( isTableOptional( tableExpression ) ) { - final int insertCount = executeInsert( - tableExpression, - dmlTableReference, - keyExpression, - tableKeyColumnVisitationSupplier, - idTableSubQuery, - assignments, - sqlAstTranslatorFactory, - jdbcMutationExecutor, - executionContext - ); - assert insertCount + updateCount == expectedUpdateCount; - } - } - - protected boolean isTableOptional(String tableExpression) { - final EntityPersister entityPersister = entityDescriptor.getEntityPersister(); - for ( int i = 0; i < entityPersister.getTableSpan(); i++ ) { - if ( tableExpression.equals( entityPersister.getTableName( i ) ) - && entityPersister.isNullableTable( i ) ) { - return true; - } - } - return false; - } - - private int executeInsert( - String targetTableExpression, - NamedTableReference targetTableReference, - Expression targetTableKeyExpression, - Supplier> tableKeyColumnVisitationSupplier, - QuerySpec idTableSubQuery, - List assignments, - SqlAstTranslatorFactory sqlAstTranslatorFactory, - JdbcMutationExecutor jdbcMutationExecutor, - ExecutionContext executionContext) { - - // Execute a query in the form - - // - // insert into (...) - // select ... - // from temptable_ - // where not exists ( - // select 1 - // from dml_ - // where dml_. = temptable_. - // ) - - // Create a new QuerySpec for the "insert source" select query. This - // is mostly a copy of the incoming `idTableSubQuery` along with the - // NOT-EXISTS predicate - final QuerySpec insertSourceSelectQuerySpec = makeInsertSourceSelectQuerySpec( idTableSubQuery ); - - // create the `select 1 ...` sub-query and apply the not-exists predicate - final QuerySpec existsSubQuerySpec = createExistsSubQuerySpec( targetTableExpression, tableKeyColumnVisitationSupplier, idTableSubQuery ); - insertSourceSelectQuerySpec.applyPredicate( - new ExistsPredicate( - existsSubQuerySpec, - true, - sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Boolean.class ) - ) - ); - - // Collect the target column references from the key expressions - final List targetColumnReferences = new ArrayList<>(); - if ( targetTableKeyExpression instanceof SqlTuple ) { - //noinspection unchecked - targetColumnReferences.addAll( (Collection) ( (SqlTuple) targetTableKeyExpression ).getExpressions() ); - } - else { - targetColumnReferences.add( (ColumnReference) targetTableKeyExpression ); - } - - // And transform assignments to target column references and selections - for ( Assignment assignment : assignments ) { - targetColumnReferences.addAll( assignment.getAssignable().getColumnReferences() ); - insertSourceSelectQuerySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( assignment.getAssignedValue() ) - ); - } - - final InsertSelectStatement insertSqlAst = new InsertSelectStatement( targetTableReference ); - insertSqlAst.addTargetColumnReferences( targetColumnReferences.toArray( new ColumnReference[0] ) ); - insertSqlAst.setSourceSelectStatement( insertSourceSelectQuerySpec ); - - final JdbcOperationQueryMutation jdbcInsert = sqlAstTranslatorFactory - .buildMutationTranslator( sessionFactory, insertSqlAst ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - - return jdbcMutationExecutor.execute( - jdbcInsert, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> { - }, - executionContext - ); - } - - protected QuerySpec createExistsSubQuerySpec(String targetTableExpression, Supplier> tableKeyColumnVisitationSupplier, QuerySpec idTableSubQuery) { - final NamedTableReference existsTableReference = new NamedTableReference( - targetTableExpression, - "dml_" - ); - - // Prepare a not exists sub-query to avoid violating constraints - final QuerySpec existsSubQuerySpec = new QuerySpec( false ); - existsSubQuerySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - new QueryLiteral<>( - 1, - sessionFactory.getTypeConfiguration().getBasicTypeForJavaType( Integer.class ) - ) - ) - ); - existsSubQuerySpec.getFromClause().addRoot( new TableGroupImpl( - null, - null, - existsTableReference, - entityDescriptor - ) ); - - final TableKeyExpressionCollector existsKeyColumnCollector = new TableKeyExpressionCollector( entityDescriptor ); - tableKeyColumnVisitationSupplier.get().accept( (columnIndex, selection) -> { - assert selection.getContainingTableExpression().equals( targetTableExpression ); - existsKeyColumnCollector.apply( new ColumnReference( existsTableReference, selection ) ); - } ); - existsSubQuerySpec.applyPredicate( - new ComparisonPredicate( - existsKeyColumnCollector.buildKeyExpression(), - ComparisonOperator.EQUAL, - asExpression( idTableSubQuery.getSelectClause()) - ) - ); - return existsSubQuerySpec; - } - - protected static QuerySpec makeInsertSourceSelectQuerySpec(QuerySpec idTableSubQuery) { - final QuerySpec idTableQuerySpec = new QuerySpec( true ); - for ( TableGroup root : idTableSubQuery.getFromClause().getRoots() ) { - idTableQuerySpec.getFromClause().addRoot( root ); - } - for ( SqlSelection sqlSelection : idTableSubQuery.getSelectClause().getSqlSelections() ) { - idTableQuerySpec.getSelectClause().addSqlSelection( sqlSelection ); - } - idTableQuerySpec.applyPredicate( idTableSubQuery.getWhereClauseRestrictions() ); - return idTableQuerySpec; - } - - private int executeUpdate(QuerySpec idTableSubQuery, ExecutionContext executionContext, List assignments, NamedTableReference dmlTableReference, SqlAstTranslatorFactory sqlAstTranslatorFactory, JdbcMutationExecutor jdbcMutationExecutor, Expression keyExpression) { - final UpdateStatement sqlAst = new UpdateStatement( - dmlTableReference, - assignments, - new InSubQueryPredicate( keyExpression, idTableSubQuery, false ) - ); - - final JdbcOperationQueryMutation jdbcUpdate = sqlAstTranslatorFactory - .buildMutationTranslator( sessionFactory, sqlAst ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - - final int updateCount = jdbcMutationExecutor.execute( - jdbcUpdate, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> { - }, - executionContext - ); - return updateCount; - } - - protected Expression resolveMutatingTableKeyExpression(String tableExpression, Supplier> tableKeyColumnVisitationSupplier) { - final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor ); - - tableKeyColumnVisitationSupplier.get().accept( - (columnIndex, selection) -> { - assert selection.getContainingTableExpression().equals( tableExpression ); - keyColumnCollector.apply( new ColumnReference( (String) null, selection ) ); - } - ); - - return keyColumnCollector.buildKeyExpression(); - } - - protected Expression asExpression(SelectClause selectClause) { - final List sqlSelections = selectClause.getSqlSelections(); - if ( sqlSelections.size() == 1 ) { - return sqlSelections.get( 0 ).getExpression(); - } - final List expressions = new ArrayList<>( sqlSelections.size() ); - for ( SqlSelection sqlSelection : sqlSelections ) { - expressions.add( sqlSelection.getExpression() ); - } - return new SqlTuple( expressions, null ); - } - - /** - * For Hibernate Reactive - */ - - protected TemporaryTable getIdTable() { - return idTable; - } - - protected Predicate getSuppliedPredicate() { - return suppliedPredicate; - } - - protected MultiTableSqmMutationConverter getSqmConverter() { - return sqmConverter; - } - - protected Function getSessionUidAccess() { - return sessionUidAccess; - } - - protected JdbcParameterBindings getJdbcParameterBindings() { - return jdbcParameterBindings; - } - - protected EntityMappingType getEntityDescriptor() { - return entityDescriptor; - } - - protected AfterUseAction getAfterUseAction() { - return forceDropAfterUse ? AfterUseAction.DROP : temporaryTableStrategy.getTemporaryTableAfterUseAction(); - } - - protected TableGroup getUpdatingTableGroup() { - return updatingTableGroup; - } - - protected Map> getAssignmentsByTable() { - return assignmentsByTable; - } - - protected SessionFactoryImplementor getSessionFactory() { - return sessionFactory; - } - - // Used by Hibernate Reactive - public TemporaryTableStrategy getTemporaryTableStrategy() { - return temporaryTableStrategy; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/MultiTableHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/MultiTableHandler.java new file mode 100644 index 000000000000..b0e80ad2c4b5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/MultiTableHandler.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.sqm.mutation.spi; + +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +/** + * Simply as a matter of code structuring, it is often worthwhile to put all of the execution code into a separate + * handler (executor) class. This contract helps unify those helpers. + * + * Hiding this "behind the strategy" also allows mixing approaches based on the nature of specific + * queries + * + * @author Steve Ebersole + * @since 7.1 + */ +public interface MultiTableHandler { + + /** + * Create the {@link JdbcParameterBindings} for this multi-table handler based on the execution context. + * + * @param executionContext Contextual information needed for execution + * @return The built parameter bindings + */ + JdbcParameterBindings createJdbcParameterBindings(DomainQueryExecutionContext executionContext); + + /** + * Signals that the SQL depends on the parameter bindings e.g. due to the need for inlining + * of parameter values or multiValued parameters. + */ + boolean dependsOnParameterBindings(); + + boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, QueryOptions queryOptions); + + /** + * Execute the multi-table update or delete indicated by the SQM AST + * passed in when this Handler was created. + * + * @param jdbcParameterBindings The parameter bindings for JDBC parameters + * @param executionContext Contextual information needed for execution + * @return The "number of rows affected" count + */ + int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext); +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/MultiTableHandlerBuildResult.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/MultiTableHandlerBuildResult.java new file mode 100644 index 000000000000..6c35b985537a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/MultiTableHandlerBuildResult.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.sqm.mutation.spi; + +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; +import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +/** + * The build result of a {@link MultiTableHandler}. + * + * @see SqmMultiTableMutationStrategy#buildHandler(SqmDeleteOrUpdateStatement, DomainParameterXref, DomainQueryExecutionContext) + * @see SqmMultiTableInsertStrategy#buildHandler(SqmInsertStatement, DomainParameterXref, DomainQueryExecutionContext) + * @since 7.1 + */ +public record MultiTableHandlerBuildResult( + MultiTableHandler multiTableHandler, + JdbcParameterBindings firstJdbcParameterBindings) { + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableInsertStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableInsertStrategy.java index d29a662885e3..beb8186025ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableInsertStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableInsertStrategy.java @@ -53,13 +53,28 @@ default void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcc } /** - * Execute the multi-table insert indicated by the passed SqmInsertStatement + * Builds a cacheable handler for the passed SqmInsertStatement. * * @return The number of rows affected */ - int executeInsert( + MultiTableHandlerBuildResult buildHandler( SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context); + /** + * Execute the multi-table insert indicated by the passed SqmInsertStatement + * + * @return The number of rows affected + * @deprecated Uses {@link #buildHandler(SqmInsertStatement, DomainParameterXref, DomainQueryExecutionContext)} instead + */ + @Deprecated(forRemoval = true, since = "7.1") + default int executeInsert( + SqmInsertStatement sqmInsertStatement, + DomainParameterXref domainParameterXref, + DomainQueryExecutionContext context) { + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmInsertStatement, domainParameterXref, context ); + return buildResult.multiTableHandler().execute( buildResult.firstJdbcParameterBindings(), context ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableMutationStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableMutationStrategy.java index 139656eef5f2..19548e0898e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableMutationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/SqmMultiTableMutationStrategy.java @@ -9,6 +9,7 @@ import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; @@ -56,23 +57,40 @@ default void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcc // by default, nothing to do... } + /** + * Builds a cacheable handler for the passed SqmDeleteOrUpdateStatement. + * + * @return The number of rows affected + */ + MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context); + /** * Execute the multi-table update indicated by the passed SqmUpdateStatement * * @return The number of rows affected + * @deprecated Use {@link #buildHandler(SqmDeleteOrUpdateStatement, DomainParameterXref, DomainQueryExecutionContext)} instead */ - int executeUpdate( + @Deprecated(forRemoval = true, since = "7.1") + default int executeUpdate( SqmUpdateStatement sqmUpdateStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context) { + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmUpdateStatement, domainParameterXref, context ); + return buildResult.multiTableHandler().execute( buildResult.firstJdbcParameterBindings(), context ); + } /** * Execute the multi-table update indicated by the passed SqmUpdateStatement * * @return The number of rows affected + * @deprecated Use {@link #buildHandler(SqmDeleteOrUpdateStatement, DomainParameterXref, DomainQueryExecutionContext)} instead */ - int executeDelete( + @Deprecated(forRemoval = true, since = "7.1") + default int executeDelete( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context) { + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmDeleteStatement, domainParameterXref, context ); + return buildResult.multiTableHandler().execute( buildResult.firstJdbcParameterBindings(), context ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/StatementCreatorHelper.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/StatementCreatorHelper.java index 699e02853ca7..dcfb03facc33 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/StatementCreatorHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/StatementCreatorHelper.java @@ -13,6 +13,7 @@ * * @author Steve Ebersole */ +@Deprecated(forRemoval = true, since = "7.1") public class StatementCreatorHelper { public static PreparedStatement prepareQueryStatement( String sql,