diff --git a/README.md b/README.md index cb356cb19..7cd62b47f 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,14 @@ Learn more at . Hibernate Reactive has been tested with: - Java 17, 21, 24 -- PostgreSQL 16 +- PostgreSQL 17 - MySQL 9 - MariaDB 11 - Db2 12 -- CockroachDB v24 -- MS SQL Server 2022 +- CockroachDB v25 +- MS SQL Server 2025 - Oracle 23 -- [Hibernate ORM][] 7.0.2.Final +- [Hibernate ORM][] 7.1.0.Final - [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 5.0.0 - [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 5.0.0 - [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 5.0.0 diff --git a/gradle.properties b/gradle.properties index 1fb1a63b1..b23834e1c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,10 +39,10 @@ org.gradle.java.installations.auto-download=false ### Settings the following properties will override the version defined in gradle/libs.versions.toml # The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) -#hibernateOrmVersion = 7.0.2.Final +#hibernateOrmVersion = 7.1.0.Final # Override default Hibernate ORM Gradle plugin version -#hibernateOrmGradlePluginVersion = 7.0.2.Final +#hibernateOrmGradlePluginVersion = 7.1.0.Final # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) # this is required when using intervals or weird versions or the build will fail diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d59faf7a0..30fc4edae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.3" -hibernateOrmVersion = "7.0.9.Final" -hibernateOrmGradlePluginVersion = "7.0.9.Final" +hibernateOrmVersion = "7.1.0.Final" +hibernateOrmGradlePluginVersion = "7.1.0.Final" jacksonDatabindVersion = "2.19.2" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" diff --git a/gradle/version.properties b/gradle/version.properties index 618fb2d72..7cc669e01 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1,5 @@ -projectVersion=4.0.0-SNAPSHOT \ No newline at end of file +<<<<<<< HEAD +projectVersion=4.0.0-SNAPSHOT +======= +projectVersion=3.1.0-SNAPSHOT +>>>>>>> 2a11b54c ([#2387] Upgrade Hibernate ORM to 7.1.0.CR1) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java index 2b4063173..90b098652 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java @@ -91,7 +91,7 @@ public TableMutationBuilder createTableMutationBuilder( return new TableInsertReturningBuilder( persister, tableReference, generatedColumns, sessionFactory ); } else { - return new TableUpdateReturningBuilder<>( persister, tableReference, generatedColumns, sessionFactory ); + return new TableUpdateReturningBuilder( persister, tableReference, generatedColumns, sessionFactory ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java index 0ebebe603..129b9e236 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java @@ -80,7 +80,6 @@ import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.logSqlException; import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -303,25 +302,25 @@ default CompletionStage reactiveGetCurrentVersion(Object id, SharedSessi return getReactiveConnection( session ) .selectJdbc( delegate().getVersionSelectString(), params ) - .thenCompose( resultSet -> currentVersion( session, resultSet ) ); + .thenApply( resultSet -> currentVersion( session, resultSet ) ); } - private CompletionStage currentVersion(SharedSessionContractImplementor session, ResultSet resultSet) { + private Object currentVersion(SharedSessionContractImplementor session, ResultSet resultSet) { try { if ( !resultSet.next() ) { - return nullFuture(); + return null; } if ( !isVersioned() ) { - return completedFuture( this ); + return this; } - return completedFuture( getVersionType() - .getJdbcMapping() - .getJdbcValueExtractor() - .extract( resultSet, 1, session ) ); + return getVersionType() + .getJdbcMapping() + .getJdbcValueExtractor() + .extract( resultSet, 1, session ); } catch (SQLException sqle) { //can never happen - return failedFuture( new JDBCException( "error reading version", sqle ) ); + throw new JDBCException( "error reading version", sqle ); } } @@ -474,13 +473,9 @@ default CompletionStage reactiveInitializeEnhancedEntityUsedAsProxy( Object entity, String nameOfAttributeBeingAccessed, SharedSessionContractImplementor session) { - final BytecodeEnhancementMetadata enhancementMetadata = getEntityPersister().getBytecodeEnhancementMetadata(); final BytecodeLazyAttributeInterceptor currentInterceptor = enhancementMetadata.extractLazyInterceptor( entity ); - if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - final EnhancementAsProxyLazinessInterceptor proxyInterceptor = - (EnhancementAsProxyLazinessInterceptor) currentInterceptor; - + if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor proxyInterceptor ) { final EntityKey entityKey = proxyInterceptor.getEntityKey(); final Object identifier = entityKey.getIdentifier(); @@ -494,19 +489,17 @@ default CompletionStage reactiveInitializeEnhancedEntityUsedAsProxy( .handleEntityNotFound( entityKey.getEntityName(), identifier ); } + final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata + .injectInterceptor( entity, identifier, session ); + if ( nameOfAttributeBeingAccessed == null ) { return null; } else { - final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata - .injectInterceptor( entity, identifier, session ); return interceptor.readObject( - entity, - nameOfAttributeBeingAccessed, - interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) - ? getPropertyValue( entity, nameOfAttributeBeingAccessed ) - : ( (LazyPropertyInitializer) this ) - .initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ) + entity, nameOfAttributeBeingAccessed, interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) + ? getPropertyValue( entity, nameOfAttributeBeingAccessed ) + : ( (LazyPropertyInitializer) this ).initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ) ); } } ); @@ -528,11 +521,12 @@ private CompletionStage loadFromDatabaseOrCache( return completedFuture( loaded ); } } - return ( (ReactiveSingleIdEntityLoader) determineLoaderToUse( session ) ) - .load( identifier, entity, LockOptions.NONE, session ); + final LockOptions lockOptions = new LockOptions(); + return ( (ReactiveSingleIdEntityLoader) determineLoaderToUse( session, lockOptions ) ) + .load( identifier, entity, lockOptions, session ); } - SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session); + SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions); boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java index 0182ef403..2443d4d9b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java @@ -139,8 +139,8 @@ protected AttributeMapping buildPluralAttributeMapping( } @Override - public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { - return super.determineLoaderToUse( session ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions) { + return super.determineLoaderToUse( session, lockOptions ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java index 3006a0eae..2f5878737 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java @@ -86,8 +86,8 @@ public GeneratedValuesMutationDelegate createInsertDelegate() { } @Override - public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { - return super.determineLoaderToUse( session ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions) { + return super.determineLoaderToUse( session, lockOptions ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java index 52871e30f..bba3cf86d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java @@ -139,8 +139,8 @@ protected AttributeMapping buildPluralAttributeMapping( } @Override - public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { - return super.determineLoaderToUse( session ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions) { + return super.determineLoaderToUse( session, lockOptions ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java index 2343a9644..13b0ae6b8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java @@ -22,6 +22,7 @@ import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator; import org.hibernate.engine.jndi.internal.JndiServiceInitiator; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; +import org.hibernate.internal.util.cache.InternalCacheFactoryInitiator; import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; import org.hibernate.reactive.context.impl.VertxContextInitiator; @@ -161,6 +162,9 @@ private static List> buildInitialServiceInitiatorLis // Custom for Hibernate Reactive: BatchLoaderFactory serviceInitiators.add( ReactiveBatchLoaderFactoryInitiator.INSTANCE ); + // [standard] InternalCacheFactoryService + serviceInitiators.add( InternalCacheFactoryInitiator.INSTANCE ); + // --- end of services defined by Hibernate ORM // --- custom ones follow: diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java index 6ca5b2024..d58ae57a9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java @@ -142,19 +142,14 @@ private ReactiveSelectQueryPlan reactiveSelectPlan() { private ReactiveNonSelectQueryPlan reactiveNonSelectPlan() { final QueryInterpretationCache.Key cacheKey = generateNonSelectInterpretationsKey(); if ( cacheKey != null ) { - NonSelectQueryPlan queryPlan = getSession().getFactory().getQueryEngine() - .getInterpretationCache().getNonSelectQueryPlan( cacheKey ); + NonSelectQueryPlan queryPlan = getSession().getFactory().getQueryEngine().getInterpretationCache().getNonSelectQueryPlan( cacheKey ); if ( queryPlan != null ) { return (ReactiveNonSelectQueryPlan) queryPlan; } } - final String sqlString = expandParameterLists(); - ReactiveNonSelectQueryPlan queryPlan = new ReactiveNativeNonSelectQueryPlan( - sqlString, - getQuerySpaces(), - getParameterOccurrences() - ); + final String sqlString = expandParameterLists( 1 ); + ReactiveNonSelectQueryPlan queryPlan = new ReactiveNativeNonSelectQueryPlan( sqlString, getQuerySpaces(), getParameterOccurrences() ); if ( cacheKey != null ) { getSession().getFactory().getQueryEngine().getInterpretationCache() .cacheNonSelectQueryPlan( cacheKey, queryPlan ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java index 388488b26..aae4316a3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java @@ -20,7 +20,7 @@ import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; +import org.hibernate.reactive.query.sqm.internal.ReactiveSqmQueryImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; /** @@ -49,10 +49,10 @@ public SqmQueryImplementor toQuery(SharedSessionContractImplementor session) public SqmQueryImplementor toQuery(SharedSessionContractImplementor session, Class resultType) { // A bit of a hack, I'm sure that if we have a better look at this we can avoid the instanceof if ( delegate instanceof NamedHqlQueryMementoImpl ) { - return new ReactiveQuerySqmImpl<>( (NamedHqlQueryMementoImpl) delegate, resultType, session ); + return new ReactiveSqmQueryImpl<>( (NamedHqlQueryMementoImpl) delegate, resultType, session ); } if ( delegate instanceof NamedCriteriaQueryMementoImpl ) { - return new ReactiveQuerySqmImpl<>( (NamedCriteriaQueryMementoImpl) delegate, resultType, session ); + return new ReactiveSqmQueryImpl<>( (NamedCriteriaQueryMementoImpl) delegate, resultType, session ); } else { throw new UnsupportedOperationException( "NamedSqmQueryMemento not recognized: " + delegate.getClass() ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java index 0794ae435..a43d917e9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java @@ -24,7 +24,7 @@ /** * @see org.hibernate.query.sqm.SqmSelectionQuery */ -public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery, SqmQuery { +public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery, SqmQuery { @Override ReactiveSqmSelectionQuery setParameter(String name, Object value); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java index 7c7cfd062..4f499f5cf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java @@ -6,40 +6,25 @@ package org.hibernate.reactive.query.sqm.internal; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; import org.hibernate.ScrollMode; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; -import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.Query; 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.spi.ScrollableResultsImplementor; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmUtil; -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.SqmTranslatorFactory; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; @@ -68,7 +53,7 @@ public class ConcreteSqmSelectReactiveQueryPlan extends ConcreteSqmSelectQuer private final SqmSelectStatement sqm; private final DomainParameterXref domainParameterXref; - private volatile CacheableSqmInterpretation cacheableSqmInterpretation; + private volatile CacheableSqmInterpretation cacheableSqmInterpretation; public ConcreteSqmSelectReactiveQueryPlan( SqmSelectStatement sqm, @@ -91,27 +76,46 @@ private static CompletionStage> listInterpreter( String hql, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, RowTransformer rowTransformer) { final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); - // I'm using a supplier so that the whenComplete at the end will catch any errors, like a finally-block - Supplier fetchHandlerSupplier = () -> SubselectFetch - .createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.selectStatement, JdbcParametersList.empty(), jdbcParameterBindings ); - return completedFuture( fetchHandlerSupplier ) - .thenApply( Supplier::get ) - .thenCompose( subSelectFetchKeyHandler -> session + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); + + return CompletionStages + .supplyStage( () -> { + final var subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( + session.getPersistenceContext() + .getBatchFetchQueue(), + sqmInterpretation.statement(), + JdbcParametersList.empty(), + jdbcParameterBindings + ); + return session .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) - .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE - .list( jdbcSelect, - jdbcParameterBindings, - ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), - rowTransformer, - ReactiveListResultsConsumer.UniqueSemantic.ALLOW - ) - ) - ) + .thenCompose( required -> { + final Expression fetchExpression = sqmInterpretation.statement() + .getQueryPart() + .getFetchClauseExpression(); + final int resultCountEstimate = fetchExpression != null + ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) + : -1; + return StandardReactiveSelectExecutor.INSTANCE.list( + jdbcSelect, + jdbcParameterBindings, + ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( + hql, + executionContext, + jdbcSelect, + subSelectFetchKeyHandler + ), + rowTransformer, + (Class) executionContext.getResultType(), + ReactiveListResultsConsumer.UniqueSemantic.ALLOW, + resultCountEstimate + ); + } ); + } ) .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } @@ -119,43 +123,47 @@ private static CompletionStage executeQueryInterpreter( String hql, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, RowTransformer rowTransformer, ReactiveResultsConsumer resultsConsumer) { final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); - // I'm using a supplier so that the whenComplete at the end will catch any errors, like a finally-block - Supplier fetchHandlerSupplier = () -> SubselectFetch - .createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.selectStatement, JdbcParametersList.empty(), jdbcParameterBindings ); - return completedFuture( fetchHandlerSupplier ) - .thenApply( Supplier::get ) - .thenCompose( subSelectFetchKeyHandler -> session - .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) - .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE - .executeQuery( - jdbcSelect, - jdbcParameterBindings, - ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( - hql, - executionContext, - jdbcSelect, - subSelectFetchKeyHandler - ), - rowTransformer, - null, - resultCountEstimate( sqmInterpretation, jdbcParameterBindings ), - resultsConsumer - ) - ) - ) + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); + return CompletionStages + .supplyStage( () -> { + final var subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( + session.getPersistenceContext() + .getBatchFetchQueue(), + sqmInterpretation.statement(), + JdbcParametersList.empty(), + jdbcParameterBindings + ); + return session + .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) + .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE + .executeQuery( + jdbcSelect, + jdbcParameterBindings, + ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( + hql, + executionContext, + jdbcSelect, + subSelectFetchKeyHandler + ), + rowTransformer, + null, + resultCountEstimate( sqmInterpretation, jdbcParameterBindings ), + resultsConsumer + ) + ); + }) .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } private static int resultCountEstimate( - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings) { - final Expression fetchExpression = sqmInterpretation.selectStatement.getQueryPart() + final Expression fetchExpression = sqmInterpretation.statement().getQueryPart() .getFetchClauseExpression(); return fetchExpression != null ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) @@ -191,16 +199,16 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec // 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; if ( localCopy == null ) { 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; } } @@ -208,15 +216,15 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec 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; } } @@ -228,130 +236,12 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec return interpreter.interpret( context, executionContext, localCopy, jdbcParameterBindings ); } - // Copy and paste from ORM - private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) { - return SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - sqmInterpretation.getJdbcParamsXref(), - new SqmParameterMappingModelResolutionAccess() { - //this is pretty ugly! - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypes().get(parameter); - } - }, - executionContext.getSession() - ); - } - - private static CacheableSqmInterpretation buildCacheableSqmInterpretation( - SqmSelectStatement sqm, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext executionContext) { - final SharedSessionContractImplementor session = executionContext.getSession(); - final SessionFactoryImplementor sessionFactory = session.getFactory(); - final QueryEngine queryEngine = sessionFactory.getQueryEngine(); - - final SqmTranslatorFactory sqmTranslatorFactory = queryEngine.getSqmTranslatorFactory(); - - final SqmTranslator sqmConverter = sqmTranslatorFactory.createSelectTranslator( - sqm, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - sessionFactory.getSqlTranslationEngine(), - true - ); - -// tableGroupAccess = sqmConverter.getFromClauseAccess(); - final SqmTranslation sqmInterpretation = sqmConverter.translate(); - final FromClauseAccess tableGroupAccess = sqmConverter.getFromClauseAccess(); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final SqlAstTranslator selectTranslator = sqlAstTranslatorFactory - .buildSelectTranslator( sessionFactory, sqmInterpretation.getSqlAst() ); - final Map, Map, List>> jdbcParamsXref = SqmUtil - .generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam ); - 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 - ); - - final JdbcOperationQuerySelect jdbcSelect = selectTranslator.translate( - jdbcParameterBindings, - executionContext.getQueryOptions() - ); - - return new CacheableSqmInterpretation( - sqmInterpretation.getSqlAst(), - jdbcSelect, - tableGroupAccess, - jdbcParamsXref, - sqmInterpretation.getSqmParameterMappingModelTypeResolutions(), - jdbcParameterBindings - ); - } - private interface SqmInterpreter { CompletionStage 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 FromClauseAccess tableGroupAccess; - private final Map, Map, List>> jdbcParamsXref; - private final Map, MappingModelExpressible> sqmParameterMappingModelTypes; - private transient JdbcParameterBindings firstParameterBindings; - - CacheableSqmInterpretation( - SelectStatement selectStatement, - JdbcOperationQuerySelect jdbcSelect, - FromClauseAccess tableGroupAccess, - Map, Map, List>> jdbcParamsXref, - Map, MappingModelExpressible> sqmParameterMappingModelTypes, - JdbcParameterBindings firstParameterBindings) { - this.selectStatement = selectStatement; - this.jdbcSelect = jdbcSelect; - this.tableGroupAccess = tableGroupAccess; - this.jdbcParamsXref = jdbcParamsXref; - this.sqmParameterMappingModelTypes = sqmParameterMappingModelTypes; - this.firstParameterBindings = firstParameterBindings; - } - - SelectStatement getSelectStatement() { - return selectStatement; - } - - JdbcOperationQuerySelect getJdbcSelect() { - return jdbcSelect; - } - - FromClauseAccess getTableGroupAccess() { - return tableGroupAccess; - } - - Map, Map, List>> getJdbcParamsXref() { - return jdbcParamsXref; - } - - public Map, MappingModelExpressible> getSqmParameterMappingModelTypes() { - return sqmParameterMappingModelTypes; - } - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java new file mode 100644 index 000000000..a1759ccbc --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java @@ -0,0 +1,35 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.internal; + +import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.AbstractMultiTableMutationQueryPlan; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.tree.SqmDmlStatement; +import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; + +import java.util.concurrent.CompletionStage; + +public abstract class ReactiveAbstractMultiTableMutationQueryPlan, F> + extends AbstractMultiTableMutationQueryPlan implements ReactiveNonSelectQueryPlan { + + public ReactiveAbstractMultiTableMutationQueryPlan( + S statement, + DomainParameterXref domainParameterXref, + F strategy) { + super( statement, domainParameterXref, strategy ); + } + + @Override + public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), getStatement() ); + final Interpretation interpretation = getInterpretation( context ); + return ((ReactiveHandler)interpretation.handler()).reactiveExecute( interpretation.jdbcParameterBindings(), context ); + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java index 90bdf6c49..30a77ba2d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java @@ -5,35 +5,32 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; /** * @see org.hibernate.query.sqm.internal.MultiTableDeleteQueryPlan */ -public class ReactiveMultiTableDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; - private final ReactiveSqmMultiTableMutationStrategy deleteStrategy; +public class ReactiveMultiTableDeleteQueryPlan + extends ReactiveAbstractMultiTableMutationQueryPlan, SqmMultiTableMutationStrategy> { public ReactiveMultiTableDeleteQueryPlan( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, ReactiveSqmMultiTableMutationStrategy deleteStrategy) { - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; - this.deleteStrategy = deleteStrategy; + super( sqmDelete, domainParameterXref, deleteStrategy ); } @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete ); - return deleteStrategy.reactiveExecuteDelete( sqmDelete, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmDeleteStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java index ed0e68b01..502f4f53c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java @@ -5,35 +5,32 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +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.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; /** * @see org.hibernate.query.sqm.internal.MultiTableInsertQueryPlan */ -public class ReactiveMultiTableInsertQueryPlan implements ReactiveNonSelectQueryPlan { - private final SqmInsertStatement sqmInsert; - private final DomainParameterXref domainParameterXref; - private final ReactiveSqmMultiTableInsertStrategy mutationStrategy; +public class ReactiveMultiTableInsertQueryPlan + extends ReactiveAbstractMultiTableMutationQueryPlan, SqmMultiTableInsertStrategy> { public ReactiveMultiTableInsertQueryPlan( SqmInsertStatement sqmInsert, DomainParameterXref domainParameterXref, ReactiveSqmMultiTableInsertStrategy mutationStrategy) { - this.sqmInsert = sqmInsert; - this.domainParameterXref = domainParameterXref; - this.mutationStrategy = mutationStrategy; + super( sqmInsert, domainParameterXref, mutationStrategy ); } @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmInsert ); - return mutationStrategy.reactiveExecuteInsert( sqmInsert, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmInsertStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableInsertStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java index 605186079..0715e06b5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java @@ -5,35 +5,31 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; /** * @see org.hibernate.query.sqm.internal.MultiTableUpdateQueryPlan */ -public class ReactiveMultiTableUpdateQueryPlan implements ReactiveNonSelectQueryPlan { - private final SqmUpdateStatement sqmUpdate; - private final DomainParameterXref domainParameterXref; - private final ReactiveSqmMultiTableMutationStrategy mutationStrategy; +public class ReactiveMultiTableUpdateQueryPlan + extends ReactiveAbstractMultiTableMutationQueryPlan, SqmMultiTableMutationStrategy> { public ReactiveMultiTableUpdateQueryPlan( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - ReactiveSqmMultiTableMutationStrategy mutationStrategy) { - this.sqmUpdate = sqmUpdate; - this.domainParameterXref = domainParameterXref; - this.mutationStrategy = mutationStrategy; + SqmMultiTableMutationStrategy mutationStrategy) { + super( sqmUpdate, domainParameterXref, mutationStrategy ); } @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmUpdate ); - return mutationStrategy.reactiveExecuteUpdate( sqmUpdate, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmUpdateStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java index eb055a38a..83113911b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java @@ -5,215 +5,89 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.sql.PreparedStatement; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - import org.hibernate.action.internal.BulkOperationCleanupAction; -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.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.QueryParameterImplementor; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SimpleDeleteQueryPlan; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -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.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -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.exec.spi.JdbcParametersList; -import org.hibernate.sql.results.internal.SqlSelectionImpl; -import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.usingLockingAndPaging; - -public class ReactiveSimpleDeleteQueryPlan extends SimpleDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - private final EntityMappingType entityDescriptor; - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; +import java.lang.invoke.MethodHandles; +import java.util.concurrent.CompletionStage; - private JdbcOperationQueryMutation jdbcOperation; +public class ReactiveSimpleDeleteQueryPlan + extends SimpleDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - private SqmTranslation sqmInterpretation; - private Map, Map, List>> jdbcParamsXref; + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); public ReactiveSimpleDeleteQueryPlan( - EntityMappingType entityDescriptor, + EntityPersister entityDescriptor, SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref) { super( entityDescriptor, sqmDelete, domainParameterXref ); - this.entityDescriptor = entityDescriptor; - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; } @Override - protected SqlAstTranslator createTranslator(DomainQueryExecutionContext 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() - ); - - sqmInterpretation = (SqmTranslation) translator.translate(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - return factory.getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, createDeleteAst() ); + protected int execute(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + throw LOG.nonReactiveMethodCall( "executeReactiveUpdate" ); } - // Copy and paste from superclass - private MutationStatement createDeleteAst() { - final MutationStatement ast; - if ( entityDescriptor.getSoftDeleteMapping() == null ) { - ast = sqmInterpretation.getSqlAst(); - } - else { - final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.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 ast; + @Override + public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), getStatement() ); + final Interpretation interpretation = getInterpretation( context ); + final ExecutionContext executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return executeReactive( interpretation.interpretation(), interpretation.jdbcParameterBindings(), executionContext ); } - @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete ); + protected CompletionStage executeReactive(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { final SharedSessionContractImplementor session = executionContext.getSession(); - final SessionFactoryImplementor factory = session.getFactory(); - 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 + return CompletionStages.loop(getCollectionTableDeletes(), delete -> + executeReactive( delete, jdbcParameterBindings, executionContext.getSession(), executionContext ) + ).thenCompose( v -> CompletionStages.loop( + getCollectionTableDeletes(), delete -> + executeReactive( + delete, + jdbcParameterBindings, + executionContext.getSession(), + executionContext + ) + ) + .thenCompose( unused -> executeReactive( + sqmInterpretation.jdbcOperation(), + jdbcParameterBindings, + session, + executionContext + ) ) ); - - if ( jdbcOperation != null - && !jdbcOperation.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - sqlAstTranslator = createTranslator( executionContext ); - } - - if ( sqlAstTranslator != null ) { - jdbcOperation = sqlAstTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - final boolean missingRestriction = sqmDelete.getWhereClause() == null - || sqmDelete.getWhereClause().getPredicate() == null; - if ( missingRestriction ) { - assert domainParameterXref.getSqmParameterCount() == 0; - assert jdbcParamsXref.isEmpty(); - } - - final SqmJdbcExecutionContextAdapter executionContextAdapter = usingLockingAndPaging( executionContext ); - - return ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - entityDescriptor, - (tableReference, attributeMapping) -> { - if ( missingRestriction ) { - return null; - } - - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final Expression fkColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor.getKeyPart(), - null, - factory - ); - - final QuerySpec matchingIdSubQuery = new QuerySpec( false ); - - final MutatingTableReferenceGroupWrapper tableGroup = new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - sqmInterpretation.getSqlAst().getTargetTable() - ); - final Expression fkTargetColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression( - tableGroup, - fkDescriptor.getTargetPart(), - sqmInterpretation.getSqlExpressionResolver(), - factory - ); - matchingIdSubQuery.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( 0, fkTargetColumnExpression ) ); - - matchingIdSubQuery.getFromClause().addRoot( - tableGroup - ); - - matchingIdSubQuery.applyPredicate( sqmInterpretation.getSqlAst().getRestriction() ); - - return new InSubQueryPredicate( fkColumnExpression, matchingIdSubQuery, false ); - }, - ( missingRestriction ? JdbcParameterBindings.NO_BINDINGS : jdbcParameterBindings ), - executionContextAdapter - ) - .thenCompose( unused -> StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcOperation, - jdbcParameterBindings, - session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, - ReactiveSimpleDeleteQueryPlan::doNothing, - executionContextAdapter ) - ); } - private static void doNothing(Integer i, PreparedStatement ps) { + private static CompletionStage executeReactive( + JdbcOperationQueryMutation delete, + JdbcParameterBindings jdbcParameterBindings, + SharedSessionContractImplementor executionContext, + ExecutionContext executionContext1) { + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + delete, + jdbcParameterBindings, + sql -> executionContext + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext1 + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java deleted file mode 100644 index bf67d16ea..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java +++ /dev/null @@ -1,121 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.internal; - - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - -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.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.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.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -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; - -/** - * @see org.hibernate.query.sqm.internal.SimpleInsertQueryPlan - */ -public class ReactiveSimpleInsertQueryPlan implements ReactiveNonSelectQueryPlan { - - private final SqmInsertStatement sqmInsert; - - private final DomainParameterXref domainParameterXref; - - private Map, MappingModelExpressible> paramTypeResolutions; - - private JdbcOperationQueryMutation jdbcInsert; - private Map, Map, List>> jdbcParamsXref; - - public ReactiveSimpleInsertQueryPlan( - SqmInsertStatement sqmInsert, - DomainParameterXref domainParameterXref) { - this.sqmInsert = sqmInsert; - this.domainParameterXref = domainParameterXref; - } - - @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmInsert ); - final SharedSessionContractImplementor session = executionContext.getSession(); - SqlAstTranslator insertTranslator = jdbcInsert == null - ? createInsertTranslator( executionContext ) - : null; - - 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 StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcInsert, - jdbcParameterBindings, - session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, - (i, ps) -> {}, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ) - ); - } - - // Copied from Hibernate ORM SimpleInsertQueryPlan#createInsertTranslator - 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() ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java new file mode 100644 index 000000000..1a2a942f5 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java @@ -0,0 +1,56 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.internal; + +import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SimpleNonSelectQueryPlan; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.tree.SqmDmlStatement; +import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +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 java.util.concurrent.CompletionStage; + +public class ReactiveSimpleNonSelectQueryPlan extends + SimpleNonSelectQueryPlan implements ReactiveNonSelectQueryPlan { + + public ReactiveSimpleNonSelectQueryPlan( + SqmDmlStatement statement, + DomainParameterXref domainParameterXref) { + super( statement, domainParameterXref ); + } + + @Override + public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), getStatement() ); + final Interpretation interpretation = getInterpretation( context ); + final ExecutionContext executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return executeReactive( interpretation.interpretation(), interpretation.jdbcParameterBindings(), executionContext ); + } + + protected CompletionStage executeReactive(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + final SharedSessionContractImplementor session = executionContext.getSession(); + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + sqmInterpretation.jdbcOperation(), + jdbcParameterBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java deleted file mode 100644 index f49f848f5..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java +++ /dev/null @@ -1,117 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.internal; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - -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.QueryEngine; -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.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.sql.SqmTranslator; -import org.hibernate.query.sqm.sql.SqmTranslatorFactory; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.spi.FromClauseAccess; -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; - - -/** - * @see org.hibernate.query.sqm.internal.SimpleUpdateQueryPlan - */ -public class ReactiveSimpleUpdateQueryPlan implements ReactiveNonSelectQueryPlan { - - private final SqmUpdateStatement sqmUpdate; - private final DomainParameterXref domainParameterXref; - - private JdbcOperationQueryMutation jdbcUpdate; - private FromClauseAccess tableGroupAccess; - private Map, Map, List>> jdbcParamsXref; - private Map, MappingModelExpressible> sqmParamMappingTypeResolutions; - - public ReactiveSimpleUpdateQueryPlan(SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref) { - this.sqmUpdate = sqmUpdate; - this.domainParameterXref = domainParameterXref; - } - - @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmUpdate ); - final SharedSessionContractImplementor session = executionContext.getSession(); - SqlAstTranslator updateTranslator = jdbcUpdate == null - ? createUpdateTranslator( executionContext ) - : null; - 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 StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcUpdate, - jdbcParameterBindings, - session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, - (i, ps) -> {}, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ) - ); - } - - // I can probably change ORM to reuse this - private SqlAstTranslator createUpdateTranslator(DomainQueryExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final QueryEngine queryEngine = factory.getQueryEngine(); - - final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); - final SqmTranslator translator = translatorFactory.createMutationTranslator( - sqmUpdate, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ); - - final SqmTranslation sqmInterpretation = translator.translate(); - tableGroupAccess = sqmInterpretation.getFromClauseAccess(); - this.jdbcParamsXref = SqmUtil - .generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam ); - this.sqmParamMappingTypeResolutions = sqmInterpretation.getSqmParameterMappingModelTypeResolutions(); - return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqmInterpretation.getSqlAst() ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java similarity index 80% rename from hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java rename to hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java index 5242ab747..b0bc9f823 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java @@ -44,11 +44,14 @@ import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.sqm.internal.QuerySqmImpl; import org.hibernate.query.sqm.internal.SqmInterpretationsKey; +import org.hibernate.query.sqm.internal.SqmQueryImpl; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; +import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; @@ -71,15 +74,15 @@ import jakarta.persistence.metamodel.Type; /** - * A reactive {@link QuerySqmImpl} + * A reactive {@link SqmQueryImpl} */ -public class ReactiveQuerySqmImpl extends QuerySqmImpl implements ReactiveSqmQueryImplementor { +public class ReactiveSqmQueryImpl extends SqmQueryImpl implements ReactiveSqmQueryImplementor { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final ReactiveAbstractSelectionQuery selectionQueryDelegate; - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( NamedHqlQueryMementoImpl memento, Class expectedResultType, SharedSessionContractImplementor session) { @@ -87,7 +90,7 @@ public ReactiveQuerySqmImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( NamedCriteriaQueryMementoImpl memento, Class resultType, SharedSessionContractImplementor session) { @@ -95,7 +98,7 @@ public ReactiveQuerySqmImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( String hql, HqlInterpretation hqlInterpretation, Class resultType, @@ -104,7 +107,7 @@ public ReactiveQuerySqmImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( SqmStatement criteria, Class resultType, SharedSessionContractImplementor session) { @@ -415,13 +418,13 @@ private ReactiveNonSelectQueryPlan buildUpdateQueryPlan() { final ReactiveSqmMultiTableMutationStrategy multiTableStrategy = (ReactiveSqmMultiTableMutationStrategy) entityDescriptor.getSqmMultiTableMutationStrategy(); return multiTableStrategy == null - ? new ReactiveSimpleUpdateQueryPlan( sqmUpdate, getDomainParameterXref() ) + ? new ReactiveSimpleNonSelectQueryPlan( sqmUpdate, getDomainParameterXref() ) : new ReactiveMultiTableUpdateQueryPlan( sqmUpdate, getDomainParameterXref(), multiTableStrategy ); } private ReactiveNonSelectQueryPlan buildInsertQueryPlan() { //noinspection rawtypes - final SqmInsertStatement sqmInsert = (SqmInsertStatement) getSqmStatement(); + final SqmInsertStatement sqmInsert = (SqmInsertStatement) getSqmStatement(); final String entityNameToInsert = sqmInsert.getTarget().getModel().getHibernateEntityName(); final EntityPersister persister = getSessionFactory() @@ -438,87 +441,101 @@ private ReactiveNonSelectQueryPlan buildInsertQueryPlan() { } } } - if ( !useMultiTableInsert ) { - return new ReactiveSimpleInsertQueryPlan( sqmInsert, getDomainParameterXref() ); - } - else { + if ( useMultiTableInsert ) { return new ReactiveMultiTableInsertQueryPlan( sqmInsert, getDomainParameterXref(), (ReactiveSqmMultiTableInsertStrategy) persister.getSqmMultiTableInsertStrategy() ); } + else if ( sqmInsert instanceof SqmInsertValuesStatement insertValues + && insertValues.getValuesList().size() != 1 + && !getSessionFactory().getJdbcServices().getDialect().supportsValuesListForInsert() ) { + // Split insert-values queries if the dialect doesn't support values lists + final List valuesList = insertValues.getValuesList(); + final ReactiveNonSelectQueryPlan[] planParts = new ReactiveNonSelectQueryPlan[valuesList.size()]; + for ( int i = 0; i < valuesList.size(); i++ ) { + final SqmInsertValuesStatement subInsert = + insertValues.copyWithoutValues( SqmCopyContext.simpleContext() ); + subInsert.values( valuesList.get( i ) ); + planParts[i] = new ReactiveSimpleNonSelectQueryPlan( subInsert, getDomainParameterXref() ); + } + + return new ReactiveAggregatedNonSelectQueryPlan( planParts ); + } + + return new ReactiveSimpleNonSelectQueryPlan( sqmInsert, getDomainParameterXref() ); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // QueryOptions @Override - public ReactiveQuerySqmImpl setHint(String hintName, Object value) { + public ReactiveSqmQueryImpl setHint(String hintName, Object value) { super.setHint( hintName, value ); return this; } @Override - public ReactiveQuerySqmImpl addQueryHint(String hint) { + public ReactiveSqmQueryImpl addQueryHint(String hint) { super.addQueryHint( hint ); return this; } @Override - public ReactiveQuerySqmImpl setLockOptions(LockOptions lockOptions) { + public ReactiveSqmQueryImpl setLockOptions(LockOptions lockOptions) { super.setLockOptions( lockOptions ); return this; } @Override - public ReactiveQuerySqmImpl setLockMode(String alias, LockMode lockMode) { + public ReactiveSqmQueryImpl setLockMode(String alias, LockMode lockMode) { super.setLockMode( alias, lockMode ); return this; } @Override - public ReactiveQuerySqmImpl setTupleTransformer(TupleTransformer transformer) { + public ReactiveSqmQueryImpl setTupleTransformer(TupleTransformer transformer) { throw new UnsupportedOperationException(); } @Override - public ReactiveQuerySqmImpl setResultListTransformer(ResultListTransformer transformer) { + public ReactiveSqmQueryImpl setResultListTransformer(ResultListTransformer transformer) { super.setResultListTransformer( transformer ); return this; } @Override @Deprecated - public ReactiveQuerySqmImpl setResultTransformer(ResultTransformer transformer) { + public ReactiveSqmQueryImpl setResultTransformer(ResultTransformer transformer) { throw new UnsupportedOperationException(); } @Override - public ReactiveQuerySqmImpl setMaxResults(int maxResult) { + public ReactiveSqmQueryImpl setMaxResults(int maxResult) { super.setMaxResults( maxResult ); return this; } @Override - public ReactiveQuerySqmImpl setFirstResult(int startPosition) { + public ReactiveSqmQueryImpl setFirstResult(int startPosition) { super.setFirstResult( startPosition ); return this; } @Override - public ReactiveQuerySqmImpl setHibernateFlushMode(FlushMode flushMode) { + public ReactiveSqmQueryImpl setHibernateFlushMode(FlushMode flushMode) { super.setHibernateFlushMode( flushMode ); return this; } @Override - public ReactiveQuerySqmImpl setFlushMode(FlushModeType flushMode) { + public ReactiveSqmQueryImpl setFlushMode(FlushModeType flushMode) { super.setFlushMode( flushMode ); return this; } @Override - public ReactiveQuerySqmImpl setLockMode(LockModeType lockMode) { + public ReactiveSqmQueryImpl setLockMode(LockModeType lockMode) { super.setLockMode( lockMode ); return this; } @@ -527,313 +544,313 @@ public ReactiveQuerySqmImpl setLockMode(LockModeType lockMode) { // covariance @Override - public ReactiveQuerySqmImpl applyGraph(RootGraph graph, GraphSemantic semantic) { + public ReactiveSqmQueryImpl applyGraph(RootGraph graph, GraphSemantic semantic) { super.applyGraph( graph, semantic ); return this; } @Override - public ReactiveQuerySqmImpl applyLoadGraph(RootGraph graph) { + public ReactiveSqmQueryImpl applyLoadGraph(RootGraph graph) { super.applyLoadGraph( graph ); return this; } @Override - public ReactiveQuerySqmImpl applyFetchGraph(RootGraph graph) { + public ReactiveSqmQueryImpl applyFetchGraph(RootGraph graph) { super.applyFetchGraph( graph ); return this; } @Override - public ReactiveQuerySqmImpl setComment(String comment) { + public ReactiveSqmQueryImpl setComment(String comment) { super.setComment( comment ); return this; } @Override - public ReactiveQuerySqmImpl setCacheMode(CacheMode cacheMode) { + public ReactiveSqmQueryImpl setCacheMode(CacheMode cacheMode) { super.setCacheMode( cacheMode ); return this; } @Override - public ReactiveQuerySqmImpl setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { + public ReactiveSqmQueryImpl setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { super.setCacheRetrieveMode( cacheRetrieveMode ); return this; } @Override - public ReactiveQuerySqmImpl setCacheStoreMode(CacheStoreMode cacheStoreMode) { + public ReactiveSqmQueryImpl setCacheStoreMode(CacheStoreMode cacheStoreMode) { super.setCacheStoreMode( cacheStoreMode ); return this; } @Override - public ReactiveQuerySqmImpl setCacheable(boolean cacheable) { + public ReactiveSqmQueryImpl setCacheable(boolean cacheable) { super.setCacheable( cacheable ); return this; } @Override - public ReactiveQuerySqmImpl setCacheRegion(String cacheRegion) { + public ReactiveSqmQueryImpl setCacheRegion(String cacheRegion) { super.setCacheRegion( cacheRegion ); return this; } @Override - public ReactiveQuerySqmImpl setHibernateLockMode(LockMode lockMode) { + public ReactiveSqmQueryImpl setHibernateLockMode(LockMode lockMode) { super.setHibernateLockMode( lockMode ); return this; } @Override - public ReactiveQuerySqmImpl setTimeout(int timeout) { + public ReactiveSqmQueryImpl setTimeout(int timeout) { super.setTimeout( timeout ); return this; } @Override - public ReactiveQuerySqmImpl setFetchSize(int fetchSize) { + public ReactiveSqmQueryImpl setFetchSize(int fetchSize) { super.setFetchSize( fetchSize ); return this; } @Override - public ReactiveQuerySqmImpl setReadOnly(boolean readOnly) { + public ReactiveSqmQueryImpl setReadOnly(boolean readOnly) { super.setReadOnly( readOnly ); return this; } @Override - public ReactiveQuerySqmImpl setProperties(Object bean) { + public ReactiveSqmQueryImpl setProperties(Object bean) { super.setProperties( bean ); return this; } @Override - public ReactiveQuerySqmImpl setProperties(Map bean) { + public ReactiveSqmQueryImpl setProperties(Map bean) { super.setProperties( bean ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(String name, Object value) { + public ReactiveSqmQueryImpl setParameter(String name, Object value) { super.setParameter( name, value ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(String name, P value, Class

javaType) { + public

ReactiveSqmQueryImpl setParameter(String name, P value, Class

javaType) { super.setParameter( name, value, javaType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(String name, P value, Type

type) { + public

ReactiveSqmQueryImpl setParameter(String name, P value, Type

type) { super.setParameter( name, value, type ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(String name, Instant value, TemporalType temporalType) { + public ReactiveSqmQueryImpl setParameter(String name, Instant value, TemporalType temporalType) { super.setParameter( name, value, temporalType ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(int position, Object value) { + public ReactiveSqmQueryImpl setParameter(int position, Object value) { super.setParameter( position, value ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(int position, P value, Class

javaType) { + public

ReactiveSqmQueryImpl setParameter(int position, P value, Class

javaType) { super.setParameter( position, value, javaType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(int position, P value, Type

type) { + public

ReactiveSqmQueryImpl setParameter(int position, P value, Type

type) { super.setParameter( position, value, type ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(int position, Instant value, TemporalType temporalType) { + public ReactiveSqmQueryImpl setParameter(int position, Instant value, TemporalType temporalType) { super.setParameter( position, value, temporalType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(QueryParameter

parameter, P value) { + public

ReactiveSqmQueryImpl setParameter(QueryParameter

parameter, P value) { super.setParameter( parameter, value ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(QueryParameter

parameter, P value, Class

javaType) { + public

ReactiveSqmQueryImpl setParameter(QueryParameter

parameter, P value, Class

javaType) { super.setParameter( parameter, value, javaType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(QueryParameter

parameter, P value, Type

type) { + public

ReactiveSqmQueryImpl setParameter(QueryParameter

parameter, P value, Type

type) { super.setParameter( parameter, value, type ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(Parameter

parameter, P value) { + public

ReactiveSqmQueryImpl setParameter(Parameter

parameter, P value) { super.setParameter( parameter, value ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(Parameter param, Calendar value, TemporalType temporalType) { + public ReactiveSqmQueryImpl setParameter(Parameter param, Calendar value, TemporalType temporalType) { super.setParameter( param, value, temporalType ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(Parameter param, Date value, TemporalType temporalType) { + public ReactiveSqmQueryImpl setParameter(Parameter param, Date value, TemporalType temporalType) { super.setParameter( param, value, temporalType ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(String name, Calendar value, TemporalType temporalType) { + public ReactiveSqmQueryImpl setParameter(String name, Calendar value, TemporalType temporalType) { super.setParameter( name, value, temporalType ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(String name, Date value, TemporalType temporalType) { + public ReactiveSqmQueryImpl setParameter(String name, Date value, TemporalType temporalType) { super.setParameter( name, value, temporalType ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(int position, Calendar value, TemporalType temporalType) { + public ReactiveSqmQueryImpl setParameter(int position, Calendar value, TemporalType temporalType) { super.setParameter( position, value, temporalType ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(int position, Date value, TemporalType temporalType) { + public ReactiveSqmQueryImpl setParameter(int position, Date value, TemporalType temporalType) { super.setParameter( position, value, temporalType ); return this; } @Override - public ReactiveQuerySqmImpl setParameterList(String name, Collection values) { + public ReactiveSqmQueryImpl setParameterList(String name, Collection values) { super.setParameterList( name, values ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(String name, Collection values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(String name, Collection values, Class

javaType) { super.setParameterList( name, values, javaType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(String name, Collection values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(String name, Collection values, Type

type) { super.setParameterList( name, values, type ); return this; } @Override - public ReactiveQuerySqmImpl setParameterList(String name, Object[] values) { + public ReactiveSqmQueryImpl setParameterList(String name, Object[] values) { super.setParameterList( name, values ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(String name, P[] values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(String name, P[] values, Class

javaType) { super.setParameterList( name, values, javaType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(String name, P[] values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(String name, P[] values, Type

type) { super.setParameterList( name, values, type ); return this; } @Override - public ReactiveQuerySqmImpl setParameterList(int position, Collection values) { + public ReactiveSqmQueryImpl setParameterList(int position, Collection values) { super.setParameterList( position, values ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(int position, Collection values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(int position, Collection values, Class

javaType) { super.setParameterList( position, values, javaType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(int position, Collection values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(int position, Collection values, Type

type) { super.setParameterList( position, values, type ); return this; } @Override - public ReactiveQuerySqmImpl setParameterList(int position, Object[] values) { + public ReactiveSqmQueryImpl setParameterList(int position, Object[] values) { super.setParameterList( position, values ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(int position, P[] values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(int position, P[] values, Class

javaType) { super.setParameterList( position, values, javaType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(int position, P[] values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(int position, P[] values, Type

type) { super.setParameterList( position, values, type ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, Collection values) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, Collection values) { super.setParameterList( parameter, values ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, Collection values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, Collection values, Class

javaType) { super.setParameterList( parameter, values, javaType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, Collection values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, Collection values, Type

type) { super.setParameterList( parameter, values, type ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, P[] values) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, P[] values) { super.setParameterList( parameter, values ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, P[] values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, P[] values, Class

javaType) { super.setParameterList( parameter, values, javaType ); return this; } @Override - public

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, P[] values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, P[] values, Type

type) { super.setParameterList( parameter, values, type ); return this; } @Override - public ReactiveQuerySqmImpl setFollowOnLocking(boolean enable) { + public ReactiveSqmQueryImpl setFollowOnLocking(boolean enable) { super.setFollowOnLocking( enable ); return this; } @@ -844,7 +861,7 @@ public void applyGraph(RootGraphImplementor graph, GraphSemantic semantic) { } @Override - public ReactiveQuerySqmImpl enableFetchProfile(String profileName) { + public ReactiveSqmQueryImpl enableFetchProfile(String profileName) { selectionQueryDelegate.enableFetchProfile( profileName ); return this; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java index d23ae3225..cab920f73 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java @@ -5,14 +5,30 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal; +import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.mutation.internal.Handler; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.Handler */ -public interface ReactiveHandler { +public interface ReactiveHandler extends Handler { + Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + @Override + default int execute(DomainQueryExecutionContext executionContext) { + throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + } + + @Override + default int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext) { + throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + } /** * Execute the multi-table update or delete indicated by the SQM AST @@ -22,5 +38,17 @@ public interface ReactiveHandler { * * @return The "number of rows affected" count */ - CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext); + default CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext){ + return reactiveExecute( createJdbcParameterBindings( executionContext ), executionContext ); + } + + /** + * 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 + */ + CompletionStage reactiveExecute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java deleted file mode 100644 index b2e36a296..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java +++ /dev/null @@ -1,161 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal; - -import java.sql.PreparedStatement; -import java.util.concurrent.CompletionStage; -import java.util.function.BiFunction; - -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.reactive.util.impl.CompletionStages.Completable; -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; - -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -/** - * @see org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper - */ -public class ReactiveSqmMutationStrategyHelper { - - private ReactiveSqmMutationStrategyHelper() { - } - - public static CompletionStage cleanUpCollectionTables( - EntityMappingType entityDescriptor, - BiFunction restrictionProducer, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - if ( !entityDescriptor.getEntityPersister().hasCollections() ) { - // none to clean-up - return voidFuture(); - } - - try { - final Completable stage = new Completable<>(); - entityDescriptor - .visitSubTypeAttributeMappings( attributeMapping -> { - if ( attributeMapping instanceof PluralAttributeMapping ) { - cleanUpCollectionTable( - (PluralAttributeMapping) attributeMapping, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( stage::complete ); - } - else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { - cleanUpCollectionTables( - (EmbeddedAttributeMapping) attributeMapping, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( stage::complete ); - } - else { - stage.complete( null, null ); - } - } - ); - return stage.getStage(); - } - catch (Throwable throwable) { - return failedFuture( throwable ); - } - } - - private static CompletionStage cleanUpCollectionTables( - EmbeddedAttributeMapping attributeMapping, - BiFunction restrictionProducer, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - try { - final Completable completable = new Completable<>(); - attributeMapping.visitSubParts( - modelPart -> { - if ( modelPart instanceof PluralAttributeMapping ) { - cleanUpCollectionTable( - (PluralAttributeMapping) modelPart, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( completable::complete ); - } - else if ( modelPart instanceof EmbeddedAttributeMapping ) { - cleanUpCollectionTables( - (EmbeddedAttributeMapping) modelPart, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( completable::complete ); - } - }, - null - ); - return completable.getStage(); - } - catch (Throwable throwable) { - return failedFuture( throwable ); - } - } - - private static CompletionStage cleanUpCollectionTable( - PluralAttributeMapping attributeMapping, - BiFunction restrictionProducer, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - final String separateCollectionTable = attributeMapping.getSeparateCollectionTable(); - if ( separateCollectionTable == null ) { - // one-to-many - update the matching rows in the associated table setting the fk column(s) to null - // not yet implemented - do nothing - return voidFuture(); - } - - final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - - // element-collection or many-to-many - delete the collection-table row - - final NamedTableReference tableReference = new NamedTableReference( - separateCollectionTable, - DeleteStatement.DEFAULT_ALIAS, - true - ); - - final DeleteStatement sqlAstDelete = new DeleteStatement( - tableReference, - restrictionProducer.apply( tableReference, attributeMapping ) - ); - - JdbcOperationQueryMutation jdbcDelete = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, sqlAstDelete ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcDelete, - jdbcParameterBindings, - executionContext.getSession().getJdbcCoordinator().getStatementPreparer()::prepareStatement, - ReactiveSqmMutationStrategyHelper::doNothing, - executionContext - ) - .thenCompose( CompletionStages::voidFuture ); - } - - private static void doNothing(Integer i, PreparedStatement ps) { - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java index 806b75718..c0eb4ee3a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java @@ -5,189 +5,49 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.query.spi.DomainQueryExecutionContext; -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.internal.cte.AbstractCteMutationHandler; -import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; -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.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.cte.CteContainer; -import org.hibernate.sql.ast.tree.cte.CteMaterialization; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.cte.CteTableGroup; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -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.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.basic.BasicResult; -import org.hibernate.sql.results.internal.SqlSelectionImpl; + +import java.util.concurrent.CompletionStage; /** * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler */ public interface ReactiveAbstractCteMutationHandler extends ReactiveAbstractMutationHandler { - CteTable getCteTable(); - - DomainParameterXref getDomainParameterXref(); - - CteMutationStrategy getStrategy(); - - void addDmlCtes( - CteContainer statement, - CteStatement idSelectCte, - MultiTableSqmMutationConverter sqmConverter, - Map, List> parameterResolutions, - SessionFactoryImplementor factory); - /** - * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler#execute(DomainQueryExecutionContext) + * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler#execute(JdbcParameterBindings, DomainQueryExecutionContext) */ @Override - default CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final SqmDeleteOrUpdateStatement sqmMutationStatement = getSqmDeleteOrUpdateStatement(); - final SessionFactoryImplementor factory = executionContext.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 ) { - explicitDmlTargetAlias = "dml_target"; - } - else { - explicitDmlTargetAlias = sqmMutationStatement.getTarget().getExplicitAlias(); - } - - final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( - entityDescriptor, - sqmMutationStatement, - sqmMutationStatement.getTarget(), - explicitDmlTargetAlias, - getDomainParameterXref(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - factory.getSqlTranslationEngine() - ); - final Map, List> parameterResolutions; - if ( getDomainParameterXref().getSqmParameterCount() == 0 ) { - parameterResolutions = Collections.emptyMap(); - } - else { - parameterResolutions = new IdentityHashMap<>(); - } - - final Predicate restriction = sqmConverter.visitWhereClause( sqmMutationStatement.getWhereClause() ); - sqmConverter.pruneTableGroupJoins(); - - final CteStatement idSelectCte = new CteStatement( - getCteTable(), - MatchingIdSelectionHelper.generateMatchingIdSelectStatement( - entityDescriptor, - sqmMutationStatement, - true, - restriction, - sqmConverter, - executionContext - ), - // The id-select cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - - // Create the main query spec that will return the count of - final QuerySpec querySpec = new QuerySpec( true, 1 ); - final List> domainResults = new ArrayList<>( 1 ); - final SelectStatement statement = new SelectStatement( querySpec, domainResults ); - final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( factory, statement ); - - final Expression count = createCountStar( factory, sqmConverter ); - domainResults.add( - new BasicResult<>( - 0, - null, - ( (SqlExpressible) count ).getJdbcMapping() - ) - ); - querySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, count ) ); - querySpec.getFromClause().addRoot( - new CteTableGroup( - new NamedTableReference( - idSelectCte.getCteTable().getTableExpression(), - AbstractCteMutationHandler.CTE_TABLE_IDENTIFIER - ) - ) - ); - - // Add all CTEs - statement.addCteStatement( idSelectCte ); - addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), sqmConverter ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter ); - } - }, - executionContext.getSession() - ); + default CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext executionContext){ final LockOptions lockOptions = executionContext.getQueryOptions().getLockOptions(); - final LockMode lockMode = lockOptions.getAliasSpecificLockMode( explicitDmlTargetAlias ); // Acquire a WRITE lock for the rows that are about to be modified - lockOptions.setAliasSpecificLockMode( explicitDmlTargetAlias, LockMode.WRITE ); - final JdbcOperationQuerySelect select = translator.translate( - jdbcParameterBindings, - executionContext.getQueryOptions() - ); - lockOptions.setAliasSpecificLockMode( explicitDmlTargetAlias, lockMode ); - + lockOptions.setLockMode( LockMode.WRITE ); return ( (ReactiveSharedSessionContractImplementor) executionContext.getSession() ) - .reactiveAutoFlushIfRequired( select.getAffectedTableNames() ) - .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE.list( - select, - jdbcParameterBindings, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), - row -> row[0], - ReactiveListResultsConsumer.UniqueSemantic.NONE - ) - .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) + .reactiveAutoFlushIfRequired( getSelect().getAffectedTableNames() ) + .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE + .list( + getSelect(), + jdbcParameterBindings, + SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), + row -> row[0], + null, + ReactiveListResultsConsumer.UniqueSemantic.NONE, + 1 + ) + .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) ); } - Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter); + JdbcOperationQuerySelect getSelect(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java index a1f555253..67c3641b8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java @@ -5,60 +5,37 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; import org.hibernate.engine.spi.SessionFactoryImplementor; +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.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.cte.CteDeleteHandler; import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.sql.ast.tree.cte.CteContainer; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.cte.CteDeleteHandler */ public class ReactiveCteDeleteHandler extends CteDeleteHandler implements ReactiveAbstractCteMutationHandler { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - protected ReactiveCteDeleteHandler( CteTable cteTable, SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory ); - } - - @Override - public void addDmlCtes( - CteContainer statement, - CteStatement idSelectCte, - MultiTableSqmMutationConverter sqmConverter, - Map, List> parameterResolutions, - SessionFactoryImplementor factory) { - super.addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory, context, firstJdbcParameterBindingsConsumer ); } @Override - public Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter) { - return super.createCountStar( factory, sqmConverter ); + public JdbcOperationQuerySelect getSelect() { + return super.getSelect(); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java index 603563e0f..9361df67f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java @@ -5,93 +5,35 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.lang.invoke.MethodHandles; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; -import org.hibernate.id.OptimizableGenerator; -import org.hibernate.id.enhanced.Optimizer; -import org.hibernate.internal.util.collections.Stack; -import org.hibernate.metamodel.mapping.BasicValuedMapping; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SqlExpressible; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.results.internal.TableGroupImpl; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.BinaryArithmeticOperator; -import org.hibernate.query.sqm.ComparisonOperator; 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.SqmInsertStrategyHelper; import org.hibernate.query.sqm.mutation.internal.cte.CteInsertHandler; -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; -import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.SqlAstJoinType; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; -import org.hibernate.sql.ast.tree.Statement; -import org.hibernate.sql.ast.tree.cte.CteColumn; -import org.hibernate.sql.ast.tree.cte.CteMaterialization; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.cte.CteTableGroup; -import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; -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.SelfRenderingSqlFragmentExpression; -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.ValuesTableGroup; -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.select.QueryPart; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.basic.BasicResult; -import org.hibernate.sql.results.internal.SqlSelectionImpl; + +import java.lang.invoke.MethodHandles; +import java.util.concurrent.CompletionStage; public class ReactiveCteInsertHandler extends CteInsertHandler implements ReactiveHandler { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final SessionFactoryImplementor sessionFactory; - public ReactiveCteInsertHandler( CteTable cteTable, SqmInsertStatement sqmStatement, DomainParameterXref domainParameterXref, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmStatement, domainParameterXref, sessionFactory ); - this.sessionFactory = sessionFactory; + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmStatement, domainParameterXref, context, firstJdbcParameterBindingsConsumer ); } @Override @@ -99,470 +41,23 @@ public int execute(DomainQueryExecutionContext executionContext) { throw LOG.nonReactiveMethodCall( "reactiveExecute" ); } - // Pretty much a copy and paste of the method in the super class - // We should refactor this @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final SqmInsertStatement sqmInsertStatement = getSqmStatement(); - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final EntityPersister entityDescriptor = getEntityDescriptor().getEntityPersister(); - final String explicitDmlTargetAlias; - if ( sqmInsertStatement.getTarget().getExplicitAlias() == null ) { - explicitDmlTargetAlias = "dml_target"; - } - else { - explicitDmlTargetAlias = sqmInsertStatement.getTarget().getExplicitAlias(); - } - - final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( - entityDescriptor, - sqmInsertStatement, - sqmInsertStatement.getTarget(), - explicitDmlTargetAlias, - getDomainParameterXref(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - factory.getSqlTranslationEngine() - ); - final TableGroup insertingTableGroup = sqmConverter.getMutatingTableGroup(); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // visit the insertion target using our special converter, collecting - // information about the target paths - - final int size = getSqmStatement().getInsertionTargetPaths().size(); - final List, Assignment>> targetPathColumns = new ArrayList<>( size ); - final List targetPathCteColumns = new ArrayList<>( size ); - final NamedTableReference entityTableReference = new NamedTableReference( - getCteTable().getTableExpression(), - TemporaryTable.DEFAULT_ALIAS, - true - ); - final InsertSelectStatement insertStatement = new InsertSelectStatement( entityTableReference ); - - final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = sqmConverter.visitInsertionTargetPaths( - (assignable, columnReferences) -> { - final SqmPathInterpretation pathInterpretation = (SqmPathInterpretation) assignable; - final int offset = CteTable.determineModelPartStartIndex( - entityDescriptor, - pathInterpretation.getExpressionType() - ); - if ( offset == -1 ) { - throw new IllegalStateException( "Couldn't find matching cte column for: " + ( (Expression) assignable ).getExpressionType() ); - } - final int end = offset + pathInterpretation.getExpressionType().getJdbcTypeCount(); - // Find a matching cte table column and set that at the current index - final List columns = getCteTable().getCteColumns().subList( offset, end ); - insertStatement.addTargetColumnReferences( columnReferences ); - targetPathCteColumns.addAll( columns ); - targetPathColumns.add( - new AbstractMap.SimpleEntry<>( - columns, - new Assignment( - assignable, - (Expression) assignable - ) - ) - ); - }, - sqmInsertStatement, - entityDescriptor, - insertingTableGroup - ); - - final boolean assignsId = targetPathCteColumns.contains( getCteTable().getCteColumns().get( 0 ) ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Create the statement that represent the source for the entity cte - - 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() ); - 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 ) ) { - final CteColumn rowNumberColumn = getCteTable().getCteColumns() - .get( getCteTable().getCteColumns().size() - 1 ); - final ColumnReference columnReference = new ColumnReference( - (String) null, - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - insertStatement.getTargetColumns().set( - insertStatement.getTargetColumns().size() - 1, - columnReference - ); - targetPathCteColumns.set( - targetPathCteColumns.size() - 1, - rowNumberColumn - ); - } - if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - SqmInsertStrategyHelper.createRowNumberingExpression( - querySpec, - sessionFactory - ) - ) - ); - } - } - ); - queryStatement = new SelectStatement( queryPart ); - } - else { - final List sqmValuesList = ( (SqmInsertValuesStatement) sqmInsertStatement ).getValuesList(); - final List valuesList = new ArrayList<>( sqmValuesList.size() ); - for ( SqmValues sqmValues : sqmValuesList ) { - final Values values = sqmConverter.visitValues( sqmValues ); - additionalInsertValues.applyValues( values ); - valuesList.add( values ); - } - final QuerySpec querySpec = new QuerySpec( true ); - final NavigablePath navigablePath = new NavigablePath( entityDescriptor.getRootPathName() ); - final List columnNames = new ArrayList<>( targetPathColumns.size() ); - final String valuesAlias = insertingTableGroup.getPrimaryTableReference().getIdentificationVariable(); - for ( Map.Entry, Assignment> entry : targetPathColumns ) { - for ( ColumnReference columnReference : entry.getValue().getAssignable().getColumnReferences() ) { - columnNames.add( columnReference.getColumnExpression() ); - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - columnReference.getQualifier().equals( valuesAlias ) - ? columnReference - : new ColumnReference( - valuesAlias, - columnReference.getColumnExpression(), - false, - null, - columnReference.getJdbcMapping() - ) - ) - ); - } - } - final ValuesTableGroup valuesTableGroup = new ValuesTableGroup( - navigablePath, - entityDescriptor.getEntityPersister(), - valuesList, - insertingTableGroup.getPrimaryTableReference().getIdentificationVariable(), - columnNames, - true, - factory - ); - querySpec.getFromClause().addRoot( valuesTableGroup ); - queryStatement = new SelectStatement( querySpec ); - } - processingStateStack.push( oldState ); - sqmConverter.pruneTableGroupJoins(); - - if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - // Add the row number to the assignments - final CteColumn rowNumberColumn = getCteTable().getCteColumns() - .get( getCteTable().getCteColumns().size() - 1 ); - final ColumnReference columnReference = new ColumnReference( - (String) null, - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - insertStatement.getTargetColumns().add( columnReference ); - targetPathCteColumns.add( rowNumberColumn ); - } - - final CteTable entityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); - - // Create the main query spec that will return the count of rows - final QuerySpec querySpec = new QuerySpec( true, 1 ); - final List> domainResults = new ArrayList<>( 1 ); - final SelectStatement statement = new SelectStatement( querySpec, domainResults ); - - final CteStatement entityCte; - if ( additionalInsertValues.requiresRowNumberIntermediate() ) { - final CteTable fullEntityCteTable = getCteTable(); - final String baseTableName = "base_" + entityCteTable.getTableExpression(); - final CteStatement baseEntityCte = new CteStatement( - entityCteTable.withName( baseTableName ), - queryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( baseEntityCte ); - - final CteColumn rowNumberColumn = fullEntityCteTable.getCteColumns().get( - fullEntityCteTable.getCteColumns().size() - 1 - ); - final ColumnReference rowNumberColumnReference = new ColumnReference( - "e", - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - final CteColumn idColumn = fullEntityCteTable.getCteColumns().get( 0 ); - final BasicValuedMapping idType = (BasicValuedMapping) idColumn.getJdbcMapping(); - final Optimizer optimizer = ( (OptimizableGenerator) entityDescriptor.getGenerator() ).getOptimizer(); - final BasicValuedMapping integerType = (BasicValuedMapping) rowNumberColumn.getJdbcMapping(); - final Expression rowNumberMinusOneModuloIncrement = new BinaryArithmeticExpression( - new BinaryArithmeticExpression( - rowNumberColumnReference, - BinaryArithmeticOperator.SUBTRACT, - new QueryLiteral<>( - 1, - (BasicValuedMapping) rowNumberColumn.getJdbcMapping() - ), - integerType - ), - BinaryArithmeticOperator.MODULO, - new QueryLiteral<>( - optimizer.getIncrementSize(), - integerType - ), - integerType - ); - - // Create the CTE that fetches a new sequence value for the row numbers that need it - { - final QuerySpec rowsWithSequenceQuery = new QuerySpec( true ); - rowsWithSequenceQuery.getFromClause().addRoot( - new CteTableGroup( new NamedTableReference( baseTableName, "e" ) ) - ); - rowsWithSequenceQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - rowNumberColumnReference - ) - ); - final String fragment = ( (BulkInsertionCapableIdentifierGenerator) entityDescriptor.getGenerator() ) - .determineBulkInsertionIdentifierGenerationSelectFragment( - sessionFactory.getSqlStringGenerationContext() - ); - rowsWithSequenceQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 1, - new SelfRenderingSqlFragmentExpression( fragment ) - ) - ); - rowsWithSequenceQuery.applyPredicate( - new ComparisonPredicate( - rowNumberMinusOneModuloIncrement, - ComparisonOperator.EQUAL, - new QueryLiteral<>( - 0, - integerType - ) - ) - ); - final CteTable rowsWithSequenceCteTable = new CteTable( - ROW_NUMBERS_WITH_SEQUENCE_VALUE, - List.of( rowNumberColumn, idColumn ) - ); - final SelectStatement rowsWithSequenceStatement = new SelectStatement( rowsWithSequenceQuery ); - final CteStatement rowsWithSequenceCte = new CteStatement( - rowsWithSequenceCteTable, - rowsWithSequenceStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( rowsWithSequenceCte ); - } - - // Create the CTE that represents the entity cte - { - final QuerySpec entityQuery = new QuerySpec( true ); - final NavigablePath navigablePath = new NavigablePath( baseTableName ); - final TableGroup baseTableGroup = new TableGroupImpl( - navigablePath, - null, - new NamedTableReference( baseTableName, "e" ), - null - ); - final TableGroup rowsWithSequenceTableGroup = new CteTableGroup( - new NamedTableReference( - ROW_NUMBERS_WITH_SEQUENCE_VALUE, - "t" - ) - ); - baseTableGroup.addTableGroupJoin( - new TableGroupJoin( - rowsWithSequenceTableGroup.getNavigablePath(), - SqlAstJoinType.LEFT, - rowsWithSequenceTableGroup, - new ComparisonPredicate( - new BinaryArithmeticExpression( - rowNumberColumnReference, - BinaryArithmeticOperator.SUBTRACT, - rowNumberMinusOneModuloIncrement, - integerType - ), - ComparisonOperator.EQUAL, - new ColumnReference( - "t", - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ) - ) - ) - ); - entityQuery.getFromClause().addRoot( baseTableGroup ); - entityQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - new BinaryArithmeticExpression( - new ColumnReference( - "t", - idColumn.getColumnExpression(), - false, - null, - idColumn.getJdbcMapping() - ), - BinaryArithmeticOperator.ADD, - new BinaryArithmeticExpression( - rowNumberColumnReference, - BinaryArithmeticOperator.SUBTRACT, - new ColumnReference( - "t", - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ), - integerType - ), - idType - ) - ) - ); - final CteTable finalEntityCteTable; - if ( targetPathCteColumns.contains( getCteTable().getCteColumns().get( 0 ) ) ) { - finalEntityCteTable = entityCteTable; - } - else { - targetPathCteColumns.add( 0, getCteTable().getCteColumns().get( 0 ) ); - finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); - } - final List cteColumns = finalEntityCteTable.getCteColumns(); - for ( int i = 1; i < cteColumns.size(); i++ ) { - final CteColumn cteColumn = cteColumns.get( i ); - entityQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - i, - new ColumnReference( - "e", - cteColumn.getColumnExpression(), - false, - null, - cteColumn.getJdbcMapping() - ) - ) - ); - } - - final SelectStatement entityStatement = new SelectStatement( entityQuery ); - entityCte = new CteStatement( - finalEntityCteTable, - entityStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( entityCte ); - } - } - else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - final String baseTableName = "base_" + entityCteTable.getTableExpression(); - final CteStatement baseEntityCte = new CteStatement( - entityCteTable.withName( baseTableName ), - queryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( baseEntityCte ); - targetPathCteColumns.add( 0, getCteTable().getCteColumns().get( 0 ) ); - final CteTable finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); - final QuerySpec finalQuerySpec = new QuerySpec( true ); - final SelectStatement finalQueryStatement = new SelectStatement( finalQuerySpec ); - entityCte = new CteStatement( - finalEntityCteTable, - finalQueryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - } - else { - entityCte = new CteStatement( - entityCteTable, - queryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( entityCte ); - } - - // Add all CTEs - final String baseInsertCte = addDmlCtes( - statement, - entityCte, - targetPathColumns, - assignsId, - sqmConverter, - factory - ); - - final Expression count = createCountStar( factory, sqmConverter ); - domainResults - .add( new BasicResult<>( 0, null, ( (SqlExpressible) count ).getJdbcMapping() ) ); - querySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, count ) ); - querySpec.getFromClause().addRoot( - new CteTableGroup( - new NamedTableReference( - // We want to return the insertion count of the base table - baseInsertCte, - CTE_TABLE_IDENTIFIER - ) - ) - ); - - // Execute the statement - final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( factory, statement ); - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), sqmConverter ), - new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions() - .get( parameter ); - } - }, - executionContext.getSession() - ); - final JdbcOperationQuerySelect select = translator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - return ( (ReactiveSharedSessionContractImplementor) executionContext.getSession() ) - .reactiveAutoFlushIfRequired( select.getAffectedTableNames() ) - .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE.list( - select, + public CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext context) { + return ( (ReactiveSharedSessionContractImplementor) context.getSession() ) + .reactiveAutoFlushIfRequired( getSelect().getAffectedTableNames() ) + .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE + .list( + getSelect(), jdbcParameterBindings, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), + SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ), row -> row[0], - ReactiveListResultsConsumer.UniqueSemantic.NONE + null, + ReactiveListResultsConsumer.UniqueSemantic.NONE, + 1 ) - .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) ); + .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java index d7259ebb1..e0a0e10fd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java @@ -5,16 +5,18 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.util.concurrent.CompletionStage; - +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.internal.InsertHandler; import org.hibernate.query.sqm.mutation.internal.cte.CteInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveCteInsertStrategy extends CteInsertStrategy implements ReactiveSqmMultiTableInsertStrategy { @@ -31,11 +33,16 @@ public ReactiveCteInsertStrategy( } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new ReactiveCteInsertHandler( getEntityCteTable(), sqmInsertStatement, domainParameterXref, getSessionFactory() ) - .reactiveExecute( context ); + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final InsertHandler multiTableHandler = new ReactiveCteInsertHandler( + getEntityCteTable(), + sqmInsertStatement, + domainParameterXref, + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java index 6da230aad..445cfe2f6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java @@ -5,17 +5,21 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.util.concurrent.CompletionStage; - +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.internal.cte.CteMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +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.reactive.query.sqm.mutation.internal.ReactiveHandler; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveCteMutationStrategy extends CteMutationStrategy implements ReactiveSqmMultiTableMutationStrategy { @@ -28,22 +32,53 @@ public ReactiveCteMutationStrategy(EntityPersister rootDescriptor, RuntimeModelC } @Override - public CompletionStage reactiveExecuteUpdate( - SqmUpdateStatement sqmUpdateStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - checkMatch( sqmUpdateStatement ); - return new ReactiveCteUpdateHandler( getIdCteTable(), sqmUpdateStatement, domainParameterXref, this, getSessionFactory() ) - .reactiveExecute( context ); + 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() ); } @Override - public CompletionStage reactiveExecuteDelete( - SqmDeleteStatement sqmDeleteStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - checkMatch( sqmDeleteStatement ); - return new ReactiveCteDeleteHandler( getIdCteTable(), sqmDeleteStatement, domainParameterXref, this, getSessionFactory() ) - .reactiveExecute( context ); + public ReactiveHandler buildHandler(SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { + checkMatch( sqmDelete ); + if ( getRootDescriptor().getSoftDeleteMapping() != null ) { + return new ReactiveCteSoftDeleteHandler( + getIdCteTable(), + sqmDelete, + domainParameterXref, + this, + getSessionFactory(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveCteDeleteHandler( + getIdCteTable(), + sqmDelete, + domainParameterXref, + this, + getSessionFactory(), + context, + firstJdbcParameterBindingsConsumer + ); + } } + + @Override + public MultiTableHandler buildHandler(SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { + checkMatch( sqmUpdate ); + return new ReactiveCteUpdateHandler( + getIdCteTable(), + sqmUpdate, + domainParameterXref, + this, + getSessionFactory(), + context, + firstJdbcParameterBindingsConsumer + ); + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java new file mode 100644 index 000000000..8e0740d14 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java @@ -0,0 +1,43 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.mutation.internal.cte; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +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.internal.cte.CteMutationStrategy; +import org.hibernate.query.sqm.mutation.internal.cte.CteSoftDeleteHandler; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +public class ReactiveCteSoftDeleteHandler extends CteSoftDeleteHandler implements ReactiveAbstractCteMutationHandler { + protected ReactiveCteSoftDeleteHandler( + CteTable cteTable, + SqmDeleteStatement sqmDeleteStatement, + DomainParameterXref domainParameterXref, + CteMutationStrategy strategy, + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( + cteTable, + sqmDeleteStatement, + domainParameterXref, + strategy, + sessionFactory, + context, + firstJdbcParameterBindingsConsumer + ); + } + + @Override + public JdbcOperationQuerySelect getSelect() { + return super.getSelect(); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java index 0730ff33e..c855f35ad 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java @@ -5,59 +5,35 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; - import org.hibernate.engine.spi.SessionFactoryImplementor; +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.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; import org.hibernate.query.sqm.mutation.internal.cte.CteUpdateHandler; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.sql.ast.tree.cte.CteContainer; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see CteUpdateHandler */ public class ReactiveCteUpdateHandler extends CteUpdateHandler implements ReactiveAbstractCteMutationHandler { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public ReactiveCteUpdateHandler( CteTable cteTable, SqmUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory ); - } - - @Override - public void addDmlCtes( - CteContainer statement, - CteStatement idSelectCte, - MultiTableSqmMutationConverter sqmConverter, - Map, List> parameterResolutions, - SessionFactoryImplementor factory) { - super.addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory, context, firstJdbcParameterBindingsConsumer ); } @Override - public Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter) { - return super.createCountStar( factory, sqmConverter ); + public JdbcOperationQuerySelect getSelect() { + return super.getSelect(); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java deleted file mode 100644 index 33acc38f6..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java +++ /dev/null @@ -1,68 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.cte; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.InsertExecutionDelegate; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; -import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTableBasedInsertHandler; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.insert.ConflictClause; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.exec.spi.ExecutionContext; - -/** - * @see InsertExecutionDelegate - */ -public class ReactiveInsertExecutionDelegate extends InsertExecutionDelegate implements ReactiveTableBasedInsertHandler.ReactiveExecutionDelegate { - - public ReactiveInsertExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable entityTable, - AfterUseAction afterUseAction, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup insertingTableGroup, - Map tableReferenceByAlias, - List assignments, - InsertSelectStatement insertStatement, - ConflictClause conflictClause, - JdbcParameter sessionUidParameter, - DomainQueryExecutionContext executionContext) { - super( - sqmConverter, - entityTable, - afterUseAction, - sessionUidAccess, - domainParameterXref, - insertingTableGroup, - tableReferenceByAlias, - assignments, - insertStatement, - conflictClause, - sessionUidParameter, - executionContext - ); - } - - @Override - public CompletionStage reactiveExecute(ExecutionContext executionContext) { - // FIXME: Why is this null? - return null; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java index e8f7bbea6..72359ee58 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java @@ -7,6 +7,8 @@ import java.lang.invoke.MethodHandles; import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.UUID; import java.util.concurrent.CompletionStage; import java.util.function.Function; @@ -16,42 +18,38 @@ import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.dialect.Dialect; 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.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; 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.mutation.internal.MultiTableSqmMutationConverter; +import org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithTemporaryTableHelper; import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; +import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTemporaryTableHelper.TemporaryTableCreationWork; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.spi.NavigablePath; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.QueryLiteral; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.StandardTableGroup; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; 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.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 static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTemporaryTableHelper.cleanTemporaryTableRows; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -64,79 +62,6 @@ public final class ReactiveExecuteWithTemporaryTableHelper { private ReactiveExecuteWithTemporaryTableHelper() { } - public static CompletionStage saveMatchingIdsIntoIdTable( - MultiTableSqmMutationConverter sqmConverter, - Predicate suppliedPredicate, - TemporaryTable idTable, - Function sessionUidAccess, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - - final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup(); - - assert mutatingTableGroup.getModelPart() instanceof EntityMappingType; - final EntityMappingType mutatingEntityDescriptor = (EntityMappingType) mutatingTableGroup.getModelPart(); - - final NamedTableReference idTableReference = new NamedTableReference( - idTable.getTableExpression(), - InsertSelectStatement.DEFAULT_ALIAS - ); - final InsertSelectStatement idTableInsert = new InsertSelectStatement( idTableReference ); - - for ( int i = 0; i < idTable.getColumns().size(); i++ ) { - final TemporaryTableColumn column = idTable.getColumns().get( i ); - idTableInsert.addTargetColumnReferences( - new ColumnReference( - idTableReference, - column.getColumnName(), - // id columns cannot be formulas and cannot have custom read and write expressions - false, - null, - column.getJdbcMapping() - ) - ); - } - - final QuerySpec matchingIdSelection = new QuerySpec( true, 1 ); - idTableInsert.setSourceSelectStatement( matchingIdSelection ); - - matchingIdSelection.getFromClause().addRoot( mutatingTableGroup ); - - mutatingEntityDescriptor.getIdentifierMapping().forEachSelectable( - (selectionIndex, selection) -> { - final TableReference tableReference = mutatingTableGroup.resolveTableReference( - mutatingTableGroup.getNavigablePath(), - selection.getContainingTableExpression() - ); - matchingIdSelection.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - selectionIndex + 1, - sqmConverter.getSqlExpressionResolver().resolveSqlExpression( - tableReference, - selection - ) - ) - ); - } - ); - - 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() - ) - ) - ); - } - - matchingIdSelection.applyPredicate( suppliedPredicate ); - return saveIntoTemporaryTable( idTableInsert, jdbcParameterBindings, executionContext ); - } - public static CompletionStage saveIntoTemporaryTable( InsertSelectStatement temporaryTableInsert, JdbcParameterBindings jdbcParameterBindings, @@ -150,12 +75,14 @@ public static CompletionStage saveIntoTemporaryTable( // 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 - if ( temporaryTableInsert.getSourceSelectStatement() != null + final QueryPart sourceSelectStatement = temporaryTableInsert.getSourceSelectStatement(); + if ( sourceSelectStatement != null && !jdbcEnvironment.getDialect().supportsOuterJoinForUpdate() ) { - temporaryTableInsert.getSourceSelectStatement().visitQuerySpecs( + sourceSelectStatement.visitQuerySpecs( querySpec -> querySpec.getFromClause().visitTableJoins( tableJoin -> { - if ( tableJoin.getJoinType() != SqlAstJoinType.INNER ) { + if ( tableJoin.isInitialized() + && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { lockOptions.setLockMode( lockMode ); } } @@ -166,131 +93,66 @@ public static CompletionStage saveIntoTemporaryTable( .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); lockOptions.setLockMode( lockMode ); + return saveIntoTemporaryTable(jdbcInsert, jdbcParameterBindings, executionContext); + } + + public static CompletionStage saveIntoTemporaryTable( + JdbcOperationQueryMutation jdbcInsert, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { return StandardReactiveJdbcMutationExecutor.INSTANCE .executeReactive( jdbcInsert, jdbcParameterBindings, - executionContext.getSession().getJdbcCoordinator().getStatementPreparer()::prepareStatement, - ReactiveExecuteWithTemporaryTableHelper::doNothing, + sql -> executionContext.getSession().getJdbcCoordinator() + .getStatementPreparer().prepareStatement( sql ), + (integer, preparedStatement) -> {}, executionContext - ); } 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 ); - - final NamedTableReference idTableReference = new NamedTableReference( - idTable.getTableExpression(), - TemporaryTable.DEFAULT_ALIAS, - true - ); - final TableGroup idTableGroup = new StandardTableGroup( - true, - new NavigablePath( idTableReference.getTableExpression() ), - entityDescriptor, - null, - idTableReference, - null, - executionContext.getSession().getFactory() - ); - - querySpec.getFromClause().addRoot( idTableGroup ); - - applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart ); - applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidAccess, executionContext ); - - return querySpec; + return ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( idTable, fkModelPart, sessionUidParameter, entityDescriptor, executionContext ); } - // TODO: I think we can reuse the method in ExecuteWithTemporaryTableHelper - private static void applyIdTableSelections( - QuerySpec querySpec, - TableReference tableReference, - TemporaryTable idTable, - ModelPart fkModelPart) { - if ( fkModelPart == null ) { - final int size = idTable.getEntityDescriptor().getIdentifierMapping().getJdbcTypeCount(); - for ( int i = 0; i < size; i++ ) { - final TemporaryTableColumn temporaryTableColumn = idTable.getColumns().get( i ); - if ( temporaryTableColumn != idTable.getSessionUidColumn() ) { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - i, - new ColumnReference( - tableReference, - temporaryTableColumn.getColumnName(), - false, - null, - temporaryTableColumn.getJdbcMapping() - ) - ) - ); - } - } - } - else { - fkModelPart.forEachSelectable( - (i, selectableMapping) -> querySpec.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( - i, - new ColumnReference( - tableReference, - selectableMapping.getSelectionExpression(), - false, - null, - selectableMapping.getJdbcMapping() - ) - ) - ) - ); - } + @Deprecated(forRemoval = true, since = "3.1") + public static CompletionStage performBeforeTemporaryTableUseActions( + TemporaryTable temporaryTable, + ExecutionContext executionContext) { + return performBeforeTemporaryTableUseActions( + temporaryTable, + executionContext.getSession().getDialect().getTemporaryTableBeforeUseAction(), + executionContext + ).thenCompose( CompletionStages::voidFuture ); } - private static void applyIdTableRestrictions( - QuerySpec querySpec, - TableReference idTableReference, - TemporaryTable idTable, - Function sessionUidAccess, + public static CompletionStage performBeforeTemporaryTableUseActions( + TemporaryTable temporaryTable, + TemporaryTableStrategy temporaryTableStrategy, ExecutionContext executionContext) { - if ( idTable.getSessionUidColumn() != null ) { - querySpec.applyPredicate( - new ComparisonPredicate( - new ColumnReference( - idTableReference, - idTable.getSessionUidColumn().getColumnName(), - false, null, - idTable.getSessionUidColumn().getJdbcMapping() - ), - ComparisonOperator.EQUAL, - new QueryLiteral<>( - UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ), - (BasicValuedMapping) idTable.getSessionUidColumn().getJdbcMapping() - ) - ) - ); - } + return performBeforeTemporaryTableUseActions( temporaryTable, temporaryTableStrategy.getTemporaryTableBeforeUseAction(), executionContext ); } - public static CompletionStage performBeforeTemporaryTableUseActions( + public static CompletionStage performBeforeTemporaryTableUseActions( TemporaryTable temporaryTable, + BeforeUseAction beforeUseAction, ExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); - if ( dialect.getTemporaryTableBeforeUseAction() == BeforeUseAction.CREATE ) { + if ( beforeUseAction == BeforeUseAction.CREATE ) { final TemporaryTableCreationWork temporaryTableCreationWork = new TemporaryTableCreationWork( temporaryTable, factory ); final TempTableDdlTransactionHandling ddlTransactionHandling = dialect.getTemporaryTableDdlTransactionHandling(); if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) { @@ -298,7 +160,7 @@ public static CompletionStage performBeforeTemporaryTableUseActions( } throw LOG.notYetImplemented(); } - return voidFuture(); + return falseFuture(); } public static CompletionStage performAfterTemporaryTableUseActions( @@ -308,14 +170,50 @@ public static CompletionStage performAfterTemporaryTableUseActions( ExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); - switch ( afterUseAction ) { - case CLEAN: - return cleanTemporaryTableRows( temporaryTable, dialect.getTemporaryTableExporter(), sessionUidAccess, executionContext.getSession() ); - case DROP: - return dropAction( temporaryTable, executionContext, factory, dialect ); - default: - return voidFuture(); + return switch ( afterUseAction ) { + case CLEAN -> cleanTemporaryTableRows( temporaryTable, dialect.getTemporaryTableExporter(), sessionUidAccess, executionContext.getSession() ); + case DROP -> dropAction( temporaryTable, executionContext, factory, dialect ); + default -> voidFuture(); + }; + } + + public static CompletionStage loadInsertedRowNumbers( + String sqlSelect, + TemporaryTable temporaryTable, + Function sessionUidAccess, + int rows, + ExecutionContext executionContext) { + final TemporaryTableSessionUidColumn sessionUidColumn = temporaryTable.getSessionUidColumn(); + final SharedSessionContractImplementor session = executionContext.getSession(); + final Object[] parameters = PreparedStatementAdaptor.bind( statement -> { + if ( sessionUidColumn != null ) { + sessionUidColumn.getJdbcMapping().getJdbcValueBinder() + .bind( statement, UUID.fromString( sessionUidAccess.apply( session ) ), 1, session ); + } + } ); + final Integer[] rowNumbers = new Integer[rows]; + return reactiveConnection( session ).selectJdbc( sqlSelect, parameters ) + .thenApply( resultSet -> getRowNumbers( rows, resultSet, rowNumbers ) ); + } + + private static Integer[] getRowNumbers(int rows, ResultSet resultSet, Integer[] rowNumbers) { + int rowIndex = 0; + try { + while ( resultSet.next() ) { + rowNumbers[rowIndex++] = resultSet.getInt( 1 ); + } + return rowNumbers; + } + catch ( IndexOutOfBoundsException e ) { + throw new IllegalArgumentException( "Expected " + rows + " to be inserted but found more", e ); } + catch ( SQLException ex ) { + throw new IllegalStateException( ex ); + } + } + + private static ReactiveConnection reactiveConnection(SharedSessionContractImplementor session) { + return ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); } private static CompletionStage dropAction( @@ -327,10 +225,11 @@ private static CompletionStage dropAction( if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) { return new ReactiveTemporaryTableHelper .TemporaryTableDropWork( temporaryTable, factory ) - .reactiveExecute( ( (ReactiveConnectionSupplier) executionContext.getSession() ).getReactiveConnection() ); + .reactiveExecute( ( (ReactiveConnectionSupplier) executionContext.getSession() ).getReactiveConnection() ) + .thenCompose( CompletionStages::voidFuture ); } - throw LOG.notYetImplemented(); + return failedFuture( LOG.notYetImplemented() ); } private static void doNothing(Integer integer, PreparedStatement preparedStatement) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java index 2f7f7bb37..9ca1dfce8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java @@ -6,16 +6,19 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; 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.mutation.internal.InsertHandler; import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy @@ -48,23 +51,23 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage.thenCompose( v -> new ReactiveTableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final InsertHandler multiTableHandler = new ReactiveTableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, // generally a global temp table should already track a Connection-specific uid, // but just in case a particular env needs it... - ReactiveGlobalTemporaryTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } - @Override public boolean isPrepared() { return prepared; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java index 66b001520..aaad73b10 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java @@ -6,22 +6,28 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +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.temptable.GlobalTemporaryTableMutationStrategy; import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +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.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy */ -public class ReactiveGlobalTemporaryTableMutationStrategy extends GlobalTemporaryTableStrategy +public class ReactiveGlobalTemporaryTableMutationStrategy extends GlobalTemporaryTableMutationStrategy implements ReactiveGlobalTemporaryTableStrategy, ReactiveSqmMultiTableMutationStrategy { private final CompletableFuture tableCreatedStage = new CompletableFuture<>(); @@ -50,35 +56,68 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteUpdate( - SqmUpdateStatement sqmUpdateStatement, + 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) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedUpdateHandler( - sqmUpdateStatement, - domainParameterXref, - getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + return new ReactiveTableBasedUpdateHandler( + sqmUpdate, + 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 + ); } - @Override - public CompletionStage reactiveExecuteDelete( - SqmDeleteStatement sqmDeleteStatement, + public MultiTableHandler buildHandler( + SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedDeleteHandler( - sqmDeleteStatement, - domainParameterXref, - getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactiveGlobalTemporaryTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new ReactiveTableBasedSoftDeleteHandler( + 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 ReactiveTableBasedDeleteHandler( + 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 + ); + } } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java index ae2298ddd..21a9b3ced 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java @@ -5,15 +5,15 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.util.concurrent.CompletionStage; - -import org.hibernate.engine.spi.SharedSessionContractImplementor; +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.internal.temptable.LocalTemporaryTableInsertStrategy; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveLocalTemporaryTableInsertStrategy extends LocalTemporaryTableInsertStrategy implements ReactiveSqmMultiTableInsertStrategy { @@ -23,27 +23,21 @@ public ReactiveLocalTemporaryTableInsertStrategy(LocalTemporaryTableInsertStrate } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new ReactiveTableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = new ReactiveTableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - afterUserAction(), - ReactiveLocalTemporaryTableInsertStrategy::throwUnexpectedCallToSessionUIDError, - getSessionFactory() - ).reactiveExecute( context ); - } - - private static String throwUnexpectedCallToSessionUIDError(SharedSessionContractImplementor session) { - throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } - private AfterUseAction afterUserAction() { - return isDropIdTables() - ? AfterUseAction.DROP - : getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java index fe07871eb..1073f118f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java @@ -5,16 +5,16 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.util.concurrent.CompletionStage; - -import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +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.temptable.LocalTemporaryTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveLocalTemporaryTableMutationStrategy extends LocalTemporaryTableMutationStrategy implements ReactiveSqmMultiTableMutationStrategy { @@ -23,44 +23,59 @@ public ReactiveLocalTemporaryTableMutationStrategy(LocalTemporaryTableMutationSt super( mutationStrategy.getTemporaryTable(), mutationStrategy.getSessionFactory() ); } - private static String throwUnexpectedAccessToSessionUID(SharedSessionContractImplementor session) { - // Should probably go in the LOG - throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); - } - - @Override - public CompletionStage reactiveExecuteUpdate( + public MultiTableHandler buildHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { return new ReactiveTableBasedUpdateHandler( sqmUpdate, domainParameterXref, getTemporaryTable(), - afterUseAction(), - ReactiveLocalTemporaryTableMutationStrategy::throwUnexpectedAccessToSessionUID, - getSessionFactory() - ).reactiveExecute( context ); + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); } - @Override - public CompletionStage reactiveExecuteDelete( + public MultiTableHandler buildHandler( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new ReactiveTableBasedDeleteHandler( - sqmDelete, - domainParameterXref, - getTemporaryTable(), - afterUseAction(), - ReactiveLocalTemporaryTableMutationStrategy::throwUnexpectedAccessToSessionUID, - getSessionFactory() - ).reactiveExecute( context ); - } - - private AfterUseAction afterUseAction() { - return isDropIdTables() - ? AfterUseAction.DROP - : getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new ReactiveTableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveTableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); + } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java index 9a44a44cf..a5b0c36c3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java @@ -10,12 +10,16 @@ import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; 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.mutation.internal.temptable.PersistentTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactivePersistentTableInsertStrategy extends PersistentTableInsertStrategy implements ReactivePersistentTableStrategy, ReactiveSqmMultiTableInsertStrategy { @@ -45,18 +49,19 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage.thenCompose( v -> new ReactiveTableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = new ReactiveTableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java index 57cd14f4b..7b7a641ae 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java @@ -5,18 +5,22 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +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.temptable.PersistentTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; public class ReactivePersistentTableMutationStrategy extends PersistentTableMutationStrategy @@ -46,38 +50,55 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce release( sessionFactory, connectionAccess, tableDroppedStage ); } - @Override - public CompletionStage reactiveExecuteUpdate( - SqmUpdateStatement sqmUpdateStatement, + public MultiTableHandler buildHandler( + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedUpdateHandler( - sqmUpdateStatement, - domainParameterXref, - getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + return new ReactiveTableBasedUpdateHandler( + sqmUpdate, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); } - @Override - public CompletionStage reactiveExecuteDelete( - SqmDeleteStatement sqmDeleteStatement, + public MultiTableHandler buildHandler( + SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedDeleteHandler( - sqmDeleteStatement, - domainParameterXref, - getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new ReactiveTableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveTableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } } - @Override public CompletionStage getDropTableActionStage() { return tableDroppedStage; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java deleted file mode 100644 index 0e1097b63..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java +++ /dev/null @@ -1,640 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.temptable; - -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.hibernate.dialect.temptable.TemporaryTable; -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.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; -import org.hibernate.query.sqm.mutation.internal.temptable.ColumnReferenceCheckingSqlAstWalker; -import org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithoutIdTableHelper; -import org.hibernate.query.sqm.mutation.internal.temptable.RestrictedDeleteExecutionDelegate; -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.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.reactive.util.impl.CompletionStages.Completable; -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.temptable.ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -/** - * The reactive version of {@link RestrictedDeleteExecutionDelegate} - */ -// Basically a copy of RestrictedDeleteExecutionDelegate, we will probably need to refactor this code to avoid -// duplication -public class ReactiveRestrictedDeleteExecutionDelegate - implements ReactiveTableBasedDeleteHandler.ReactiveExecutionDelegate { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - private final EntityMappingType entityDescriptor; - private final TemporaryTable idTable; - private final AfterUseAction afterUseAction; - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; - private final SessionFactoryImplementor sessionFactory; - - private final Function sessionUidAccess; - private final MultiTableSqmMutationConverter converter; - - public ReactiveRestrictedDeleteExecutionDelegate( - EntityMappingType entityDescriptor, - TemporaryTable idTable, - AfterUseAction afterUseAction, - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - Function sessionUidAccess, - QueryOptions queryOptions, - LoadQueryInfluencers loadQueryInfluencers, - QueryParameterBindings queryParameterBindings, - SessionFactoryImplementor sessionFactory) { - this.entityDescriptor = entityDescriptor; - this.idTable = idTable; - this.afterUseAction = afterUseAction; - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; - this.sessionUidAccess = sessionUidAccess; - this.sessionFactory = sessionFactory; - this.converter = new MultiTableSqmMutationConverter( - entityDescriptor, - sqmDelete, - sqmDelete.getTarget(), - domainParameterXref, - queryOptions, - loadQueryInfluencers, - queryParameterBindings, - sessionFactory.getSqlTranslationEngine() - ); - } - - @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final EntityPersister entityDescriptor = sessionFactory.getRuntimeMetamodels() - .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 the referenced columns (if any at all) are from the root table - // we can perform all 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, - executionContext.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( - executionContext ); - - if ( needsIdTable ) { - return executeWithIdTable( - predicateCollector.getPredicate(), - converter.getJdbcParamsBySqmParam(), - converter.getSqmParameterMappingModelExpressibleResolutions(), - executionContextAdapter - ); - } - else { - return executeWithoutIdTable( - predicateCollector.getPredicate(), - deletingTableGroup, - converter.getJdbcParamsBySqmParam(), - converter.getSqmParameterMappingModelExpressibleResolutions(), - converter.getSqlExpressionResolver(), - executionContextAdapter - ); - } - } - - private CompletionStage executeWithoutIdTable( - Predicate suppliedPredicate, - TableGroup tableGroup, - Map, List>> restrictionSqmParameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, - SqlExpressionResolver sqlExpressionResolver, - ExecutionContext executionContext) { - assert entityDescriptor == entityDescriptor.getRootEntityDescriptor(); - - final EntityPersister rootEntityPersister = entityDescriptor.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, - sessionFactory - ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - SqmUtil.generateJdbcParamsXref( - domainParameterXref, - () -> restrictionSqmParameterResolutions - ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - executionContext.getSession() - ); - - - CompletionStage cleanUpCollectionTablesStage = ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - entityDescriptor, - (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, - sessionFactory - ); - } - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - sessionFactory - ), - idSelectFkSubQuery, - false - ); - - }, - jdbcParameterBindings, - executionContext - ); - - final CompletionStage[] deleteFromNonRootStages = new CompletionStage[] { voidFuture() }; - if ( rootTableReference instanceof UnionTableReference ) { - final MutableInteger rows = new MutableInteger(); - return cleanUpCollectionTablesStage - .thenCompose( v -> visitUnionTableReferences( - suppliedPredicate, - tableGroup, - sqlExpressionResolver, - executionContext, - matchingIdSubQuerySpec, - jdbcParameterBindings, - deleteFromNonRootStages, - rows - ) ) - .thenApply( o -> rows.get() ); - } - else { - entityDescriptor.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 - idMatchingSubQuerySpec = suppliedPredicate == null ? null : matchingIdSubQuerySpec; - CompletableFuture future = new CompletableFuture<>(); - deleteFromNonRootStages[0] = deleteFromNonRootStages[0] - .thenCompose( v -> future ); - try { - deleteFromNonRootTableWithoutIdTable( - tableReference, - tableKeyColumnVisitationSupplier, - sqlExpressionResolver, - tableGroup, - idMatchingSubQuerySpec, - jdbcParameterBindings, - executionContext - ) - .thenCompose( CompletionStages::voidFuture ) - .whenComplete( (unused, throwable) -> { - if ( throwable == null ) { - future.complete( unused ); - } - else { - future.completeExceptionally( throwable ); - } - } ); - } - catch (Throwable t) { - future.completeExceptionally( t ); - } - } - } - ); - - return deleteFromNonRootStages[0] - .thenCompose( v -> deleteFromRootTableWithoutIdTable( - rootTableReference, - suppliedPredicate, - jdbcParameterBindings, - executionContext - ) ); - } - } - - private CompletionStage visitUnionTableReferences( - Predicate suppliedPredicate, - TableGroup tableGroup, - SqlExpressionResolver sqlExpressionResolver, - ExecutionContext executionContext, - QuerySpec matchingIdSubQuerySpec, - JdbcParameterBindings jdbcParameterBindings, - CompletionStage[] deleteFromNonRootStages, - MutableInteger rows) { - entityDescriptor.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 - idMatchingSubQuerySpec = suppliedPredicate == null ? null : matchingIdSubQuerySpec; - CompletableFuture future = new CompletableFuture<>(); - deleteFromNonRootStages[0] = deleteFromNonRootStages[0] - .thenCompose( v -> future ); - deleteFromNonRootTableWithoutIdTable( - tableReference, - tableKeyColumnVisitationSupplier, - sqlExpressionResolver, - tableGroup, - idMatchingSubQuerySpec, - jdbcParameterBindings, - executionContext - ) - .thenAccept( rows::plus ) - .whenComplete( (unused, throwable) -> { - if ( throwable == null ) { - future.complete( unused ); - } - else { - future.completeExceptionally( throwable ); - } - } ); - } - ); - return deleteFromNonRootStages[0]; - } - - private CompletionStage deleteFromRootTableWithoutIdTable( - NamedTableReference rootTableReference, - Predicate predicate, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - return executeSqlDelete( - new DeleteStatement( rootTableReference, predicate ), - jdbcParameterBindings, - executionContext - ); - } - - private CompletionStage deleteFromNonRootTableWithoutIdTable( - NamedTableReference targetTableReference, - Supplier> tableKeyColumnVisitationSupplier, - SqlExpressionResolver sqlExpressionResolver, - TableGroup rootTableGroup, - QuerySpec matchingIdSubQuerySpec, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - assert targetTableReference != null; - LOG.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 = deletingTableColumnRefs.size() == 1 - ? deletingTableColumnRefs.get( 0 ) - : new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() ); - - tableDeletePredicate = new InSubQueryPredicate( - deletingTableColumnRefsExpression, - matchingIdSubQuerySpec, - false - ); - } - - final DeleteStatement sqlAstDelete = new DeleteStatement( deleteTableReference, tableDeletePredicate ); - return executeSqlDelete( - sqlAstDelete, - jdbcParameterBindings, - executionContext - ).thenApply( rows -> { - LOG.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows ); - return rows; - } ); - } - - private static CompletionStage 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 StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcDelete, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContext - ); - } - - private CompletionStage executeWithIdTable( - Predicate predicate, - Map, List>> restrictionSqmParameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, - ExecutionContext executionContext) { - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - SqmUtil.generateJdbcParamsXref( - domainParameterXref, - () -> restrictionSqmParameterResolutions - ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - executionContext.getSession() - ); - - return ReactiveExecuteWithTemporaryTableHelper - .performBeforeTemporaryTableUseActions( idTable, executionContext ) - .thenCompose( v -> executeUsingIdTable( predicate, executionContext, jdbcParameterBindings ) - .handle( CompletionStages::handle ) - .thenCompose( resultHandler -> ReactiveExecuteWithTemporaryTableHelper - .performAfterTemporaryTableUseActions( - idTable, - sessionUidAccess, - afterUseAction, - executionContext - ) - .thenCompose( resultHandler::getResultAsCompletionStage ) - ) - ); - } - - private CompletionStage executeUsingIdTable( - Predicate predicate, - ExecutionContext executionContext, - JdbcParameterBindings jdbcParameterBindings) { - return ReactiveExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable( - converter, - predicate, - idTable, - sessionUidAccess, - jdbcParameterBindings, - executionContext ) - .thenCompose( rows -> { - final QuerySpec idTableIdentifierSubQuery = createIdTableSelectQuerySpec( - idTable, - sessionUidAccess, - entityDescriptor, - executionContext - ); - - return ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - entityDescriptor, - (tableReference, attributeMapping) -> { - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idTableFkSubQuery = fkDescriptor.getTargetPart() - .isEntityIdentifierMapping() - ? idTableIdentifierSubQuery - : createIdTableSelectQuerySpec( idTable, fkDescriptor.getTargetPart(), sessionUidAccess, entityDescriptor, executionContext ); - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - sessionFactory - ), - idTableFkSubQuery, - false - ); - - }, - JdbcParameterBindings.NO_BINDINGS, - executionContext - ).thenCompose( unused -> visitConstraintOrderedTables( idTableIdentifierSubQuery, executionContext ) - .thenApply( v -> rows ) ); - } ); - } - - private CompletionStage visitConstraintOrderedTables( - QuerySpec idTableIdentifierSubQuery, - ExecutionContext executionContext) { - final Completable completable = new Completable<>(); - entityDescriptor - .visitConstraintOrderedTables( (tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable( - tableExpression, - tableKeyColumnVisitationSupplier, - idTableIdentifierSubQuery, - executionContext - ) - .handle( completable::complete ) - ); - return completable.getStage().thenCompose( CompletionStages::voidFuture ); - } - - private CompletionStage deleteFromTableUsingIdTable( - String tableExpression, - Supplier> tableKeyColumnVisitationSupplier, - QuerySpec idTableSubQuery, - ExecutionContext executionContext) { - LOG.tracef( "deleteFromTableUsingIdTable - %s", tableExpression ); - - final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor ); - 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 - ); - - return executeSqlDelete( - new DeleteStatement( targetTable, predicate ), - JdbcParameterBindings.NO_BINDINGS, - executionContext - ); - } - -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java index bb9ceff9c..c27fbf502 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java @@ -6,67 +6,147 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.lang.invoke.MethodHandles; +import java.util.UUID; import java.util.concurrent.CompletionStage; import java.util.function.Function; import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.persister.entity.UnionSubclassEntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; +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.mutation.internal.temptable.TableBasedDeleteHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; public class ReactiveTableBasedDeleteHandler extends TableBasedDeleteHandler implements ReactiveAbstractMutationHandler { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public interface ReactiveExecutionDelegate extends TableBasedDeleteHandler.ExecutionDelegate { - - @Override - default int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext); - } - public ReactiveTableBasedDeleteHandler( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmDeleteStatement, domainParameterXref, idTable, afterUseAction, sessionUidAccess, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( + sqmDeleteStatement, + domainParameterXref, + idTable, + temporaryTableStrategy, + forceDropAfterUse, + sessionUidAccess, + context, + firstJdbcParameterBindingsConsumer + ); } @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { + public CompletionStage reactiveExecute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Starting multi-table delete execution - %s", - getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() + getSqmStatement().getTarget().getModel().getName() + ); + } + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + final CacheableSqmInterpretation idTableInsert = getIdTableInsert(); + final TemporaryTable idTable = getIdTable(); + final StandardReactiveJdbcMutationExecutor jdbcMutationExecutor = StandardReactiveJdbcMutationExecutor.INSTANCE; + if ( idTableInsert != null ) { + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + idTable, + getTemporaryTableStrategy(), + executionContext + ).thenCompose( unused -> + ReactiveExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( idTableInsert.jdbcOperation(), jdbcParameterBindings, executionContext ) + .thenCompose( rows -> executeDelete( jdbcParameterBindings, rows, executionContext, jdbcMutationExecutor ) ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( idTable, getSessionUidAccess(), getAfterUseAction(), executionContext ) + .thenCompose( v -> handler.getResultAsCompletionStage() ) ) + ); + } + else { + return loop(getCollectionTableDeletes() ,delete -> + reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + ).thenCompose( v -> { + final int[] rows = { 0 }; + return deleteRows( jdbcParameterBindings, jdbcMutationExecutor, executionContext, rows ) + .thenApply( vv -> rows[0] ); + } ); + } + } + + private CompletionStage deleteRows(JdbcParameterBindings jdbcParameterBindings, StandardReactiveJdbcMutationExecutor jdbcMutationExecutor, SqmJdbcExecutionContextAdapter executionContext, int[] rows) { + if ( getEntityDescriptor() instanceof UnionSubclassEntityPersister ) { + return CompletionStages + .loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + .thenApply( tot -> rows[0] += tot ) + ); + } + else { + return CompletionStages + .loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + .thenApply( tot -> rows[0] = tot ) + ); + } + } + + private CompletionStage executeDelete( + JdbcParameterBindings jdbcParameterBindings, + Integer rows, + SqmJdbcExecutionContextAdapter executionContext, + StandardReactiveJdbcMutationExecutor jdbcMutationExecutor) { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( executionContext.getSession() ) ) + ) ); } - return resolveDelegate( executionContext ).reactiveExecute( executionContext ); + return loop( getCollectionTableDeletes(), delete -> + reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + ).thenApply( v -> rows ); } - protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - return new ReactiveRestrictedDeleteExecutionDelegate( - getEntityDescriptor(), - getIdTable(), - getAfterUseAction(), - getSqmDeleteOrUpdateStatement(), - getDomainParameterXref(), - getSessionUidAccess(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - getSessionFactory() + private static CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + JdbcOperationQueryMutation delete, + StandardReactiveJdbcMutationExecutor jdbcMutationExecutor, + SqmJdbcExecutionContextAdapter executionContext) { + return jdbcMutationExecutor.executeReactive( + delete, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java index d697758ed..194b8b598 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java @@ -5,105 +5,398 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; - import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.Generator; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.id.insert.Binder; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.JdbcMapping; +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; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedInsertHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.reactive.id.insert.ReactiveInsertGeneratedIdentifierDelegate; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; -import org.hibernate.reactive.query.sqm.mutation.internal.cte.ReactiveInsertExecutionDelegate; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; +import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.insert.ConflictClause; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBinder; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.type.descriptor.ValueBinder; -public class ReactiveTableBasedInsertHandler extends TableBasedInsertHandler implements ReactiveHandler { +import java.lang.invoke.MethodHandles; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.stream.IntStream; - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); +import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; - public interface ReactiveExecutionDelegate extends ExecutionDelegate { - @Override - default int execute(ExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } +public class ReactiveTableBasedInsertHandler extends TableBasedInsertHandler implements ReactiveHandler { - CompletionStage reactiveExecute(ExecutionContext executionContext); - } + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); public ReactiveTableBasedInsertHandler( SqmInsertStatement sqmInsert, DomainParameterXref domainParameterXref, TemporaryTable entityTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmInsert, domainParameterXref, entityTable, afterUseAction, sessionUidAccess, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmInsert, domainParameterXref, entityTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, context, firstJdbcParameterBindingsConsumer ); } @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { + public CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext context) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Starting multi-table insert execution - %s", - getSqmInsertStatement().getTarget().getModel().getName() + getSqmStatement().getTarget().getModel().getName() ); } - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter - .omittingLockingAndPaging( executionContext ); - return resolveDelegate( executionContext ) - .reactiveExecute( executionContextAdapter ); + 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 + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + getEntityTable(), + getTemporaryTableStrategy(), + executionContext + ).thenCompose( createdTable -> + ReactiveExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + getTemporaryTableInsert().jdbcOperation(), + jdbcParameterBindings, + executionContext + ).thenCompose( rows -> { + if ( rows != 0 ) { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( executionContext.getSession() ) ) + ) + ); + } + return insertRootTable( rows, createdTable, sessionUidBindings, executionContext ) + .thenCompose( insertedRows -> CompletionStages + .loop( + getNonRootTableInserts(), nonRootTableInsert -> + insertTable( nonRootTableInsert, sessionUidBindings, executionContext ) + ).thenApply( v -> insertedRows ) + ); + } + return CompletionStages.completedFuture( rows ); + } ) ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( + getEntityTable(), + getSessionUidAccess(), + getAfterUseAction(), + executionContext + ) + .thenCompose( v -> handler.getResultAsCompletionStage() ) + ); } - @Override - protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - return (ReactiveExecutionDelegate) super.resolveDelegate( executionContext ); + private CompletionStage insertTable( + JdbcOperationQueryMutation nonRootTableInsert, + JdbcParameterBindings sessionUidBindings, + ExecutionContext executionContext) { + return StandardReactiveJdbcMutationExecutor.INSTANCE + .executeReactive( + nonRootTableInsert, + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ).thenCompose( unused -> CompletionStages.voidFuture() ); } - @Override - protected ExecutionDelegate buildExecutionDelegate( - SqmInsertStatement sqmInsert, - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable entityTable, - AfterUseAction afterUseAction, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup insertingTableGroup, - Map tableReferenceByAlias, - List assignments, - InsertSelectStatement insertStatement, - ConflictClause conflictClause, - JdbcParameter sessionUidParameter, - DomainQueryExecutionContext executionContext) { - return new ReactiveInsertExecutionDelegate( - sqmConverter, - entityTable, - afterUseAction, - sessionUidAccess, - domainParameterXref, - insertingTableGroup, - tableReferenceByAlias, - assignments, - insertStatement, - conflictClause, - sessionUidParameter, - executionContext - ); + private CompletionStage 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 RootTableInserter rootTableInserter = getRootTableInserter(); + + if ( rootTableInserter.temporaryTableIdentitySelect() != null ) { + return StandardReactiveSelectExecutor.INSTANCE.list( + rootTableInserter.temporaryTableIdentitySelect(), + sessionUidBindings, + executionContext, + null, + null, + ReactiveListResultsConsumer.UniqueSemantic.NONE, + rows + ).thenApply( list -> { + Map entityTableToRootIdentity = new LinkedHashMap<>( list.size() ); + for ( Object o : list ) { + entityTableToRootIdentity.put( o, null ); + } + return entityTableToRootIdentity; + } ).thenCompose( entityTableToRootIdentity -> insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session + ) ); + } + else { + final Map entityTableToRootIdentity = null; + + if ( rootTableInserter.temporaryTableIdUpdate() != null ) { + final BeforeExecutionGenerator beforeExecutionGenerator = (BeforeExecutionGenerator) generator; + final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 3 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + updateBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().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; + + if ( !rowNumberStartsAtOne ) { + return ReactiveExecuteWithTemporaryTableHelper.loadInsertedRowNumbers( + rootTableInserter.temporaryTableRowNumberSelectSql(), + getEntityTable(), + getSessionUidAccess(), + rows, + executionContext + ).thenCompose( rowNumbers -> + forEachRow( + rowNumbers, + executionContext, + updateBindings, + rowNumber, + rootIdentity, + basicIdentifierMapping, + beforeExecutionGenerator, + session, + rootTableInserter + ).thenCompose( v -> insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session ) + ) + ); + } + else { + final Integer[] rowNumbers = IntStream.range( 1, rows + 1 ).boxed() + .toArray(Integer[]::new); + return forEachRow( + rowNumbers, + executionContext, + updateBindings, + rowNumber, + rootIdentity, + basicIdentifierMapping, + beforeExecutionGenerator, + session, + rootTableInserter + ).thenCompose( v -> insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session) + ); + } + } + return insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session + ); + } + } + + private static CompletionStage forEachRow( + Integer[] rowNumbers, + SqmJdbcExecutionContextAdapter executionContext, + JdbcParameterBindings updateBindings, + JdbcParameter rowNumber, + JdbcParameter rootIdentity, + BasicEntityIdentifierMapping basicIdentifierMapping, + BeforeExecutionGenerator beforeExecutionGenerator, + SharedSessionContractImplementor session, + RootTableInserter rootTableInserter) { + return loop( rowNumbers, rowNumberValue -> { + updateBindings.addBinding( + rowNumber, + new JdbcParameterBindingImpl( + rowNumber.getExpressionType().getSingleJdbcMapping(), + rowNumberValue + ) + ); + updateBindings.addBinding( + rootIdentity, + new JdbcParameterBindingImpl( + basicIdentifierMapping.getJdbcMapping(), + beforeExecutionGenerator.generate( session, null, null, INSERT ) + ) + ); + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + rootTableInserter.temporaryTableIdUpdate(), + updateBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ).thenApply( updateCount -> { + assert updateCount == 1; + return updateCount; + } ); + } ); } + + private CompletionStage insertRootTable( + JdbcParameterBindings sessionUidBindings, + SqmJdbcExecutionContextAdapter executionContext, + RootTableInserter rootTableInserter, + EntityPersister entityPersister, + EntityIdentifierMapping identifierMapping, + Map entityTableToRootIdentity, + SharedSessionContractImplementor session ) { + 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 ReactiveInsertGeneratedIdentifierDelegate identifierDelegate = (ReactiveInsertGeneratedIdentifierDelegate) insertDelegate; + final ValueBinder jdbcValueBinder = basicIdentifierMapping.getJdbcMapping().getJdbcValueBinder(); + return loop(entityTableToRootIdentity.entrySet() , entry -> + identifierDelegate.reactivePerformInsertReturning( + rootTableInserter.rootTableInsertWithReturningSql(), + session, + new Binder() { + @Override + public void bindValues(PreparedStatement ps) throws SQLException { + jdbcValueBinder.bind( ps, entry.getKey(), 1, session ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidParameter.getParameterBinder().bindParameterValue( + ps, + 2, + sessionUidBindings, + executionContext + ); + } + } + + @Override + public Object getEntity() { + return null; + } + } + ).thenAccept( generatedValues -> { + entry.setValue( generatedValues.getGeneratedValue( identifierMapping ) ); + } ) + ).thenCompose( unused -> { + 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 ); + return loop(entityTableToRootIdentity.entrySet(), entry -> { + JdbcMapping jdbcMapping = basicIdentifierMapping.getJdbcMapping(); + updateBindings.addBinding( + entityIdentity, + new JdbcParameterBindingImpl( jdbcMapping, entry.getKey() ) + ); + updateBindings.addBinding( + rootIdentity, + new JdbcParameterBindingImpl( jdbcMapping, entry.getValue() ) + ); + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + rootTableInserter.temporaryTableIdentityUpdate(), + updateBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + }).thenApply( v -> entityTableToRootIdentity.size() ); + }); + } + else { + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + rootTableInserter.rootTableInsert(), + sessionUidBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java new file mode 100644 index 000000000..e0edb82e2 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java @@ -0,0 +1,129 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.mutation.internal.temptable; + +import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.query.spi.DomainQueryExecutionContext; +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.mutation.internal.temptable.TableBasedSoftDeleteHandler; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import java.lang.invoke.MethodHandles; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +public class ReactiveTableBasedSoftDeleteHandler extends TableBasedSoftDeleteHandler implements ReactiveHandler { + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + public ReactiveTableBasedSoftDeleteHandler( + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + TemporaryTable idTable, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, + Function sessionUidAccess, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( + sqmDelete, + domainParameterXref, + idTable, + temporaryTableStrategy, + forceDropAfterUse, + sessionUidAccess, + context, + firstJdbcParameterBindingsConsumer + ); + } + + @Override + public CompletionStage reactiveExecute( + 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 ); + StandardReactiveJdbcMutationExecutor jdbcMutationExecutor = StandardReactiveJdbcMutationExecutor.INSTANCE; + + final CacheableSqmInterpretation idTableInsert = getIdTableInsert(); + if ( idTableInsert != null ) { + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + getIdTable(), + getTemporaryTableStrategy(), + executionContext + ).thenCompose( unused -> + ReactiveExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + idTableInsert.jdbcOperation(), + jdbcParameterBindings, + executionContext + ).thenCompose( rows -> { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( executionContext.getSession() ) ) + ) + ); + } + return jdbcMutationExecutor.executeReactive( + getSoftDelete(), + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ).thenApply( u -> rows ); + } ) + .handle( CompletionStages::handle) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( + getIdTable(), + getSessionUidAccess(), + getAfterUseAction(), + executionContext) + .thenCompose( v -> handler.getResultAsCompletionStage() ) ) + ); + } + else { + return jdbcMutationExecutor.executeReactive( + getSoftDelete(), + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java index 7379f962e..d3ba6f6f3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java @@ -6,99 +6,117 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.function.Function; +import org.hibernate.AssertionFailure; import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedUpdateHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; 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.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; public class ReactiveTableBasedUpdateHandler extends TableBasedUpdateHandler implements ReactiveAbstractMutationHandler { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public interface ReactiveExecutionDelegate extends TableBasedUpdateHandler.ExecutionDelegate { - @Override - default int execute(ExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - CompletionStage reactiveExecute(ExecutionContext executionContext); - } - public ReactiveTableBasedUpdateHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmUpdate, domainParameterXref, idTable, afterUseAction, sessionUidAccess, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmUpdate, domainParameterXref, idTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, context, firstJdbcParameterBindingsConsumer ); } @Override - public int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { + public CompletionStage reactiveExecute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Starting multi-table update execution - %s", - getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() + getSqmStatement().getTarget().getModel().getName() ); } - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); - return resolveDelegate( executionContext ).reactiveExecute( executionContextAdapter ); + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + getIdTable(), + getTemporaryTableStrategy(), + executionContext + ).thenCompose( unused -> ReactiveExecuteWithTemporaryTableHelper + .saveIntoTemporaryTable( + getMatchingIdsIntoIdTableInsert().jdbcOperation(), + jdbcParameterBindings, + executionContext + ).thenCompose( rows -> loop(getTableUpdaters(), tableUpdater -> + updateTable( tableUpdater, rows, jdbcParameterBindings, executionContext ) ) + .thenApply(v -> rows)) + .handle( CompletionStages::handle) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( getIdTable(), getSessionUidAccess(), getAfterUseAction(), executionContext ) + .thenCompose( v -> handler.getResultAsCompletionStage() )) + ); } - @Override - protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - return (ReactiveExecutionDelegate) super.resolveDelegate( executionContext ); + private CompletionStage updateTable( + TableUpdater tableUpdater, + int expectedUpdateCount, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + if ( tableUpdater == null ) { + // no assignments for this table - skip it + return voidFuture(); + } + return executeMutation( tableUpdater.jdbcUpdate(), jdbcParameterBindings, executionContext ) + .thenCompose( updateCount -> { + // We are done when the update count matches + if ( updateCount == expectedUpdateCount ) { + return voidFuture(); + } + + // If the table is optional, execute an insert + if ( tableUpdater.jdbcInsert() != null ) { + return executeMutation( tableUpdater.jdbcInsert(), jdbcParameterBindings, executionContext ) + .thenAccept( insertCount -> { + if(insertCount + updateCount != expectedUpdateCount){ + throw new AssertionFailure( "insertCount + updateCount != expectedUpdateCount"); + } + } ); + } + return voidFuture(); + } ); } - @Override - protected ReactiveUpdateExecutionDelegate buildExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable idTable, - AfterUseAction afterUseAction, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup updatingTableGroup, - Map tableReferenceByAlias, - List assignments, - Predicate suppliedPredicate, - DomainQueryExecutionContext executionContext) { - return new ReactiveUpdateExecutionDelegate( - sqmConverter, - idTable, - afterUseAction, - sessionUidAccess, - domainParameterXref, - updatingTableGroup, - tableReferenceByAlias, - assignments, - suppliedPredicate, + private CompletionStage executeMutation(JdbcOperationQueryMutation jdbcUpdate, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + jdbcUpdate, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, executionContext ); } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java index 126c168f8..9dde81ab7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java @@ -23,7 +23,7 @@ import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.util.impl.CompletionStages; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; /** * @see org.hibernate.dialect.temptable.TemporaryTableHelper @@ -38,13 +38,12 @@ public class ReactiveTemporaryTableHelper { * @see org.hibernate.jdbc.Work */ public interface ReactiveWork { - CompletionStage reactiveExecute(ReactiveConnection connection); + CompletionStage reactiveExecute(ReactiveConnection connection); } public static class TemporaryTableCreationWork implements ReactiveWork { private final TemporaryTable temporaryTable; private final TemporaryTableExporter exporter; - private final SessionFactoryImplementor sessionFactory; public TemporaryTableCreationWork( TemporaryTable temporaryTable, @@ -62,23 +61,25 @@ public TemporaryTableCreationWork( SessionFactoryImplementor sessionFactory) { this.temporaryTable = temporaryTable; this.exporter = exporter; - this.sessionFactory = sessionFactory; } @Override - public CompletionStage reactiveExecute(ReactiveConnection connection) { + public CompletionStage reactiveExecute(ReactiveConnection connection) { try { final String creationCommand = exporter.getSqlCreateCommand( temporaryTable ); return connection.executeUnprepared( creationCommand ) .handle( (integer, throwable) -> { + if ( throwable == null ) { + return true; + } logException( "create", creationCommand, temporaryTable, throwable ); - return null; + return false; } ); } catch (Exception e) { logException( "create", null, temporaryTable, e ); - return voidFuture(); + return falseFuture(); } } } @@ -111,19 +112,22 @@ public TemporaryTableDropWork( } @Override - public CompletionStage reactiveExecute(ReactiveConnection connection) { + public CompletionStage reactiveExecute(ReactiveConnection connection) { try { final String dropCommand = exporter.getSqlDropCommand( temporaryTable ); return connection.update( dropCommand ) .handle( (integer, throwable) -> { + if ( throwable == null ) { + return true; + } logException( "drop", dropCommand, temporaryTable, throwable ); - return null; + return false; } ); } catch (Exception e) { logException( "drop", null, temporaryTable, e ); - return voidFuture(); + return falseFuture(); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java deleted file mode 100644 index 927105dfa..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java +++ /dev/null @@ -1,292 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.temptable; - -import java.lang.invoke.MethodHandles; -import java.sql.PreparedStatement; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.SelectableConsumer; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.UpdateExecutionDelegate; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -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.TableReference; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -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.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.results.internal.SqlSelectionImpl; - -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions; -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions; -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -public class ReactiveUpdateExecutionDelegate extends UpdateExecutionDelegate implements ReactiveTableBasedUpdateHandler.ReactiveExecutionDelegate { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - public ReactiveUpdateExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable idTable, - AfterUseAction afterUseAction, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup updatingTableGroup, - Map tableReferenceByAlias, - List assignments, - Predicate suppliedPredicate, - DomainQueryExecutionContext executionContext) { - super( - sqmConverter, - idTable, - afterUseAction, - sessionUidAccess, - domainParameterXref, - updatingTableGroup, - tableReferenceByAlias, - assignments, - suppliedPredicate, - executionContext - ); - } - - private static void doNothing(Integer integer, PreparedStatement preparedStatement) { - } - - @Override - public int execute(ExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - @Override - public CompletionStage reactiveExecute(ExecutionContext executionContext) { - return performBeforeTemporaryTableUseActions( - getIdTable(), - executionContext - ) - .thenCompose( v -> saveMatchingIdsIntoIdTable( - getSqmConverter(), - getSuppliedPredicate(), - getIdTable(), - getSessionUidAccess(), - getJdbcParameterBindings(), - executionContext - ) ) - .thenCompose( rows -> { - final QuerySpec idTableSubQuery = createIdTableSelectQuerySpec( - getIdTable(), - getSessionUidAccess(), - getEntityDescriptor(), - executionContext - ); - - final CompletionStage[] resultStage = new CompletionStage[] { voidFuture() }; - getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> resultStage[0] = resultStage[0].thenCompose( - v -> reactiveUpdateTable( - tableExpression, - tableKeyColumnVisitationSupplier, - rows, - idTableSubQuery, - executionContext - ) ) - ); - return resultStage[0].thenApply( v -> rows ); - }) - .handle( CompletionStages::handle ) - .thenCompose( handler -> performAfterTemporaryTableUseActions( - getIdTable(), - getSessionUidAccess(), - getAfterUseAction(), - executionContext - ) - .thenCompose( handler::getResultAsCompletionStage ) - ); - } - - private CompletionStage reactiveUpdateTable( - String tableExpression, - Supplier> tableKeyColumnVisitationSupplier, - int expectedUpdateCount, - QuerySpec idTableSubQuery, - ExecutionContext executionContext) { - - // update `updatingTableReference` - // set ... - // where `keyExpression` in ( `idTableSubQuery` ) - - final TableReference updatingTableReference = getUpdatingTableGroup().getTableReference( - getUpdatingTableGroup().getNavigablePath(), - tableExpression, - true - ); - - final List assignments = getAssignmentsByTable().get( updatingTableReference ); - if ( assignments == null || assignments.isEmpty() ) { - // no assignments for this table - skip it - return voidFuture(); - } - - final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); - final JdbcServices jdbcServices = getSessionFactory().getJdbcServices(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcServices.getJdbcEnvironment().getSqlAstTranslatorFactory(); - - final Expression keyExpression = resolveMutatingTableKeyExpression( tableExpression, tableKeyColumnVisitationSupplier ); - - return executeUpdate( idTableSubQuery, executionContext, assignments, dmlTableReference, sqlAstTranslatorFactory, keyExpression ) - .thenCompose( updateCount -> { - // We are done when the update count matches - if ( updateCount == expectedUpdateCount ) { - return voidFuture(); - } - // If the table is optional, execute an insert - if ( isTableOptional( tableExpression ) ) { - return executeInsert( - tableExpression, - dmlTableReference, - keyExpression, - tableKeyColumnVisitationSupplier, - idTableSubQuery, - assignments, - sqlAstTranslatorFactory, - executionContext - ) - .thenAccept( insertCount -> { - assert insertCount + updateCount == expectedUpdateCount; - } ); - } - return voidFuture(); - } ); - } - - - private CompletionStage executeUpdate(QuerySpec idTableSubQuery, ExecutionContext executionContext, List assignments, NamedTableReference dmlTableReference, SqlAstTranslatorFactory sqlAstTranslatorFactory, Expression keyExpression) { - final UpdateStatement sqlAst = new UpdateStatement( - dmlTableReference, - assignments, - new InSubQueryPredicate( keyExpression, idTableSubQuery, false ) - ); - - final JdbcOperationQueryMutation jdbcUpdate = sqlAstTranslatorFactory - .buildMutationTranslator( getSessionFactory(), sqlAst ) - .translate( getJdbcParameterBindings(), executionContext.getQueryOptions() ); - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcUpdate, - getJdbcParameterBindings(), - executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - ::prepareStatement, - ReactiveUpdateExecutionDelegate::doNothing, - executionContext - ); - } - - private CompletionStage executeInsert( - String targetTableExpression, - NamedTableReference targetTableReference, - Expression targetTableKeyExpression, - Supplier> tableKeyColumnVisitationSupplier, - QuerySpec idTableSubQuery, - List assignments, - SqlAstTranslatorFactory sqlAstTranslatorFactory, - 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, - getSessionFactory().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( getSessionFactory(), insertSqlAst ) - .translate( getJdbcParameterBindings(), executionContext.getQueryOptions() ); - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcInsert, - getJdbcParameterBindings(), - executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - ::prepareStatement, - ReactiveUpdateExecutionDelegate::doNothing, - executionContext - ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java index a4a622537..f3a3b9a42 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java @@ -8,7 +8,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; /** @@ -16,8 +15,6 @@ */ public interface ReactiveAbstractMutationHandler extends ReactiveHandler { - SqmDeleteOrUpdateStatement getSqmDeleteOrUpdateStatement(); - EntityMappingType getEntityDescriptor(); SessionFactoryImplementor getSessionFactory(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java index dc52d893e..93399180d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java @@ -10,10 +10,12 @@ import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +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.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; public interface ReactiveSqmMultiTableInsertStrategy extends SqmMultiTableInsertStrategy { @@ -27,8 +29,18 @@ default int executeInsert( throw LOG.nonReactiveMethodCall( "reactiveExecuteInsert" ); } - CompletionStage reactiveExecuteInsert( + /** + * 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 = "3.1") + default CompletionStage reactiveExecuteInsert( SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context){ + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmInsertStatement, domainParameterXref, context ); + return ((ReactiveHandler)buildResult.multiTableHandler()).reactiveExecute( buildResult.firstJdbcParameterBindings(), context ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java index 41b1c6224..b4a4bd077 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java @@ -10,11 +10,14 @@ import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +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.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; public interface ReactiveSqmMultiTableMutationStrategy extends SqmMultiTableMutationStrategy { @@ -28,10 +31,21 @@ default int executeUpdate( throw LOG.nonReactiveMethodCall( "reactiveExecuteUpdate" ); } - CompletionStage reactiveExecuteUpdate( + /** + * Execute the multi-table update indicated by the passed SqmUpdateStatement + * + * @return The number of rows affected + * @deprecated Use {@link #buildHandler(SqmDeleteOrUpdateStatement, DomainParameterXref, DomainQueryExecutionContext)} instead + */ + @Deprecated(forRemoval = true, since = "7.1") + default CompletionStage reactiveExecuteUpdate( SqmUpdateStatement sqmUpdateStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context){ + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmUpdateStatement, domainParameterXref, context ); + return ((ReactiveHandler)buildResult.multiTableHandler()).reactiveExecute( buildResult.firstJdbcParameterBindings(), context ); + + } @Override default int executeDelete( @@ -41,8 +55,18 @@ default int executeDelete( throw LOG.nonReactiveMethodCall( "reactiveExecuteDelete" ); } - CompletionStage reactiveExecuteDelete( + /** + * Execute the multi-table update indicated by the passed SqmUpdateStatement + * + * @return The number of rows affected + * @deprecated Use {@link #buildHandler(SqmDeleteOrUpdateStatement, DomainParameterXref, DomainQueryExecutionContext)} instead + */ + @Deprecated(forRemoval = true, since = "3.1") + default CompletionStage reactiveExecuteDelete( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context){ + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmDeleteStatement, domainParameterXref, context ); + return ((ReactiveHandler)buildResult.multiTableHandler()).reactiveExecute( buildResult.firstJdbcParameterBindings(), context ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 795b565ff..7b0bd112a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -109,7 +109,7 @@ import org.hibernate.reactive.query.ReactiveSelectionQuery; import org.hibernate.reactive.query.sql.internal.ReactiveNativeQueryImpl; import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor; -import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; +import org.hibernate.reactive.query.sqm.internal.ReactiveSqmQueryImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.util.impl.CompletionStages; @@ -390,7 +390,7 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) } protected ReactiveQueryImplementor createReactiveCriteriaQuery(SqmStatement criteria, Class resultType) { - final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( criteria, resultType, this ); + final ReactiveSqmQueryImpl query = new ReactiveSqmQueryImpl<>( criteria, resultType, this ); applyQuerySettingsAndHints( query ); return query; } @@ -400,11 +400,11 @@ public ReactiveQuery createReactiveQuery(TypedQueryReference typedQuer checksBeforeQueryCreation(); if ( typedQueryReference instanceof SelectionSpecificationImpl specification ) { final CriteriaQuery query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, specification.getResultType(), this ); } else if ( typedQueryReference instanceof MutationSpecificationImpl specification ) { final CommonAbstractCriteria query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); } else { @SuppressWarnings("unchecked") @@ -433,8 +433,8 @@ public ReactiveQuery createReactiveQuery(String queryString, Class exp try { final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); - final ReactiveQuerySqmImpl query = - new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this ); + final ReactiveSqmQueryImpl query = + new ReactiveSqmQueryImpl<>( queryString, interpretation, expectedResultType, this ); applyQuerySettingsAndHints( query ); query.setComment( queryString ); return query; @@ -611,7 +611,7 @@ protected ReactiveNativeQueryImpl createReactiveNativeQueryImplementor(Cl return (ReactiveNativeQueryImpl) query; } - protected ReactiveQuerySqmImpl createReactiveSqmQueryImplementor(Class resultType, NamedSqmQueryMemento memento) { + protected ReactiveSqmQueryImpl createReactiveSqmQueryImplementor(Class resultType, NamedSqmQueryMemento memento) { final SqmQueryImplementor query = memento.toQuery( this, resultType ); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic query" ); @@ -620,7 +620,7 @@ protected ReactiveQuerySqmImpl createReactiveSqmQueryImplementor(Class if ( memento.getLockOptions() != null ) { query.setLockOptions( memento.getLockOptions() ); } - return (ReactiveQuerySqmImpl) query; + return (ReactiveSqmQueryImpl) query; } private RuntimeException convertNamedQueryException(RuntimeException e) { @@ -649,7 +649,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(String hqlString final QueryImplementor query = createQuery( hqlString ); final SqmStatement sqmStatement = ( (SqmQueryImplementor) query ).getSqmStatement(); checkMutationQuery( hqlString, sqmStatement ); - return new ReactiveQuerySqmImpl<>( sqmStatement, null, this ); + return new ReactiveSqmQueryImpl<>( sqmStatement, null, this ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index d67cc2018..51cf19747 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -68,7 +68,7 @@ import org.hibernate.reactive.query.ReactiveSelectionQuery; import org.hibernate.reactive.query.sql.internal.ReactiveNativeQueryImpl; import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor; -import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; +import org.hibernate.reactive.query.sqm.internal.ReactiveSqmQueryImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; import org.hibernate.reactive.session.ReactiveSqmQueryImplementor; import org.hibernate.reactive.session.ReactiveStatelessSession; @@ -319,11 +319,12 @@ public void whenComplete(BiConsumer consumer) { private CompletionStage recreateCollections(Object entity, Object id, EntityPersister persister) { final Completable stage = new Completable<>(); + final String entityName = persister.getEntityName(); + final EventMonitor eventMonitor = getEventMonitor(); final Loop loop = new Loop(); forEachOwnedCollection( entity, id, persister, (descriptor, collection) -> { - firePreRecreate( collection, descriptor ); - final EventMonitor eventMonitor = getEventMonitor(); + firePreRecreate( collection, descriptor, entityName, entity ); final DiagnosticEvent event = eventMonitor.beginCollectionRecreateEvent(); loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) .reactiveRecreate( collection, id, this ) ) @@ -335,7 +336,7 @@ private CompletionStage recreateCollections(Object entity, Object id, Enti if ( statistics.isStatisticsEnabled() ) { statistics.recreateCollection( descriptor.getRole() ); } - firePostRecreate( collection, descriptor ); + firePostRecreate( collection, id, entityName, descriptor ); } ) ); } @@ -480,29 +481,35 @@ public CompletionStage reactiveDelete(Object entity) { } private CompletionStage removeCollections(Object entity, Object id, EntityPersister persister) { - final Completable stage = new Completable<>(); - final Loop loop = new Loop(); - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> { - firePreRemove( collection, entity, descriptor ); - final EventMonitor eventMonitor = getEventMonitor(); - final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); - loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) - .reactiveRemove( id, this ) ) - .whenComplete( (unused, throwable) -> eventMonitor - .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) - ) - .thenAccept( v -> { - firePostRemove( collection, entity, descriptor ); - final StatisticsImplementor statistics = getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.removeCollection( descriptor.getRole() ); - } - } ) - ); - } ); - loop.whenComplete( stage::complete ); - return stage.getStage(); + if ( persister.hasOwnedCollections() ) { + final Loop loop = new Loop(); + final Completable stage = new Completable<>(); + final String entityName = persister.getEntityName(); + forEachOwnedCollection( + entity, id, persister, + (descriptor, collection) -> { + firePreRemove( collection, id, entityName, entity ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); + loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) + .reactiveRemove( id, this ) ) + .whenComplete( (unused, throwable) -> eventMonitor + .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) + ) + .thenAccept( v -> { + firePostRemove( collection, id, entityName, entity ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.removeCollection( descriptor.getRole() ); + } + } ) + ); + } + ); + loop.whenComplete( stage::complete ); + return stage.getStage(); + } + return voidFuture(); } @Override @@ -561,31 +568,37 @@ private CompletionStage executeReactiveUpdate(Object entity) { } private CompletionStage removeAndRecreateCollections(Object entity, Object id, EntityPersister persister) { - final Completable stage = new Completable<>(); - final Loop loop = new Loop(); - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> { - firePreUpdate( collection, descriptor ); - final EventMonitor eventMonitor = getEventMonitor(); - final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); - ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) persister; - loop.then( () -> supplyStage( () -> reactivePersister - .reactiveRemove( id, this ) - .thenCompose( v -> reactivePersister.reactiveRecreate( collection, id, this ) ) ) - .whenComplete( (unused, throwable) -> eventMonitor - .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) - ) - .thenAccept( v -> { - firePostUpdate( collection, descriptor ); - final StatisticsImplementor statistics = getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.updateCollection( descriptor.getRole() ); - } - } ) - ); - } ); - loop.whenComplete( stage::complete ); - return stage.getStage(); + if ( persister.hasOwnedCollections() ) { + final String entityName = persister.getEntityName(); + final Completable stage = new Completable<>(); + final Loop loop = new Loop(); + forEachOwnedCollection( + entity, id, persister, + (descriptor, collection) -> { + firePreUpdate( collection, id, entityName, entity ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); + ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) persister; + loop.then( () -> supplyStage( () -> reactivePersister + .reactiveRemove( id, this ) + .thenCompose( v -> reactivePersister.reactiveRecreate( collection, id, this ) ) ) + .whenComplete( (unused, throwable) -> eventMonitor + .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) + ) + .thenAccept( v -> { + firePostUpdate( collection, id, entityName, entity); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.updateCollection( descriptor.getRole() ); + } + } ) + ); + } + ); + loop.whenComplete( stage::complete ); + return stage.getStage(); + } + return voidFuture(); } @Override @@ -938,11 +951,11 @@ public ReactiveQuery createReactiveQuery(TypedQueryReference typedQuer checksBeforeQueryCreation(); if ( typedQueryReference instanceof SelectionSpecificationImpl specification ) { final CriteriaQuery query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, specification.getResultType(), this ); } if ( typedQueryReference instanceof MutationSpecificationImpl specification ) { final CommonAbstractCriteria query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); } @SuppressWarnings("unchecked") // this cast is fine because of all our impls of TypedQueryReference return Class @@ -987,7 +1000,7 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) } private ReactiveQuery createReactiveCriteriaQuery(SqmStatement criteria, Class resultType) { - final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( criteria, resultType, this ); + final ReactiveSqmQueryImpl query = new ReactiveSqmQueryImpl<>( criteria, resultType, this ); applyQuerySettingsAndHints( query ); return query; } @@ -1013,8 +1026,8 @@ public ReactiveSqmQueryImplementor createReactiveQuery(String queryString try { final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); - final ReactiveQuerySqmImpl query = - new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this ); + final ReactiveSqmQueryImpl query = + new ReactiveSqmQueryImpl<>( queryString, interpretation, expectedResultType, this ); applyQuerySettingsAndHints( query ); query.setComment( queryString ); @@ -1157,7 +1170,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(String hqlString final QueryImplementor query = createQuery( hqlString ); final SqmStatement sqmStatement = ( (SqmQueryImplementor) query ).getSqmStatement(); checkMutationQuery( hqlString, sqmStatement ); - return new ReactiveQuerySqmImpl<>( sqmStatement, null, this ); + return new ReactiveSqmQueryImpl<>( sqmStatement, null, this ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java index a4b45101f..8b66a3bfb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java @@ -27,7 +27,7 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; /** - * @see org.hibernate.query.results.ResultSetMappingImpl + * @see org.hibernate.query.results.internal.ResultSetMappingImpl */ public class ReactiveResultSetMapping implements ResultSetMapping, ReactiveValuesMappingProducer { @@ -62,6 +62,11 @@ public CompletionStage reactiveResolve( .thenApply( columnCount -> delegate.resolve( jdbcResultsMetadata, loadQueryInfluencers, sessionFactory ) ); } + @Override + public ResultSetMapping cacheKeyInstance() { + return new ReactiveResultSetMapping( delegate.cacheKeyInstance() ); + } + @Override public String getMappingIdentifier() { return delegate.getMappingIdentifier(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java index 46a15c1e9..685717f35 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java @@ -38,10 +38,8 @@ public ReactiveEmbeddableFetchImpl(EmbeddableFetchImpl original) { } @Override - public EmbeddableInitializer createInitializer( - InitializerParent parent, - AssemblerCreationState creationState) { - return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), parent, creationState, true ); + public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), getNullIndicatorResult(), parent, creationState, true ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java index 1e3433320..426345ab5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java @@ -20,7 +20,7 @@ public ReactiveEmbeddableForeignKeyResultImpl(EmbeddableForeignKeyResultImpl @Override public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { return getReferencedModePart() instanceof NonAggregatedIdentifierMapping - ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) - : new ReactiveEmbeddableInitializerImpl( this, null, null, creationState, true ); + ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) + : new ReactiveEmbeddableInitializerImpl( this, null, null, null, creationState, true ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java index 02b3a76d1..6d5fbcd5e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -16,6 +16,7 @@ import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerData; @@ -66,10 +67,11 @@ public EmbeddableMappingType.ConcreteEmbeddableType getConcreteEmbeddableType() public ReactiveEmbeddableInitializerImpl( EmbeddableResultGraphNode resultDescriptor, BasicFetch discriminatorFetch, + DomainResult nullIndicatorResult, InitializerParent parent, AssemblerCreationState creationState, boolean isResultInitializer) { - super( resultDescriptor, discriminatorFetch, parent, creationState, isResultInitializer ); + super( resultDescriptor, discriminatorFetch, nullIndicatorResult, parent, creationState, isResultInitializer ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java index ac119a7d0..8de288c0e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java @@ -7,14 +7,15 @@ import java.lang.invoke.MethodHandles; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; +import org.hibernate.JDBCException; import org.hibernate.LockOptions; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -65,6 +66,17 @@ public ReactiveDeferredResultSetAccess( this.sqlStatementLogger = executionContext.getSession().getJdbcServices().getSqlStatementLogger(); } + @Override + public JdbcServices getJdbcServices() { + return getFactory().getJdbcServices(); + } + + @Override + public JDBCException convertSqlException(SQLException e, String message) { + return getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper() + .convert( e, message ); + } + /** * Reactive version of {@link org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess#registerAfterLoadAction(ExecutionContext, LockOptions)} * calling {@link ReactiveSession#reactiveLock(String, Object, LockOptions)} @@ -141,20 +153,6 @@ private JdbcValuesMetadata convertToMetadata(ResultSet resultSet) { return (JdbcValuesMetadata) resultSet; } - @Override - public CompletionStage getReactiveMetadata() { - return getReactiveResultSet().thenApply( this::reactiveMetadata ); - } - - private ResultSetMetaData reactiveMetadata(ResultSet resultSet) { - try { - return resultSet.getMetaData(); - } - catch (SQLException e) { - throw new RuntimeException( e ); - } - } - private static int columnCount(ResultSet resultSet) { try { return resultSet.getMetaData().getColumnCount(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java index 2412f3941..80d3217aa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java @@ -8,9 +8,11 @@ import java.lang.invoke.MethodHandles; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; +import java.sql.SQLException; import java.util.concurrent.CompletionStage; +import org.hibernate.JDBCException; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.reactive.logging.impl.Log; @@ -40,8 +42,13 @@ public ReactiveDirectResultSetAccess( } @Override - public SessionFactoryImplementor getFactory() { - return getPersistenceContext().getFactory(); + public JdbcServices getJdbcServices() { + return getFactory().getJdbcServices(); + } + + @Override + public JDBCException convertSqlException(SQLException e, String message) { + return getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( e, message ); } @Override @@ -73,11 +80,6 @@ public CompletionStage getReactiveResultSet() { return completedFuture( resultSet ); } - @Override - public CompletionStage getReactiveMetadata() { - return completedFuture( getMetaData() ); - } - @Override public CompletionStage getReactiveColumnCount() { return completedFuture( getColumnCount() ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java index 75a265ed5..ad9be5a6a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java @@ -10,9 +10,9 @@ import java.sql.SQLException; import java.util.concurrent.CompletionStage; +import org.hibernate.JDBCException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; @@ -27,14 +27,14 @@ */ public interface ReactiveResultSetAccess extends JdbcValuesMetadata { CompletionStage getReactiveResultSet(); - CompletionStage getReactiveMetadata(); CompletionStage getReactiveColumnCount(); CompletionStage resolveJdbcValueMetadata(); ResultSet getResultSet(); - SessionFactoryImplementor getFactory(); + JdbcServices getJdbcServices(); + void release(); /** @@ -51,43 +51,35 @@ default int getColumnCount() { return getResultSet().getMetaData().getColumnCount(); } catch (SQLException e) { - throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( - e, - "Unable to access ResultSet column count" - ); + throw convertSqlException( e, "Unable to access ResultSet column count" ); } } + JDBCException convertSqlException(SQLException e, String message); + default int resolveColumnPosition(String columnName) { try { return getResultSet().findColumn( columnName ); } catch (SQLException e) { - throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( - e, - "Unable to find column position by name" - ); + throw convertSqlException( e, "Unable to find column position by name" ); } } default String resolveColumnName(int position) { try { - return getFactory().getJdbcServices().getJdbcEnvironment() + return getJdbcServices().getJdbcEnvironment() .getDialect() .getColumnAliasExtractor() .extractColumnAlias( getResultSet().getMetaData(), position ); } catch (SQLException e) { - throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( - e, - "Unable to find column name by position" - ); + throw convertSqlException( e, "Unable to find column name by position" ); } } @Override default BasicType resolveType(int position, JavaType explicitJavaType, TypeConfiguration typeConfiguration) { - final JdbcServices jdbcServices = getFactory().getJdbcServices(); try { final ResultSetMetaData metaData = getResultSet().getMetaData(); final String columnTypeName = metaData.getColumnTypeName( position ); @@ -95,7 +87,7 @@ default BasicType resolveType(int position, JavaType explicitJavaType, final int scale = metaData.getScale( position ); final int precision = metaData.getPrecision( position ); final int displaySize = metaData.getColumnDisplaySize( position ); - final Dialect dialect = jdbcServices.getDialect(); + final Dialect dialect = getJdbcServices().getDialect(); final int length = dialect.resolveSqlTypeLength( columnTypeName, columnType, @@ -104,13 +96,7 @@ default BasicType resolveType(int position, JavaType explicitJavaType, displaySize ); final JdbcType resolvedJdbcType = dialect - .resolveSqlTypeDescriptor( - columnTypeName, - columnType, - length, - scale, - typeConfiguration.getJdbcTypeRegistry() - ); + .resolveSqlTypeDescriptor( columnTypeName, columnType, length, scale, typeConfiguration.getJdbcTypeRegistry() ); final JavaType javaType; final JdbcType jdbcType; // If there is an explicit JavaType, then prefer its recommended JDBC type @@ -145,26 +131,19 @@ public EnumType getEnumeratedType() { @Override public Dialect getDialect() { - return getFactory().getJdbcServices().getDialect(); + return getJdbcServices().getDialect(); } } ); } else { jdbcType = resolvedJdbcType; - javaType = jdbcType.getJdbcRecommendedJavaTypeMapping( - length, - scale, - typeConfiguration - ); + javaType = jdbcType.getJdbcRecommendedJavaTypeMapping( length, scale, typeConfiguration ); } return typeConfiguration.getBasicTypeRegistry().resolve( javaType, jdbcType ); } catch (SQLException e) { - throw jdbcServices.getSqlExceptionHelper().convert( - e, - "Unable to determine JDBC type code for ResultSet position " + position - ); + throw convertSqlException( e, "Unable to determine JDBC type code for ResultSet position " + position ); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java index 81af936ba..ab84952ef 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java @@ -5,26 +5,27 @@ */ package org.hibernate.reactive.type.descriptor.jdbc; -import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JsonHelper; -import org.hibernate.type.descriptor.jdbc.JsonJdbcType; +import org.hibernate.type.descriptor.jdbc.spi.JsonGeneratingVisitor; +import org.hibernate.type.format.StringJsonDocumentWriter; import io.vertx.core.json.JsonArray; -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; /** * @see org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType @@ -65,21 +66,24 @@ protected X fromString(String string, JavaType javaType, WrapperOptions o if ( string == null ) { return null; } - - return JsonHelper.arrayFromString( javaType, getElementJdbcType(), string, options ); + if ( ((BasicPluralJavaType) javaType).getElementJavaType() instanceof UnknownBasicJavaType ) { + return options.getJsonFormatMapper().fromString( string, javaType, options ); + } + else { + return JsonHelper.arrayFromString( javaType, this.getElementJdbcType(), string, options ); + } } protected String toString(X value, JavaType javaType, WrapperOptions options) { - final JdbcType elementJdbcType = getElementJdbcType(); - final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); - if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { - final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); - return JsonHelper.arrayToString( embeddableMappingType, domainObjects, options ); + final JavaType elementJavaType = ( (BasicPluralJavaType) javaType ).getElementJavaType(); + if ( elementJavaType instanceof UnknownBasicJavaType ) { + return options.getJsonFormatMapper().toString( value, javaType, options); } else { - assert !( elementJdbcType instanceof AggregateJdbcType ); - final JavaType elementJavaType = ( (BasicPluralJavaType) javaType ).getElementJavaType(); - return JsonHelper.arrayToString( elementJavaType, elementJdbcType, domainObjects, options ); + final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); + final StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); + JsonGeneratingVisitor.INSTANCE.visitArray( elementJavaType, getElementJdbcType(), domainObjects, options, writer ); + return writer.getJson(); } } @@ -121,12 +125,8 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o } private X getObject(Object array, WrapperOptions options) throws SQLException { - if ( array == null ) { - return null; - } - - return ( (ReactiveJsonArrayJdbcType) getJdbcType() ) - .fromString( ( (JsonArray) array ).encode(), getJavaType(), options ); + final String json = array == null ? null : ( (JsonArray) array ).encode(); + return ( (ReactiveJsonArrayJdbcType) getJdbcType() ).fromString( json, getJavaType(), options ); } }; } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java new file mode 100644 index 000000000..f1c08d4ce --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java @@ -0,0 +1,225 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletionStage; + +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.SoftDelete; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Tuple; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.common.Identifier.id; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +@Timeout(value = 10, timeUnit = MINUTES) +public class ImplicitSoftDeleteTests extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( ImplicitEntity.class ); + } + + @Override + protected CompletionStage cleanDb() { + return voidFuture(); + } + + @BeforeEach + void createTestData(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createNativeQuery( "delete from implicit_entities" ).executeUpdate() ) + .call( () -> getMutinySessionFactory().withTransaction( s -> s + .persistAll( new ImplicitEntity( 1, "first" ), new ImplicitEntity( 2, "second" ), new ImplicitEntity( 3, "third" ) ) + ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity first = s.getReference( ImplicitEntity.class, 1 ); + return s.remove( first ).call( s::flush ); + } ) ) + .call( () -> getMutinySessionFactory() + .withTransaction( s -> s.createNativeQuery( "select * from implicit_entities e order by id", Tuple.class ).getResultList() ) + .invoke( tuples -> assertThat( tuples ).hasSize( 3 ) ) + ) + ); + } + + @Test + void testSelectionQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createQuery( "from ImplicitEntity", ImplicitEntity.class ).getResultList() ) + .invoke( list -> assertThat( list ).hasSize( 2 ) ) + ); + } + + @Test + void testLoading(VertxTestContext context) { + test( + context, getMutinySessionFactory() + // Load + .withTransaction( s -> s + .find( ImplicitEntity.class, 1 ).invoke( entity -> assertThat( entity ).isNull() ) + .call( () -> s.find( ImplicitEntity.class, 2 ).invoke( entity -> assertThat( entity ).isNotNull() ) ) + .call( () -> s.find( ImplicitEntity.class, 3 ).invoke( entity -> assertThat( entity ).isNotNull() ) ) + ) + // Proxy + // We deleted the entity, so we expect an ObjectNotFoundException + .chain( () -> assertThrown( ObjectNotFoundException.class, getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 1 ); + return s.fetch( reference ).map( ImplicitEntity::getName ); + } ) ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 2 ); + return s.fetch( reference ).map( ImplicitEntity::getName ); + } ) ) + .invoke( name -> assertThat( name ).isEqualTo( "second" ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 3 ); + return s.fetch( reference ).map( ImplicitEntity::getName ); + } ) ) + .invoke( name -> assertThat( name ).isEqualTo( "third" ) ) + ); + } + + @Test + void testMultiLoading(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( ImplicitEntity.class, 1, 2, 3 ) ) + .invoke( list -> assertThat( list ) + .containsExactly( null, new ImplicitEntity( null, "second" ), new ImplicitEntity( null, "third" ) ) ) + ); + } + + @Test + void testNaturalIdLoading(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( ImplicitEntity.class, id( "name", "first" ) ) ) + .invoke( entity -> assertThat( entity ).isNull() ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s.find( ImplicitEntity.class, id( "name", "second" ) ) ) ) + .invoke( entity -> assertThat( entity ).extracting( ImplicitEntity::getId ).isEqualTo( 2 ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s.find( ImplicitEntity.class, id( "name", "third" ) ) ) ) + .invoke( entity -> assertThat( entity ).extracting( ImplicitEntity::getId ).isEqualTo( 3 ) ) + ); + } + + @Test + void testDeletion(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 2 ); + return s.remove( reference ).call( s::flush ) + .call( () -> s.createSelectionQuery( "from ImplicitEntity", ImplicitEntity.class ).getResultList() + // #1 was "deleted" up front and we just "deleted" #2... only #3 should be active + .invoke( list -> { + assertThat( list ).extracting( ImplicitEntity::getId ).containsExactly( 3 ); + assertThat( list ).extracting( ImplicitEntity::getName ).containsExactly( "third" ); + } ) + ); + } ) + ); + } + + @Test + void testFullUpdateMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "update ImplicitEntity set name = null" ).executeUpdate() ) + .invoke( affected -> assertThat( affected ).isEqualTo( 2 ) ) + ); + } + + @Test + void testRestrictedUpdateMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "update ImplicitEntity set name = null where name = 'second'" ).executeUpdate() ) + .invoke( affected -> assertThat( affected ).isEqualTo( 1 ) ) + ); + } + + @Test + void testFullDeleteMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "delete ImplicitEntity" ).executeUpdate() ) + // only #2 and #3 + .invoke( affected -> assertThat( affected ).isEqualTo( 2 ) ) + ); + } + + @Test + void testRestrictedDeleteMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "delete ImplicitEntity where name = 'second'" ).executeUpdate() ) + // only #2 + .invoke( affected -> assertThat( affected ).isEqualTo( 1 ) ) + ); + } + + + @Entity(name = "ImplicitEntity") + @Table(name = "implicit_entities") + @SoftDelete + public static class ImplicitEntity { + @Id + private Integer id; + @NaturalId + private String name; + + public ImplicitEntity() { + } + + public ImplicitEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object object) { + if ( object == null || getClass() != object.getClass() ) { + return false; + } + ImplicitEntity that = (ImplicitEntity) object; + return Objects.equals( name, that.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java index 3d22d0e71..73233b5e9 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java @@ -146,14 +146,14 @@ private String getIsbn() { private Author author; public Book(String isbn, String title, Author author) { - super( "Book", 1, singleton( "isbn" ), null ); + super( new EntityRelatedState( "Book", singleton( "isbn" ) ), 1, null ); this.title = title; this.isbn = isbn; this.author = author; } public Book() { - super( "Book", 1, singleton( "isbn" ), null ); + super( new EntityRelatedState( "Book", singleton( "isbn" ) ), 1, null ); } @Override diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java index 8dcc07a6a..8cf8e6931 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java @@ -144,14 +144,10 @@ private void assertQueries() { } private boolean hasMergeOperator() { - switch ( dbType() ) { - case SQLSERVER: - case ORACLE: - case POSTGRESQL: - return true; - default: - return false; - } + return switch ( dbType() ) { + case SQLSERVER, ORACLE, POSTGRESQL, DB2 -> true; + default -> false; + }; } @Entity(name = "Record") diff --git a/integration-tests/bytecode-enhancements-it/build.gradle b/integration-tests/bytecode-enhancements-it/build.gradle index 74ee2d563..aaf1f5d8a 100644 --- a/integration-tests/bytecode-enhancements-it/build.gradle +++ b/integration-tests/bytecode-enhancements-it/build.gradle @@ -41,5 +41,8 @@ dependencies { testImplementation(libs.io.vertx.vertx.junit5) } -// Optional: enable the bytecode enhancements -hibernate { enhancement } +hibernate { + enhancement { + // We want everything enabled for the tests + } +} diff --git a/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java b/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java index c2300977e..eef39ba37 100644 --- a/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java +++ b/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java @@ -49,7 +49,7 @@ * that's been close ahead of time by someone else. * Theoretically, everything could happen in the right order because of chance, * but it's unlikely and at the moment I don't have a better solution. - * See the the related issue + * See the related issue * for more details. *

*/ diff --git a/settings.gradle b/settings.gradle index 159d91e20..f1c542d37 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,13 +24,13 @@ def GRADLE_MAX_SUPPORTED_BYTECODE_VERSION = 23 dependencyResolutionManagement { versionCatalogs { create("libs") { - // ./gradlew build -PhibernateOrmVersion=7.0.2.Final + // ./gradlew build -PhibernateOrmVersion=7.1.0.Final def hibernateOrmVersion = settings.ext.find("hibernateOrmVersion") ?: "" if ( hibernateOrmVersion != "" ) { version("hibernateOrmVersion", hibernateOrmVersion) } - // ./gradlew build -PhibernateOrmGradlePluginVersion=7.0.2.Final + // ./gradlew build -PhibernateOrmGradlePluginVersion=7.1.0.Final def hibernateOrmGradlePluginVersion = settings.ext.find("hibernateOrmGradlePluginVersion") ?: "" if ( hibernateOrmGradlePluginVersion ) { version("hibernateOrmGradlePluginVersion", hibernateOrmGradlePluginVersion)