From 6a65356b5309a9daf3ea5f68d3eae3f76373466a Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 5 Jun 2024 15:02:27 +0200 Subject: [PATCH 01/12] [#1935] Change properties to test ORM 6.5 snapshots --- gradle.properties | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index b1218c9384..3d21137f9b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -29,22 +29,24 @@ org.gradle.java.installations.auto-download=false #db = MSSQL # Enable the SonatypeOS maven repository (mainly for Vert.x snapshots) when present (value ignored) -#enableSonatypeOpenSourceSnapshotsRep = true +enableSonatypeOpenSourceSnapshotsRep = true # Enable the maven local repository (for local development when needed) when present (value ignored) #enableMavenLocalRepo = true # Override default Hibernate ORM version -#hibernateOrmVersion = 6.5.2.Final +# I'm not setting a version window here, because the examples will end up using the latest stable version +# instead of the latest snapshot +hibernateOrmVersion = 6.5.3-SNAPSHOT # Override default Hibernate ORM Gradle plugin version # Using the stable version because I don't know how to configure the build to download the snapshot version from # a remote repository -#hibernateOrmGradlePluginVersion = 6.5.2.Final +hibernateOrmGradlePluginVersion = 6.5.2.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 -#skipOrmVersionParsing = true +skipOrmVersionParsing = true # Override default Vert.x Sql client version #vertxSqlClientVersion = 4.5.8-SNAPSHOT From 84a84640bbb56a7114f7b9d4a374d049f8d25c31 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 5 Jun 2024 15:02:03 +0200 Subject: [PATCH 02/12] [#1935] Fixes for Hibernate ORM 6.5.3.Final --- .../ReactiveToOneAttributeMapping.java | 5 +++++ ...veInformationSchemaBasedExtractorImpl.java | 19 +++++++++++++++++++ .../ReactiveConnectionPoolTest.java | 6 ++++++ 3 files changed, 30 insertions(+) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java index 3acdff04ca..09acc9bb98 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java @@ -97,4 +97,9 @@ public ReactiveToOneAttributeMapping copy( TableGroupProducer declaringTableGroupProducer) { return new ReactiveToOneAttributeMapping( super.copy( declaringType, declaringTableGroupProducer ) ); } + + @Override + protected Object lazyInitialize(Object domainValue) { + return domainValue; + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java index 5daafc28e2..8ba013cb56 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/AbstractReactiveInformationSchemaBasedExtractorImpl.java @@ -5,6 +5,7 @@ */ package org.hibernate.reactive.provider.service; +import java.lang.invoke.MethodHandles; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -14,6 +15,8 @@ import org.hibernate.boot.model.TruthValue; import org.hibernate.boot.model.naming.DatabaseIdentifier; import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.tool.schema.extract.internal.AbstractInformationExtractorImpl; import org.hibernate.tool.schema.extract.internal.ColumnInformationImpl; import org.hibernate.tool.schema.extract.spi.ColumnInformation; @@ -29,6 +32,8 @@ */ public abstract class AbstractReactiveInformationSchemaBasedExtractorImpl extends AbstractInformationExtractorImpl { + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + public AbstractReactiveInformationSchemaBasedExtractorImpl(ExtractionContext extractionContext) { super( extractionContext ); } @@ -154,6 +159,20 @@ protected T processCatalogsResultSet(ExtractionContext.ResultSetProcessor ); } + @Override + protected T processCrossReferenceResultSet( + String parentCatalog, + String parentSchema, + String parentTable, + String foreignCatalog, + String foreignSchema, + String foreignTable, + ExtractionContext.ResultSetProcessor processor) { + // This method has been added as fix for https://hibernate.atlassian.net/browse/HHH-18221 + // The issue is only for Informix that we don't currently support. + throw LOG.notYetImplemented(); + } + @Override protected T processSchemaResultSet( String catalog, diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java index 1e755aa622..babae4bd47 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java @@ -12,6 +12,7 @@ import org.hibernate.engine.jdbc.internal.JdbcServicesImpl; import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.reactive.containers.DatabaseConfiguration; import org.hibernate.reactive.pool.ReactiveConnectionPool; @@ -64,6 +65,11 @@ private ReactiveConnectionPool configureAndStartPool(Map config) public SqlStatementLogger getSqlStatementLogger() { return new SqlStatementLogger(); } + + @Override + public SqlExceptionHelper getSqlExceptionHelper() { + return new SqlExceptionHelper( true ); + } } ); DefaultSqlClientPool reactivePool = new DefaultSqlClientPool(); reactivePool.injectServices( registryExtension.getServiceRegistry() ); From 0d74bea534a1e75fec8eba404983751b9cef62ca Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 11 Jun 2024 10:57:26 +0200 Subject: [PATCH 03/12] [#1932] Support ReactiveSelectionQuery#getResultCount for HQL I need some changes in ORM before I can support it for native queries. --- .../org/hibernate/reactive/mutiny/Mutiny.java | 11 ++++ .../reactive/mutiny/impl/MutinyQueryImpl.java | 5 ++ .../mutiny/impl/MutinySelectionQueryImpl.java | 5 ++ .../query/ReactiveSelectionQuery.java | 2 + .../spi/ReactiveAbstractSelectionQuery.java | 16 +++++- .../sql/internal/ReactiveNativeQueryImpl.java | 10 ++++ .../ConcreteSqmSelectReactiveQueryPlan.java | 55 +++++++++++++++++- .../sqm/internal/ReactiveQuerySqmImpl.java | 11 ++++ .../ReactiveSqmSelectionQueryImpl.java | 6 ++ .../sqm/spi/ReactiveSelectQueryPlan.java | 3 +- .../internal/ReactiveInitializersList.java | 6 ++ .../spi/ReactiveSingleResultConsumer.java | 46 +++++++++++++++ .../org/hibernate/reactive/stage/Stage.java | 56 ++++++++++++------- .../reactive/stage/impl/StageQueryImpl.java | 5 ++ .../stage/impl/StageSelectionQueryImpl.java | 5 ++ 15 files changed, 218 insertions(+), 24 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java index 22ec48da1c..65b0ad9062 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java @@ -194,6 +194,17 @@ interface SelectionQuery extends AbstractQuery { */ Uni getSingleResultOrNull(); + /** + * Determine the size of the query result list that would be + * returned by calling {@link #getResultList()} with no + * {@linkplain #getFirstResult() offset} or + * {@linkplain #getMaxResults() limit} applied to the query. + * + * @return the size of the list that would be returned + */ + @Incubating + Uni getResultCount(); + /** * Asynchronously execute this query, returning the query results * as a {@link List}, via a {@link Uni}. If the query diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java index 9cb3a0287c..d0608ba5f7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java @@ -47,6 +47,11 @@ public int getMaxResults() { return delegate.getMaxResults(); } + @Override + public Uni getResultCount() { + return uni( delegate::getReactiveResultCount ); + } + @Override public Uni> getResultList() { return uni( delegate::getReactiveResultList ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java index 0d037e0ed6..b001e618a9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java @@ -45,6 +45,11 @@ public int getMaxResults() { return delegate.getMaxResults(); } + @Override + public Uni getResultCount() { + return uni( delegate::getReactiveResultCount ); + } + @Override public Uni> getResultList() { return uni( delegate::getReactiveResultList ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java index 81508c13b4..2b347b3de7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java @@ -49,6 +49,8 @@ default CompletionStage> getReactiveResultList() { CompletionStage getReactiveSingleResultOrNull(); + CompletionStage getReactiveResultCount(); + CompletionStage reactiveUnique(); CompletionStage> reactiveUniqueResultOptional(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java index 69ebe52a1c..f01b5d3150 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/spi/ReactiveAbstractSelectionQuery.java @@ -21,6 +21,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.hql.internal.QuerySplitter; +import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext; +import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.sqm.internal.DomainParameterXref; @@ -33,6 +35,7 @@ import org.hibernate.reactive.query.sqm.internal.AggregatedSelectReactiveQueryPlan; import org.hibernate.reactive.query.sqm.internal.ConcreteSqmSelectReactiveQueryPlan; import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; +import org.hibernate.reactive.sql.results.spi.ReactiveSingleResultConsumer; import org.hibernate.sql.results.internal.TupleMetadata; import jakarta.persistence.NoResultException; @@ -146,6 +149,17 @@ public CompletionStage getReactiveSingleResult() { .exceptionally( this::convertException ); } + public CompletionStage getReactiveResultsCount(SqmSelectStatement sqmStatement, DomainQueryExecutionContext domainQueryExecutionContext) { + final DelegatingDomainQueryExecutionContext context = new DelegatingDomainQueryExecutionContext( domainQueryExecutionContext ) { + @Override + public QueryOptions getQueryOptions() { + return QueryOptions.NONE; + } + }; + return buildConcreteSelectQueryPlan( sqmStatement.createCountQuery(), Long.class, getQueryOptions() ) + .reactiveExecuteQuery( context, new ReactiveSingleResultConsumer<>() ); + } + private R reactiveSingleResult(List list) { if ( list.isEmpty() ) { throw new NoResultException( String.format( "No result found for query [%s]", getQueryString() ) ); @@ -269,7 +283,7 @@ private ReactiveSelectQueryPlan buildAggregatedSelectQueryPlan(SqmSelectState return new AggregatedSelectReactiveQueryPlan<>( aggregatedQueryPlans ); } - private ReactiveSelectQueryPlan buildConcreteSelectQueryPlan( + public ReactiveSelectQueryPlan buildConcreteSelectQueryPlan( SqmSelectStatement concreteSqmStatement, Class resultType, QueryOptions queryOptions) { 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 a441b4b555..1c97f847a3 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 @@ -93,6 +93,16 @@ private T getNull() { return null; } + @Override + public long getResultCount() { + throw LOG.nonReactiveMethodCall( "getReactiveResultCount()" ); + } + + @Override + public CompletionStage getReactiveResultCount() { + throw LOG.notYetImplemented(); + } + private ReactiveAbstractSelectionQuery createSelectionQueryDelegate(SharedSessionContractImplementor session) { return new ReactiveAbstractSelectionQuery<>( this::getQueryOptions, 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 16135b9a2c..858e2d2098 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 @@ -36,6 +36,7 @@ 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; @@ -59,6 +60,7 @@ public class ConcreteSqmSelectReactiveQueryPlan extends ConcreteSqmSelectQueryPlan implements ReactiveSelectQueryPlan { + private final SqmInterpreter> executeQueryInterpreter; private final SqmInterpreter, Void> listInterpreter; private final RowTransformer rowTransformer; @@ -80,6 +82,8 @@ public ConcreteSqmSelectReactiveQueryPlan( this.rowTransformer = determineRowTransformer( sqm, resultType, tupleMetadata, queryOptions ); this.listInterpreter = (unused, executionContext, sqmInterpretation, jdbcParameterBindings) -> listInterpreter( hql, domainParameterXref, executionContext, sqmInterpretation, jdbcParameterBindings, rowTransformer ); + this.executeQueryInterpreter = (resultsConsumer, executionContext, sqmInterpretation, jdbcParameterBindings) -> + executeQueryInterpreter( hql, domainParameterXref, executionContext, sqmInterpretation, jdbcParameterBindings, rowTransformer, resultsConsumer ); } private static CompletionStage> listInterpreter( @@ -110,6 +114,40 @@ private static CompletionStage> listInterpreter( .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } + private static CompletionStage executeQueryInterpreter( + String hql, + DomainParameterXref domainParameterXref, + DomainQueryExecutionContext executionContext, + 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, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareQueryStatement( sql, false, null ), + resultsConsumer + ) + ) + ) + .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); + } + @Override public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, DomainQueryExecutionContext executionContext) { throw new UnsupportedOperationException(); @@ -119,10 +157,21 @@ public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, Doma public CompletionStage> reactivePerformList(DomainQueryExecutionContext executionContext) { return executionContext.getQueryOptions().getEffectiveLimit().getMaxRowsJpa() == 0 ? completedFuture( emptyList() ) - : withCacheableSqmInterpretation( executionContext, listInterpreter ); + : withCacheableSqmInterpretation( executionContext, null, listInterpreter ); + } + + @Override + public CompletionStage reactiveExecuteQuery( + DomainQueryExecutionContext executionContext, + ReactiveResultsConsumer resultsConsumer) { + return withCacheableSqmInterpretation( + executionContext, + resultsConsumer, + (SqmInterpreter>) (SqmInterpreter) executeQueryInterpreter + ); } - private CompletionStage withCacheableSqmInterpretation(DomainQueryExecutionContext executionContext, SqmInterpreter interpreter) { + private CompletionStage withCacheableSqmInterpretation(DomainQueryExecutionContext executionContext, X context, SqmInterpreter interpreter) { // NOTE : VERY IMPORTANT - intentional double-lock checking // The other option would be to leverage `java.util.concurrent.locks.ReadWriteLock` // to protect access. However, synchronized is much simpler here. We will verify @@ -162,7 +211,7 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext ); } - return interpreter.interpret( null, executionContext, localCopy, jdbcParameterBindings ); + return interpreter.interpret( context, executionContext, localCopy, jdbcParameterBindings ); } private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) { 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/ReactiveQuerySqmImpl.java index 3c23e6c765..fbe3295d68 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/ReactiveQuerySqmImpl.java @@ -139,6 +139,17 @@ public CompletionStage getReactiveSingleResult() { return selectionQueryDelegate.getReactiveSingleResult(); } + @Override + public long getResultCount() { + throw LOG.nonReactiveMethodCall( "getReactiveResultCount()" ); + } + + @Override + public CompletionStage getReactiveResultCount() { + return selectionQueryDelegate + .getReactiveResultsCount( ( (SqmSelectStatement) getSqmStatement() ).createCountQuery(), this ); + } + @Override public CompletionStage getReactiveSingleResultOrNull() { return selectionQueryDelegate.getReactiveSingleResultOrNull(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java index 138f996a4c..13e1676979 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java @@ -212,6 +212,12 @@ public R getSingleResultOrNull() { return selectionQueryDelegate.getSingleResultOrNull(); } + @Override + public CompletionStage getReactiveResultCount() { + return selectionQueryDelegate + .getReactiveResultsCount( getSqmStatement().createCountQuery(), this ); + } + @Override public List getResultList() { return selectionQueryDelegate.getResultList(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/spi/ReactiveSelectQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/spi/ReactiveSelectQueryPlan.java index c83a6883f9..6ef50764cb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/spi/ReactiveSelectQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/spi/ReactiveSelectQueryPlan.java @@ -14,6 +14,7 @@ import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; import org.hibernate.sql.results.spi.ResultsConsumer; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; @@ -44,7 +45,7 @@ default T executeQuery(DomainQueryExecutionContext executionContext, Results /** * Execute the query */ - default CompletionStage reactiveExecuteQuery(DomainQueryExecutionContext executionContext, ResultsConsumer resultsConsumer) { + default CompletionStage reactiveExecuteQuery(DomainQueryExecutionContext executionContext, ReactiveResultsConsumer resultsConsumer) { return failedFuture( new UnsupportedOperationException() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java index d861cddc4c..e752a49ca0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java @@ -56,6 +56,12 @@ public void finishUpRow(final RowProcessingState rowProcessingState) { } } + public void startLoading(final RowProcessingState rowProcessingState) { + for ( int i = initializers.length - 1; i >= 0; i-- ) { + initializers[i].startLoading( rowProcessingState ); + } + } + public CompletionStage initializeInstance(final ReactiveRowProcessingState rowProcessingState) { return loop( initializers, initializer -> { if ( initializer instanceof ReactiveInitializer ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java new file mode 100644 index 0000000000..ad3d17853a --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java @@ -0,0 +1,46 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.spi; + +import java.util.concurrent.CompletionStage; + +import org.hibernate.Incubating; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; +import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; + +@Incubating +public class ReactiveSingleResultConsumer implements ReactiveResultsConsumer { + + @Override + public CompletionStage consume( + ReactiveValuesResultSet jdbcValues, + SharedSessionContractImplementor session, + JdbcValuesSourceProcessingOptions processingOptions, + JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, + ReactiveRowProcessingState rowProcessingState, + ReactiveRowReader rowReader) { + rowReader.getReactiveInitializersList().startLoading( rowProcessingState ); + return rowProcessingState.next() + .thenCompose( hasNext -> rowReader + .reactiveReadRow( rowProcessingState, processingOptions ) + .thenApply( result -> { + rowProcessingState.finishRowProcessing( true ); + rowReader.finishUp( jdbcValuesSourceProcessingState ); + jdbcValuesSourceProcessingState.finishUp( false ); + return result; + } ) + ); + } + + @Override + public boolean canResultsBeCached() { + return false; + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java index 5fc1d671ee..47da9f6cff 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java @@ -5,20 +5,18 @@ */ package org.hibernate.reactive.stage; -import jakarta.persistence.CacheRetrieveMode; -import jakarta.persistence.CacheStoreMode; -import jakarta.persistence.EntityGraph; -import jakarta.persistence.FlushModeType; -import jakarta.persistence.LockModeType; -import jakarta.persistence.Parameter; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.CriteriaUpdate; -import jakarta.persistence.metamodel.Attribute; -import jakarta.persistence.metamodel.Metamodel; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; +import java.util.function.Function; + import org.hibernate.Cache; -import org.hibernate.*; +import org.hibernate.CacheMode; +import org.hibernate.Filter; +import org.hibernate.FlushMode; +import org.hibernate.Incubating; +import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.collection.spi.AbstractPersistentCollection; import org.hibernate.collection.spi.PersistentCollection; @@ -38,16 +36,25 @@ import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.stat.Statistics; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.concurrent.CompletionStage; -import java.util.function.BiFunction; -import java.util.function.Function; +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.FlushModeType; +import jakarta.persistence.LockModeType; +import jakarta.persistence.Parameter; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.metamodel.Attribute; +import jakarta.persistence.metamodel.Metamodel; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.internal.util.LockModeConverter.convertToLockMode; -import static org.hibernate.jpa.internal.util.CacheModeHelper.*; +import static org.hibernate.jpa.internal.util.CacheModeHelper.interpretCacheMode; +import static org.hibernate.jpa.internal.util.CacheModeHelper.interpretCacheRetrieveMode; +import static org.hibernate.jpa.internal.util.CacheModeHelper.interpretCacheStoreMode; /** * An API for Hibernate Reactive where non-blocking operations are @@ -188,6 +195,17 @@ interface SelectionQuery extends AbstractQuery { */ CompletionStage getSingleResultOrNull(); + /** + * Determine the size of the query result list that would be + * returned by calling {@link #getResultList()} with no + * {@linkplain #getFirstResult() offset} or + * {@linkplain #getMaxResults() limit} applied to the query. + * + * @return the size of the list that would be returned + */ + @Incubating + CompletionStage getResultCount(); + /** * Asynchronously execute this query, returning the query results * as a {@link List}, via a {@link CompletionStage}. If the query diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java index 3a4948275a..fb022c4997 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java @@ -38,6 +38,11 @@ public int getMaxResults() { return delegate.getMaxResults(); } + @Override + public CompletionStage getResultCount() { + return delegate.getReactiveResultCount(); + } + @Override public CompletionStage> getResultList() { return delegate.getReactiveResultList(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java index f28bfd9d03..6a1be5d084 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java @@ -41,6 +41,11 @@ public int getMaxResults() { return delegate.getMaxResults(); } + @Override + public CompletionStage getResultCount() { + return delegate.getReactiveResultCount(); + } + @Override public CompletionStage> getResultList() { return delegate.getReactiveResultList(); From c53fa2952697d13e0303f3eadbaec7f4d7c058b7 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 11 Jun 2024 10:58:54 +0200 Subject: [PATCH 04/12] [#1932] Test for `#getReactiveResultCount` with HQL --- .../org/hibernate/reactive/QueryTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java index 054264011b..00e42e82ed 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java @@ -607,6 +607,70 @@ public void testSingleResultOrNullNonUniqueException(VertxTestContext context) { ); } + @Test + public void testSelectionQueryGetResultCountWithStage(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + test( context, getSessionFactory() + .withTransaction( s -> s.persist( author1, author2 ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .createSelectionQuery( "from Author", Author.class ) + .getResultCount() ) ) + .thenAccept( count -> assertEquals( 2L, count ) ) + ); + } + + @Test + public void testQueryGetResultCountWithStage(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + test( context, getSessionFactory() + .withTransaction( s -> s.persist( author1, author2 ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .createQuery( "from Author", Author.class ) + .getResultCount() ) ) + .thenAccept( count -> assertEquals( 2L, count ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .createQuery( "from Author", Author.class ) + .setMaxResults( 1 ) + .setFirstResult( 1 ) + .getResultCount() ) ) + .thenAccept( count -> assertEquals( 2L, count ) ) + ); + } + + @Test + public void testSelectionQueryGetResultCountWithMutiny(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + test( context, getSessionFactory() + .withTransaction( s -> s.persist( author1, author2 ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .createSelectionQuery( "from Author", Author.class ) + .getResultCount() ) ) + .thenAccept( count -> assertEquals( 2L, count ) ) + ); + } + + @Test + public void testQueryGetResultCountWithMutiny(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + test( context, getMutinySessionFactory() + .withTransaction( s -> s.persistAll( author1, author2 ) ) + .chain( () -> getMutinySessionFactory().withSession( s -> s + .createQuery( "from Author", Author.class ) + .getResultCount() ) ) + .invoke( count -> assertEquals( 2L, count ) ) + .chain( () -> getMutinySessionFactory().withSession( s -> s + .createQuery( "from Author", Author.class ) + .setMaxResults( 1 ) + .setFirstResult( 1 ) + .getResultCount() ) ) + .invoke( count -> assertEquals( 2L, count ) ) + ); + } + @NamedNativeQuery( name = SQL_NAMED_QUERY, resultClass = Object[].class, From 13e315ba1853bf30af744a3709d1c5da8d532fa2 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 2 Aug 2024 10:13:43 +0200 Subject: [PATCH 05/12] [#1930] Add snapshot repository for the examples We didn't include it in the past because we wanted to keep the examples simpler. But, we need to be able to test the examples with the snapshots and it's disabled on the `main` branch as default --- examples/native-sql-example/build.gradle | 5 +++++ examples/session-example/build.gradle | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle index 2d6cd5b94e..fabdbc7fa6 100644 --- a/examples/native-sql-example/build.gradle +++ b/examples/native-sql-example/build.gradle @@ -7,6 +7,11 @@ buildscript { // Useful for local development, it should be disabled otherwise mavenLocal() } + // Optional: Enables snapshots repository + // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep + if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + } mavenCentral() } } diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle index 05e9dcc04a..7cf7fb5f69 100644 --- a/examples/session-example/build.gradle +++ b/examples/session-example/build.gradle @@ -7,6 +7,11 @@ buildscript { // Useful for local development, it should be disabled otherwise mavenLocal() } + // Optional: Enables snapshots repository + // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep + if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + } mavenCentral() } } From eb9d863ff5e206fd3a5bd00ac83d93dcfb4f827c Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 2 Aug 2024 10:16:53 +0200 Subject: [PATCH 06/12] [#1930] Update GitHub action to build and release snapshots And print the actual Hibernate ORM version used --- .github/workflows/build.yml | 71 ++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a7858c66a..b156350c55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,13 +1,18 @@ +# GitHub actions for branch testing the latest Hibernate ORM 6.5 snapshot name: Hibernate Reactive CI on: push: branches: - - main - tags: - - '2.*' + - wip/2.3 pull_request: - branches: main + branches: wip/2.3 + schedule: + # * is a special character in YAML, so you have to quote this string + # Run every hour at minute 25 + - cron: '25 * * * *' + # Allow running this workflow against a specific branch/tag + workflow_dispatch: # See https://github.com/hibernate/hibernate-orm/pull/4615 for a description of the behavior we're getting. concurrency: @@ -85,6 +90,8 @@ jobs: with: distribution: 'temurin' java-version: 11 + - name: Print the effective ORM version used + run: ./gradlew :${{ matrix.example }}:dependencyInsight --dependency org.hibernate.orm:hibernate-core - name: Run examples in '${{ matrix.example }}' on ${{ matrix.db }} run: ./gradlew :${{ matrix.example }}:runAllExamplesOn${{ matrix.db }} - name: Upload reports (if build failed) @@ -122,6 +129,8 @@ jobs: with: distribution: 'temurin' java-version: 11 + - name: Print the effective ORM version used + run: ./gradlew :hibernate-reactive-core:dependencyInsight --dependency org.hibernate.orm:hibernate-core - name: Build and Test with ${{ matrix.db }} run: ./gradlew build -PshowStandardOutput -Pdocker -Pdb=${{ matrix.db }} - name: Upload reports (if build failed) @@ -200,6 +209,8 @@ jobs: - name: Display exact version of JDK ${{ matrix.java.name }} run: | ${{ steps.testjdk-exportpath.outputs.path }}/bin/java -version + - name: Print the effective ORM version used + run: ./gradlew :hibernate-reactive-core:dependencyInsight --dependency org.hibernate.orm:hibernate-core - name: Build and Test with Java ${{ matrix.java.name }} run: | ./gradlew build -PshowStandardOutput -Pdocker -Ptest.jdk.version=${{ matrix.java.java_version_numeric }} \ @@ -212,37 +223,25 @@ jobs: name: reports-java${{ matrix.java.name }} path: './**/build/reports/' - release: - name: Release - if: github.event_name == 'push' && startsWith( github.ref, 'refs/tags/' ) + snapshot: + name: Create snapshot + if: github.event_name == 'push' && startsWith( github.ref, 'refs/heads/' ) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set git username and email - run: | - git config --global user.email "hibernate@users.noreply.github.com" - git config --global user.name "hibernate" - - name: Set up JDK 11 - uses: actions/setup-java@v2.2.0 - with: - distribution: 'temurin' - java-version: 11 - - name: Create artifacts - run: ./gradlew assemble - - name: Install SSH key - uses: shimataro/ssh-key-action@v2 - with: - key: ${{ secrets.HIBERNATE_ORG_SSH_KEY }} - name: id_rsa_hibernate.org - known_hosts: ${{ secrets.HIBERNATE_ORG_SSH_KNOWN_HOSTS }} - config: | - Host github.com - User hibernate - IdentityFile ~/.ssh/id_rsa_hibernate.org - - name: Publish documentation on Hibernate.org - run: ./gradlew publishDocumentation -PdocPublishBranch=production - - name: Publish artifacts to OSSRH, close repository and release - env: - ORG_GRADLE_PROJECT_sonatypeOssrhUser: ${{ secrets.SONATYPE_OSSRH_USER }} - ORG_GRADLE_PROJECT_sonatypeOssrhPassword: ${{ secrets.SONATYPE_OSSRH_PASSWORD }} - run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2.2.0 + with: + distribution: 'temurin' + java-version: 11 + - name: Create artifacts + run: ./gradlew assemble + - name: Detect the version of Hibernate Reactive + id: detect-version + run: | + sed -E 's/^projectVersion( *= *| +)([^ ]+)/::set-output name=version::\2/g' gradle/version.properties + - name: Publish snapshots to OSSRH, close repository and release + env: + ORG_GRADLE_PROJECT_sonatypeOssrhUser: ${{ secrets.SONATYPE_OSSRH_USER }} + ORG_GRADLE_PROJECT_sonatypeOssrhPassword: ${{ secrets.SONATYPE_OSSRH_PASSWORD }} + run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository From 660700fa60797076db23f3db7118cbc7c289244d Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 2 Aug 2024 11:52:49 +0200 Subject: [PATCH 07/12] [#1949] Update project version to 2.4.0-SNAPSHOT --- gradle/version.properties | 2 +- tooling/jbang/CockroachDBReactiveTest.java.qute | 2 +- tooling/jbang/Db2ReactiveTest.java.qute | 2 +- tooling/jbang/Example.java | 2 +- tooling/jbang/MariaDBReactiveTest.java.qute | 2 +- tooling/jbang/MySQLReactiveTest.java.qute | 2 +- tooling/jbang/PostgreSQLReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gradle/version.properties b/gradle/version.properties index 06df703b44..61c0b3764d 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=2.3.2-SNAPSHOT \ No newline at end of file +projectVersion=2.4.0-SNAPSHOT diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 9a0cff4a69..47b9322ddc 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.8} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:cockroachdb:1.19.8 diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute index 36c721e1d0..e675f30493 100755 --- a/tooling/jbang/Db2ReactiveTest.java.qute +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.8} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:db2:1.19.8 diff --git a/tooling/jbang/Example.java b/tooling/jbang/Example.java index b94c2f6c39..9d0f05b88d 100644 --- a/tooling/jbang/Example.java +++ b/tooling/jbang/Example.java @@ -9,7 +9,7 @@ //DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.8} //DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.8} //DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.8} -//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index 3521f28e32..bdda0c8432 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.8} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:mariadb:1.19.8 diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index f0fa8deb5c..e98df51ca5 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.8} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:mysql:1.19.8 diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index 6293b120b9..08d9fe62a3 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -7,7 +7,7 @@ //DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.8} //DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:postgresql:1.19.8 diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 08cf52002f..2d30f4ff94 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -10,7 +10,7 @@ //DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.8} //DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.8} //DEPS io.vertx:vertx-unit:${vertx.version:4.5.8} -//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.3.1.Final} +//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 //DEPS org.testcontainers:postgresql:1.19.8 From b73f9e0fc33f753633af13504c02076326ef61e8 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 2 Aug 2024 12:11:55 +0200 Subject: [PATCH 08/12] [#1930] Change properties to test ORM 6.6 snapshots --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 3d21137f9b..a6ee7e1590 100644 --- a/gradle.properties +++ b/gradle.properties @@ -37,12 +37,12 @@ enableSonatypeOpenSourceSnapshotsRep = true # Override default Hibernate ORM version # I'm not setting a version window here, because the examples will end up using the latest stable version # instead of the latest snapshot -hibernateOrmVersion = 6.5.3-SNAPSHOT +hibernateOrmVersion = 6.6.0-SNAPSHOT # Override default Hibernate ORM Gradle plugin version # Using the stable version because I don't know how to configure the build to download the snapshot version from # a remote repository -hibernateOrmGradlePluginVersion = 6.5.2.Final +hibernateOrmGradlePluginVersion = 6.6.0.CR1 # 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 From e1228efc532d17a559b043cf022284de02e17237 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 12 Jul 2024 10:39:17 +0200 Subject: [PATCH 09/12] [#1949] Upgrade Hibernate ORM to 6.6.0.CR2 To make this work I had to refactor the Cascade class. I don't know what wasn't working, but a test was failing and couldn't figure out what was wrong. I think the class now is more similar to the one in Hibernate ORM, it's easier to debug, logs the messages in the correct order, and it fixes the issues I had. --- README.md | 4 +- build.gradle | 2 +- .../reactive/engine/ReactiveActionQueue.java | 63 +- .../reactive/engine/impl/Cascade.java | 639 +++++++------ .../reactive/engine/impl/CascadingAction.java | 15 +- .../engine/impl/CascadingActions.java | 113 ++- .../ReactiveCollectionRecreateAction.java | 28 +- .../impl/ReactiveCollectionUpdateAction.java | 49 +- .../ReactiveEntityIdentityInsertAction.java | 5 + .../impl/ReactiveEntityInsertAction.java | 48 +- .../ReactiveEntityRegularInsertAction.java | 10 + .../ReactivePersistenceContextAdapter.java | 17 +- ...AbstractReactiveFlushingEventListener.java | 95 +- .../AbstractReactiveSaveEventListener.java | 31 +- .../DefaultReactiveDeleteEventListener.java | 211 +++-- ...tiveInitializeCollectionEventListener.java | 17 +- .../DefaultReactiveLockEventListener.java | 8 +- .../DefaultReactiveMergeEventListener.java | 8 +- .../DefaultReactiveRefreshEventListener.java | 21 +- .../ReactiveGeneratedValuesHelper.java | 7 +- .../id/impl/BlockingIdentifierGenerator.java | 39 +- .../id/impl/ReactiveGeneratorWrapper.java | 4 +- .../ReactiveIdentifierGeneratorFactory.java | 12 - .../internal/DatabaseSnapshotExecutor.java | 12 +- .../ReactiveSingleIdArrayLoadPlan.java | 7 +- ...ctiveSingleIdEntityLoaderStandardImpl.java | 6 +- .../ReactiveStandardBatchLoaderFactory.java | 6 +- .../ReactivePluralAttributeMapping.java | 548 +++++++++++ .../ReactiveToOneAttributeMapping.java | 17 +- .../ReactiveAbstractPersisterDelegate.java | 50 +- ...ReactiveJoinedSubclassEntityPersister.java | 2 +- .../ReactiveSingleTableEntityPersister.java | 16 +- .../ReactiveUnionSubclassEntityPersister.java | 3 +- .../ConcreteSqmSelectReactiveQueryPlan.java | 34 +- .../session/impl/ReactiveSessionImpl.java | 5 +- .../StandardReactiveSelectExecutor.java | 209 +++-- .../exec/spi/ReactiveRowProcessingState.java | 66 +- .../sql/exec/spi/ReactiveSelectExecutor.java | 30 +- .../sql/exec/spi/ReactiveValuesResultSet.java | 49 +- .../graph/ReactiveDomainResultsAssembler.java | 2 +- .../results/graph/ReactiveInitializer.java | 50 +- .../ReactiveCollectionDomainResult.java | 94 ++ .../internal/ReactiveEmbeddableFetchImpl.java | 40 + ...eactiveEmbeddableForeignKeyResultImpl.java | 27 + .../ReactiveEmbeddableInitializerImpl.java | 91 ++ ...veNonAggregatedIdentifierMappingFetch.java | 69 ++ ...ggregatedIdentifierMappingInitializer.java | 108 +++ .../ReactiveAbstractEntityInitializer.java | 219 ----- .../internal/ReactiveEntityAssembler.java | 35 +- ...ReactiveEntityDelayedFetchInitializer.java | 158 +++- .../ReactiveEntityFetchJoinedImpl.java | 70 +- .../ReactiveEntityFetchSelectImpl.java | 7 +- .../ReactiveEntityInitializerImpl.java | 853 ++++++++++++++++++ .../ReactiveEntityJoinedFetchInitializer.java | 133 --- .../ReactiveEntityResultInitializer.java | 64 -- ...titySelectFetchByUniqueKeyInitializer.java | 104 +-- .../ReactiveEntitySelectFetchInitializer.java | 319 +++---- ...veEntitySelectFetchInitializerBuilder.java | 95 +- .../ReactiveDeferredResultSetAccess.java | 9 +- .../ReactiveDirectResultSetAccess.java | 5 + .../ReactiveEntityDelayedFetchImpl.java | 33 +- .../internal/ReactiveEntityResultImpl.java | 75 +- .../internal/ReactiveInitializersList.java | 114 +-- .../internal/ReactiveResultSetAccess.java | 9 + .../internal/ReactiveResultsHelper.java | 145 +-- .../ReactiveRowTransformerArrayImpl.java | 40 + .../internal/ReactiveStandardRowReader.java | 452 ++++++++-- .../domain/ReactiveCircularFetchImpl.java | 31 +- .../spi/ReactiveListResultsConsumer.java | 126 ++- .../sql/results/spi/ReactiveRowReader.java | 14 - .../spi/ReactiveSingleResultConsumer.java | 4 +- .../ReactiveArrayJdbcTypeConstructor.java | 2 +- .../jdbc/ReactiveOracleArrayJdbcType.java | 81 +- .../EagerOneToOneAssociationTest.java | 78 +- .../org/hibernate/reactive/OrderTest.java | 2 + .../hibernate/reactive/SoftDeleteTest.java | 43 +- .../org/hibernate/reactive/UriConfigTest.java | 57 +- .../reactive/UriPoolConfiguration.java | 39 - .../ReactiveConnectionPoolTest.java | 2 +- .../reactive/containers/OracleDatabase.java | 5 +- .../schema/SchemaUpdateOracleTestBase.java | 2 +- 81 files changed, 4359 insertions(+), 2053 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java delete mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java diff --git a/README.md b/README.md index 23e8372851..120472ff0b 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Learn more at . Hibernate Reactive has been tested with: -- Java 11, 17, 20, 21 +- Java 11, 17, 20, 21, 22 - PostgreSQL 16 - MySQL 8 - MariaDB 11 @@ -37,7 +37,7 @@ Hibernate Reactive has been tested with: - CockroachDB v24 - MS SQL Server 2022 - Oracle 23 -- [Hibernate ORM][] 6.5.2.Final +- [Hibernate ORM][] 6.6.0.CR2 - [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.8 - [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.8 - [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.8 diff --git a/build.gradle b/build.gradle index 81279a569c..48f46cd765 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,7 @@ version = projectVersion // ./gradlew clean build -PhibernateOrmVersion=5.6.15-SNAPSHOT ext { if ( !project.hasProperty('hibernateOrmVersion') ) { - hibernateOrmVersion = '6.5.2.Final' + hibernateOrmVersion = '6.6.0.CR2' } if ( !project.hasProperty( 'hibernateOrmGradlePluginVersion' ) ) { // Same as ORM as default diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java index 144978be0c..034c9f583f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java @@ -241,16 +241,8 @@ public CompletionStage addAction(ReactiveEntityInsertAction action) { return addInsertAction( action ); } - private CompletionStage addInsertAction( ReactiveEntityInsertAction insert) { - CompletionStage ret = voidFuture(); - if ( insert.isEarlyInsert() ) { - // For early inserts, must execute inserts before finding non-nullable transient entities. - // TODO: find out why this is necessary - LOG.tracev( "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", insert ); - ret = ret.thenCompose( v -> executeInserts() ); - } - - return ret + private CompletionStage addInsertAction(ReactiveEntityInsertAction insert) { + return executeEarlyInsertsIfRequired( insert ) .thenCompose( v -> insert.reactiveFindNonNullableTransientEntities() ) .thenCompose( nonNullables -> { if ( nonNullables == null ) { @@ -270,40 +262,51 @@ private CompletionStage addInsertAction( ReactiveEntityInsertAction insert } ); } + private CompletionStage executeEarlyInsertsIfRequired(ReactiveEntityInsertAction insert) { + if ( insert.isEarlyInsert() ) { + // For early inserts, must execute inserts before finding non-nullable transient entities. + // TODO: find out why this is necessary + LOG.tracev( + "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", + insert + ); + return executeInserts(); + } + return voidFuture(); + } + private CompletionStage addResolvedEntityInsertAction(ReactiveEntityInsertAction insert) { - CompletionStage ret; if ( insert.isEarlyInsert() ) { - LOG.trace( "Executing insertions before resolved early-insert" ); - ret = executeInserts() - .thenCompose( v -> { + // For early inserts, must execute inserts before finding non-nullable transient entities. + LOG.tracev( "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", insert ); + return executeInserts().thenCompose( v -> { LOG.debug( "Executing identity-insert immediately" ); return execute( insert ); - } ); + } ) + .thenCompose( v -> postResolvedEntityInsertAction( insert ) ); } else { LOG.trace( "Adding resolved non-early insert action." ); OrderedActions.EntityInsertAction.ensureInitialized( this ); this.insertions.add( new ReactiveEntityInsertActionHolder( insert ) ); - ret = voidFuture(); + return postResolvedEntityInsertAction( insert ); } + } - return ret.thenCompose( v -> { - if ( !insert.isVeto() ) { - CompletionStage comp = insert.reactiveMakeEntityManaged(); - if ( unresolvedInsertions == null ) { - return comp; - } - else { - return comp.thenCompose( vv -> loop( + private CompletionStage postResolvedEntityInsertAction(ReactiveEntityInsertAction insert) { + if ( !insert.isVeto() ) { + return insert.reactiveMakeEntityManaged().thenCompose( v -> { + if ( unresolvedInsertions != null ) { + return loop( unresolvedInsertions.resolveDependentActions( insert.getInstance(), session.getSharedContract() ), resolvedAction -> addResolvedEntityInsertAction( (ReactiveEntityRegularInsertAction) resolvedAction ) - ) ); + ); } - } - else { - throw new ReactiveEntityActionVetoException( "The ReactiveEntityInsertAction was vetoed.", insert ); - } - } ); + return voidFuture(); + } ); + } + + throw new ReactiveEntityActionVetoException( "The ReactiveEntityInsertAction was vetoed.", insert ); } private static String[] convertTimestampSpaces(Serializable[] spaces) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java index bb0666ebee..36466b5565 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.engine.impl; -import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -23,6 +22,7 @@ import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; @@ -30,17 +30,21 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.ComponentType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; +import static java.lang.invoke.MethodHandles.lookup; +import static java.util.Collections.EMPTY_LIST; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.type.ForeignKeyDirection.TO_PARENT; @@ -54,35 +58,11 @@ * @author Gavin King * @see CascadingAction */ -public final class Cascade { +public final class Cascade { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + private static final Log LOG = make( Log.class, lookup() ); - private final CascadingAction action; - private final EntityPersister persister; - private final Object parent; - private final EventSource eventSource; - private final C context; - private CascadePoint cascadePoint; - - private CompletionStage stage = voidFuture(); - - /** - * @param persister The parent's entity persister - * @param parent The parent reference. - */ - public Cascade(final CascadingAction action, - final CascadePoint cascadePoint, - final EntityPersister persister, - final Object parent, - final C context, - final EventSource eventSource) { - this.action = action; - this.parent = parent; - this.persister = persister; - this.cascadePoint = cascadePoint; - this.eventSource = eventSource; - this.context = context; + private Cascade() { } public static CompletionStage fetchLazyAssociationsBeforeCascade( @@ -93,13 +73,16 @@ public static CompletionStage fetchLazyAssociationsBeforeCascade( CompletionStage beforeDelete = voidFuture(); if ( persister.hasCascades() ) { - CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); - Object[] state = persister.getValues( entity ); + final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); + final Object[] state = persister.getValues( entity ); for (int i = 0; i < cascadeStyles.length; i++) { if ( cascadeStyles[i].doCascade( action.delegate() ) ) { Object fetchable = state[i]; if ( !Hibernate.isInitialized( fetchable ) ) { - beforeDelete = beforeDelete.thenCompose( v -> session.unwrap(ReactiveSession.class).reactiveFetch( fetchable, true ) ); + beforeDelete = beforeDelete.thenCompose( v -> session + .unwrap( ReactiveSession.class ) + .reactiveFetch( fetchable, true ) + ); } } } @@ -107,165 +90,220 @@ public static CompletionStage fetchLazyAssociationsBeforeCascade( return beforeDelete; } - /** - * Cascade an action from the parent entity instance to all its children. - */ - public CompletionStage cascade() throws HibernateException { - return voidFuture().thenCompose( v -> { - CacheMode cacheMode = eventSource.getCacheMode(); - if ( action == CascadingActions.DELETE ) { - eventSource.setCacheMode( CacheMode.GET ); - } - eventSource.getPersistenceContextInternal().incrementCascadeLevel(); - return cascadeInternal().whenComplete( (vv, e) -> { - eventSource.getPersistenceContextInternal().decrementCascadeLevel(); - eventSource.setCacheMode( cacheMode ); - } ); - } ); + public static CompletionStage cascade( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final EntityPersister persister, + final Object parent, + final T anything) throws HibernateException { + CacheMode cacheMode = eventSource.getCacheMode(); + // In Hibernate actually set the cache before calling cascade for the remove, but this solution reduces some code + if ( action == CascadingActions.REMOVE ) { + eventSource.setCacheMode( CacheMode.GET ); + } + // Hibernate ORM actually increment/decrement the level before calling cascade, but keeping it here avoid extra + // code every time we need to cascade + eventSource.getPersistenceContextInternal().incrementCascadeLevel(); + return voidFuture() + .thenCompose( v -> cascadeInternal( action, cascadePoint, eventSource, persister, parent, anything ) ) + .whenComplete( (unused, throwable) -> { + eventSource.getPersistenceContextInternal().decrementCascadeLevel(); + eventSource.setCacheMode( cacheMode ); + } ); } - private CompletionStage cascadeInternal() throws HibernateException { - if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) { // performance opt + private static CompletionStage cascadeInternal( + CascadingAction action, + CascadePoint cascadePoint, + EventSource eventSource, + EntityPersister persister, + Object parent, + T anything) { + if ( persister.hasCascades() || action == CascadingActions.CHECK_ON_FLUSH ) { // performance opt final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { LOG.tracev( "Processing cascade {0} for: {1}", action, persister.getEntityName() ); } - final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); - final EntityEntry entry = persistenceContext.getEntry( parent ); - if ( entry != null && entry.getLoadedState() == null && entry.getStatus() == Status.MANAGED && persister.getBytecodeEnhancementMetadata() - .isEnhancedForLazyLoading() ) { - return voidFuture(); - } - - final Type[] types = persister.getPropertyTypes(); - final String[] propertyNames = persister.getPropertyNames(); - final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); - final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); - - for ( int i = 0; i < types.length; i++) { - final CascadeStyle style = cascadeStyles[ i ]; - final String propertyName = propertyNames[ i ]; - final boolean isUninitializedProperty = - hasUninitializedLazyProperties && - !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); - - final Type type = types[i]; - if ( style.doCascade( action.delegate() ) ) { - final Object child; - if ( isUninitializedProperty ) { - // parent is a bytecode enhanced entity. - // Cascade to an uninitialized, lazy value only if - // parent is managed in the PersistenceContext. - // If parent is a detached entity being merged, - // then parent will not be in the PersistenceContext - // (so lazy attributes must not be initialized). - if ( entry == null ) { - // parent was not in the PersistenceContext - continue; - } - if ( type.isCollectionType() ) { - // CollectionType#getCollection gets the PersistentCollection - // that corresponds to the uninitialized collection from the - // PersistenceContext. If not present, an uninitialized - // PersistentCollection will be added to the PersistenceContext. - // The action may initialize it later, if necessary. - // This needs to be done even when action.performOnLazyProperty() returns false. - final CollectionType collectionType = (CollectionType) type; - child = collectionType.getCollection( - collectionType.getKeyOfOwner( parent, eventSource ), - eventSource, - parent, - null - ); - } - else if ( type.isComponentType() ) { - // Hibernate does not support lazy embeddables, so this shouldn't happen. - throw new UnsupportedOperationException( - "Lazy components are not supported." - ); + return doCascade( action, cascadePoint, eventSource, persister, parent, anything ) + .thenRun( () -> { + if ( traceEnabled ) { + LOG.tracev( "Done processing cascade {0} for: {1}", action, persister.getEntityName() ); } - else if ( action.performOnLazyProperty() && type.isEntityType() ) { - // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() - // returns true. - LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() - .extractInterceptor( parent ); - child = interceptor.fetchAttribute( parent, propertyName ); + } ); + } + return voidFuture(); + } - } - else { - // Nothing to do, so just skip cascading to this lazy attribute. - continue; - } + /** + * Cascade an action from the parent entity instance to all its children. + */ + private static CompletionStage doCascade( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final EntityPersister persister, + final Object parent, + final T anything) throws HibernateException { + final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); + final EntityEntry entry = persistenceContext.getEntry( parent ); + + if ( entry != null + && entry.getLoadedState() == null + && entry.getStatus() == Status.MANAGED + && persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + return voidFuture(); + } + + final Type[] types = persister.getPropertyTypes(); + final String[] propertyNames = persister.getPropertyNames(); + final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); + final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); + + CompletionStage stage = voidFuture(); + for ( int i = 0; i < types.length; i++ ) { + final CascadeStyle style = cascadeStyles[i]; + final String propertyName = propertyNames[i]; + final boolean isUninitializedProperty = hasUninitializedLazyProperties + && !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); + + final Type type = types[i]; + if ( style.doCascade( action.delegate() ) ) { + if ( isUninitializedProperty ) { + // parent is a bytecode enhanced entity. + // Cascade to an uninitialized, lazy value only if + // parent is managed in the PersistenceContext. + // If parent is a detached entity being merged, + // then parent will not be in the PersistenceContext + // (so lazy attributes must not be initialized). + if ( entry == null ) { + // parent was not in the PersistenceContext + continue; + } + if ( type.isCollectionType() ) { + // CollectionType#getCollection gets the PersistentCollection + // that corresponds to the uninitialized collection from the + // PersistenceContext. If not present, an uninitialized + // PersistentCollection will be added to the PersistenceContext. + // The action may initialize it later, if necessary. + // This needs to be done even when action.performOnLazyProperty() returns false. + final CollectionType collectionType = (CollectionType) type; + Object child = collectionType.getCollection( + collectionType.getKeyOfOwner( parent, eventSource ), + eventSource, + parent, + null + ); + stage = stage.thenCompose( v -> cascadeProperty( + action, + cascadePoint, + eventSource, + null, + parent, + child, + type, + style, + propertyName, + anything, + false + ) ); + } + else if ( type.isComponentType() ) { + // Hibernate does not support lazy embeddables, so this shouldn't happen. + throw new UnsupportedOperationException( "Lazy components are not supported." ); + } + else if ( action.performOnLazyProperty() && type.isEntityType() ) { + // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() + // returns true. + LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() + .extractInterceptor( parent ); + stage = stage + .thenCompose( v -> (CompletionStage) interceptor + .fetchAttribute( parent, propertyName ) + ) + .thenCompose( actualChild -> cascadeProperty( + action, + cascadePoint, + eventSource, + null, + parent, + actualChild, + type, + style, + propertyName, + anything, + false + ) ); } else { - child = persister.getValue( parent, i ); + // Nothing to do, so just skip cascading to this lazy attribute. + continue; } - cascadeProperty( + } + else { + Object child = persister.getValue( parent, i ); + stage = stage.thenCompose( v -> cascadeProperty( + action, + cascadePoint, + eventSource, null, + parent, child, type, style, propertyName, + anything, false - ); - } - else { - if ( action.requiresNoCascadeChecking() ) { - noCascade( eventSource, parent, persister, types, i ); - } - // If the property is uninitialized, then there cannot be any orphans. - if ( action.deleteOrphans() && !isUninitializedProperty ) { - cascadeLogicalOneToOneOrphanRemoval( - null, - persister.getValue( parent, i ), - type, - style, - propertyName, - false - ); - } + ) ); } } - - if ( traceEnabled ) { - LOG.tracev( "Done processing cascade {0} for: {1}", action, persister.getEntityName() ); + else { + // If the property is uninitialized, then there cannot be any orphans. + if ( action.deleteOrphans() && !isUninitializedProperty ) { + final int index = i; + stage = stage.thenCompose( v -> cascadeLogicalOneToOneOrphanRemoval( + action, + eventSource, + null, + parent, + persister.getValue( parent, index ), + type, + style, + propertyName, + false + ) ); + } } } - return stage; } - private void noCascade( - final EventSource eventSource, - final Object parent, - final EntityPersister persister, - final Type[] types, - final int i) { - stage = stage.thenCompose( v -> action.noCascade( eventSource, parent, persister, types[i], i ) ); - } - /** * Cascade an action to the child or children */ - private void cascadeProperty( + private static CompletionStage cascadeProperty( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, List componentPath, + final Object parent, final Object child, final Type type, final CascadeStyle style, final String propertyName, + final T anything, final boolean isCascadeDeleteEnabled) throws HibernateException { if ( child != null ) { if ( type.isAssociationType() ) { final AssociationType associationType = (AssociationType) type; - if ( cascadeAssociationNow( cascadePoint, associationType ) ) { - cascadeAssociation( - componentPath, - child, - type, - style, - isCascadeDeleteEnabled - ); + final boolean unownedTransient = eventSource.getSessionFactory() + .getSessionFactoryOptions() + .isUnownedAssociationTransientCheck(); + if ( cascadeAssociationNow( action, cascadePoint, associationType, eventSource.getFactory(), unownedTransient ) ) { + final List path = componentPath; + return cascadeAssociation( action, cascadePoint, eventSource, path, parent, child, type, style, anything, isCascadeDeleteEnabled ) + .thenCompose( v -> cascadeLogicalOneToOne( action, eventSource, path, parent, child, type, style, propertyName, isCascadeDeleteEnabled ) ); } } else if ( type.isComponentType() ) { @@ -275,25 +313,39 @@ else if ( type.isComponentType() ) { if ( componentPath != null ) { componentPath.add( propertyName ); } - cascadeComponent( - componentPath, - child, - (CompositeType) type - ); + final List path = componentPath; + return cascadeComponent( action, cascadePoint, eventSource, path, parent, child, (CompositeType) type, anything ) + .thenRun( () -> { + if ( path != null ) { + path.remove( path.size() - 1 ); + } + } ) + .thenCompose( v -> cascadeLogicalOneToOne( action, eventSource, path, parent, child, type, style, propertyName, isCascadeDeleteEnabled ) ); } } + return cascadeLogicalOneToOne( action, eventSource, componentPath, parent, child, type, style, propertyName, isCascadeDeleteEnabled ); + } - cascadeLogicalOneToOneOrphanRemoval( - componentPath, - child, - type, - style, - propertyName, - isCascadeDeleteEnabled ); + private static CompletionStage cascadeLogicalOneToOne( + CascadingAction action, + EventSource eventSource, + List componentPath, + Object parent, + Object child, + Type type, + CascadeStyle style, + String propertyName, + boolean isCascadeDeleteEnabled) { + return isLogicalOneToOne( type ) + ? cascadeLogicalOneToOneOrphanRemoval( action, eventSource, componentPath, parent, child, type, style, propertyName, isCascadeDeleteEnabled ) + : voidFuture(); } - private void cascadeLogicalOneToOneOrphanRemoval( + private static CompletionStage cascadeLogicalOneToOneOrphanRemoval( + final CascadingAction action, + final EventSource eventSource, final List componentPath, + final Object parent, final Object child, final Type type, final CascadeStyle style, @@ -355,7 +407,7 @@ private void cascadeLogicalOneToOneOrphanRemoval( // associated one-to-one. if ( child == loadedValue ) { // do nothing - return; + return voidFuture(); } } @@ -369,24 +421,24 @@ private void cascadeLogicalOneToOneOrphanRemoval( ); } - final Object loaded = loadedValue; if ( type.isAssociationType() - && ( (AssociationType) type ).getForeignKeyDirection().equals(TO_PARENT) ) { + && ( (AssociationType) type ).getForeignKeyDirection().equals( TO_PARENT ) ) { // If FK direction is to-parent, we must remove the orphan *before* the queued update(s) // occur. Otherwise, replacing the association on a managed entity, without manually // nulling and flushing, causes FK constraint violations. - stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource ) - .reactiveRemoveOrphanBeforeUpdates( entityName, loaded ) ); + return ( (ReactiveSession) eventSource ) + .reactiveRemoveOrphanBeforeUpdates( entityName, loadedValue ); } else { // Else, we must delete after the updates. - stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource ) - .reactiveRemove( entityName, loaded, isCascadeDeleteEnabled, DeleteContext.create() ) ); + return ( (ReactiveSession) eventSource ) + .reactiveRemove( entityName, loadedValue, isCascadeDeleteEnabled, DeleteContext.create() ); } } } } } + return voidFuture(); } /** @@ -397,22 +449,55 @@ private void cascadeLogicalOneToOneOrphanRemoval( * * @return True if the attribute represents a logical one to one association */ - private boolean isLogicalOneToOne(Type type) { + private static boolean isLogicalOneToOne(Type type) { return type.isEntityType() && ( (EntityType) type ).isLogicalOneToOne(); } - private boolean cascadeAssociationNow(final CascadePoint cascadePoint, AssociationType associationType) { - return associationType.getForeignKeyDirection().cascadeNow( cascadePoint ); + private static boolean cascadeAssociationNow( + CascadingAction action, + CascadePoint cascadePoint, + AssociationType associationType, + SessionFactoryImplementor factory, + boolean unownedTransient) { + return associationType.getForeignKeyDirection().cascadeNow( cascadePoint ) + // For check on flush, we should only check unowned associations when strictness is enforced + && ( action != CascadingActions.CHECK_ON_FLUSH || unownedTransient || !isUnownedAssociation( associationType, factory ) ); } - private void cascadeComponent( - List componentPath, + private static boolean isUnownedAssociation(AssociationType associationType, SessionFactoryImplementor factory) { + if ( associationType.isEntityType() ) { + if ( associationType instanceof ManyToOneType ) { + final ManyToOneType manyToOne = (ManyToOneType) associationType; + // logical one-to-one + non-null unique key property name indicates unowned + return manyToOne.isLogicalOneToOne() && manyToOne.getRHSUniqueKeyPropertyName() != null; + } + else if ( associationType instanceof OneToOneType ) { + final OneToOneType oneToOne = (OneToOneType) associationType; + // constrained false + non-null unique key property name indicates unowned + return oneToOne.isNullable() && oneToOne.getRHSUniqueKeyPropertyName() != null; + } + } + else if ( associationType.isCollectionType() ) { + // for collections, we can ask the persister if we're on the inverse side + return ( (CollectionType) associationType ).isInverse( factory ); + } + return false; + } + + private static CompletionStage cascadeComponent( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, - final CompositeType componentType) { + final CompositeType componentType, + final T anything) { Object[] children = null; final Type[] types = componentType.getSubtypes(); final String[] propertyNames = componentType.getPropertyNames(); + CompletionStage stage = voidFuture(); for ( int i = 0; i < types.length; i++ ) { final CascadeStyle componentPropertyStyle = componentType.getCascadeStyle( i ); final String subPropertyName = propertyNames[i]; @@ -422,77 +507,106 @@ private void cascadeComponent( // Get children on demand. children = componentType.getPropertyValues( child, eventSource ); } - cascadeProperty( + final Object propertyChild = children[i]; + final Type propertyType = types[i]; + stage = stage.thenCompose( v -> cascadeProperty( + action, + cascadePoint, + eventSource, componentPath, - children[i], - types[i], + parent, + propertyChild, + propertyType, componentPropertyStyle, subPropertyName, + anything, false - ); + ) + ); } } + return stage; } - private void cascadeAssociation( - List componentPath, + private static CompletionStage cascadeAssociation( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, final Type type, final CascadeStyle style, + final T anything, final boolean isCascadeDeleteEnabled) { if ( type.isEntityType() || type.isAnyType() ) { - cascadeToOne( child, type, style, isCascadeDeleteEnabled ); + return cascadeToOne( action, eventSource, parent, child, type, style, anything, isCascadeDeleteEnabled ); } else if ( type.isCollectionType() ) { - cascadeCollection( + return cascadeCollection( + action, + cascadePoint, + eventSource, componentPath, + parent, child, style, + anything, (CollectionType) type ); } + return voidFuture(); } /** * Cascade an action to a collection */ - private void cascadeCollection( - List componentPath, + private static CompletionStage cascadeCollection( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, final CascadeStyle style, + final T anything, final CollectionType type) { - final CollectionPersister persister = - eventSource.getFactory().getMappingMetamodel() - .getCollectionDescriptor( type.getRole() ); + final CollectionPersister persister = eventSource.getFactory().getMappingMetamodel() + .getCollectionDescriptor( type.getRole() ); final Type elemType = persister.getElementType(); - - CascadePoint elementsCascadePoint = cascadePoint; - if ( cascadePoint == CascadePoint.AFTER_INSERT_BEFORE_DELETE ) { - cascadePoint = CascadePoint.AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION; - } - //cascade to current collection elements if ( elemType.isEntityType() || elemType.isAnyType() || elemType.isComponentType() ) { - cascadeCollectionElements( - componentPath, - child, - type, - style, - elemType, - persister.isCascadeDeleteEnabled() + return cascadeCollectionElements( + action, + cascadePoint == CascadePoint.AFTER_INSERT_BEFORE_DELETE + ? CascadePoint.AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION + : cascadePoint, + eventSource, + componentPath, + parent, + child, + type, + style, + elemType, + anything, + persister.isCascadeDeleteEnabled() + ); } - - cascadePoint = elementsCascadePoint; + return voidFuture(); } /** * Cascade an action to a to-one association or any type */ - private void cascadeToOne( + private static CompletionStage cascadeToOne( + final CascadingAction action, + final EventSource eventSource, + final Object parent, final Object child, final Type type, final CascadeStyle style, + final T anything, final boolean isCascadeDeleteEnabled) { final String entityName = type.isEntityType() ? ( (EntityType) type ).getAssociatedEntityName() @@ -501,47 +615,66 @@ private void cascadeToOne( //not really necessary, but good for consistency... final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); persistenceContext.addChildParent( child, parent ); - stage = stage.thenCompose( v -> action.cascade( eventSource, child, entityName, context, isCascadeDeleteEnabled ) ) - .whenComplete( (vv, e) -> persistenceContext.removeChildParent( child ) ); + return voidFuture() + .thenCompose( v -> action.cascade( eventSource, child, entityName, anything, isCascadeDeleteEnabled ) ) + .whenComplete( (v, e) -> persistenceContext.removeChildParent( child ) ); } + return voidFuture(); } /** * Cascade to the collection elements */ - private void cascadeCollectionElements( - List componentPath, + private static CompletionStage cascadeCollectionElements( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, final CollectionType collectionType, final CascadeStyle style, final Type elemType, + final T anything, final boolean isCascadeDeleteEnabled) throws HibernateException { - final boolean reallyDoCascade = style.reallyDoCascade( action.delegate() ) - && child != CollectionType.UNFETCHED_COLLECTION; - + final boolean reallyDoCascade = style.reallyDoCascade( action.delegate() ) && child != CollectionType.UNFETCHED_COLLECTION; if ( reallyDoCascade ) { final boolean traceEnabled = LOG.isTraceEnabled(); - if ( traceEnabled ) { - LOG.tracev( "Cascade {0} for collection: {1}", action, collectionType.getRole() ); - } - - final Iterator itr = action.getCascadableChildrenIterator( eventSource, collectionType, child ); - while ( itr.hasNext() ) { - cascadeProperty( - componentPath, - itr.next(), - elemType, - style, - null, - isCascadeDeleteEnabled - ); - } if ( traceEnabled ) { LOG.tracev( "Done cascade {0} for collection: {1}", action, collectionType.getRole() ); } + + final Iterator itr = action.getCascadableChildrenIterator( eventSource, collectionType, child ); + return loop( itr, (value, integer) -> cascadeProperty( + action, + cascadePoint, + eventSource, + componentPath, + parent, + value, + elemType, + style, + collectionType.getRole().substring( collectionType.getRole().lastIndexOf( '.' ) + 1 ), + anything, + isCascadeDeleteEnabled + ) ).thenRun( () -> { + if ( traceEnabled ) { + LOG.tracev( "Done cascade {0} for collection: {1}", action, collectionType.getRole() ); + } + } ).thenCompose( v -> doDeleteOrphans( action, eventSource, child, collectionType, style, elemType ) ); } + return doDeleteOrphans( action, eventSource, child, collectionType, style, elemType ); + } + + private static CompletionStage doDeleteOrphans( + CascadingAction action, + EventSource eventSource, + Object child, + CollectionType collectionType, + CascadeStyle style, + Type elemType) { final boolean deleteOrphans = style.hasOrphanDelete() && action.deleteOrphans() && elemType.isEntityType() @@ -558,39 +691,33 @@ private void cascadeCollectionElements( // 1. newly instantiated collections // 2. arrays (we can't track orphans for detached arrays) final String entityName = collectionType.getAssociatedEntityName( eventSource.getFactory() ); - deleteOrphans( entityName, (PersistentCollection) child ); - - if ( traceEnabled ) { - LOG.tracev( "Done deleting orphans for collection: {0}", collectionType.getRole() ); - } + return doDeleteOrphans( eventSource, entityName, (PersistentCollection) child ) + .thenRun( () -> { + if ( traceEnabled ) { + LOG.tracev( "Done deleting orphans for collection: {0}", collectionType.getRole() ); + } + } ); } + return voidFuture(); } /** * Delete any entities that were removed from the collection */ - private void deleteOrphans(String entityName, PersistentCollection pc) throws HibernateException { - //TODO: suck this logic into the collection! - final Collection orphans; + private static CompletionStage doDeleteOrphans(EventSource eventSource, String entityName, PersistentCollection pc) { + final Collection orphans = getOrphans( eventSource, entityName, pc ); + final ReactiveSession session = (ReactiveSession) eventSource; + return loop( orphans, Objects::nonNull, orphan -> { + LOG.tracev( "Deleting orphaned entity instance: {0}", entityName ); + return session.reactiveRemove( entityName, orphan, false, DeleteContext.create() ); + } ); + } + + private static Collection getOrphans(EventSource eventSource, String entityName, PersistentCollection pc) { if ( pc.wasInitialized() ) { final CollectionEntry ce = eventSource.getPersistenceContextInternal().getCollectionEntry( pc ); - if ( ce == null ) { - return; - } - orphans = ce.getOrphans( entityName, pc ); - } - else { - orphans = pc.getQueuedOrphans( entityName ); + return ce == null ? EMPTY_LIST : ce.getOrphans( entityName, pc ); } - - ReactiveSession session = (ReactiveSession) eventSource; - stage = stage.thenCompose( v -> loop( - orphans, - Objects::nonNull, - orphan -> { - LOG.tracev( "Deleting orphaned entity instance: {0}", entityName ); - return session.reactiveRemove( entityName, orphan, false, DeleteContext.create() ); - } - ) ); + return pc.getQueuedOrphans( entityName ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java index 7c6bdd749c..a8707cc116 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java @@ -5,6 +5,9 @@ */ package org.hibernate.reactive.engine.impl; +import java.util.Iterator; +import java.util.concurrent.CompletionStage; + import org.hibernate.HibernateException; import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.EntityPersister; @@ -12,9 +15,6 @@ import org.hibernate.type.CollectionType; import org.hibernate.type.Type; -import java.util.Iterator; -import java.util.concurrent.CompletionStage; - /** * A {@link Stage.Session reactive session} operation that may * be cascaded from a parent entity to its children. A non-blocking counterpart @@ -63,13 +63,17 @@ Iterator getCascadableChildrenIterator( */ boolean deleteOrphans(); - /** * Does the specified cascading action require verification of no cascade validity? * * @return True if this action requires no-cascade verification; false otherwise. + * + * @deprecated No longer used */ - boolean requiresNoCascadeChecking(); + @Deprecated(since = "2.4", forRemoval = true) + default boolean requiresNoCascadeChecking() { + return false; + } /** * Called (in the case of {@link #requiresNoCascadeChecking} returning true) to validate @@ -81,6 +85,7 @@ Iterator getCascadableChildrenIterator( * @param propertyType The property type * @param propertyIndex The index of the property within the owner. */ + @Deprecated(since = "2.4", forRemoval = true) CompletionStage noCascade(EventSource session, Object parent, EntityPersister persister, Type propertyType, int propertyIndex); /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java index d194fe312b..28289c022e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java @@ -10,10 +10,11 @@ import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; +import org.hibernate.Internal; import org.hibernate.LockOptions; +import org.hibernate.TransientObjectException; import org.hibernate.TransientPropertyValueException; import org.hibernate.engine.spi.EntityEntry; - import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.MergeContext; @@ -29,6 +30,9 @@ import org.hibernate.type.Type; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; +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.falseFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -48,7 +52,7 @@ private CascadingActions() { /** * @see org.hibernate.Session#remove(Object) */ - public static final CascadingAction DELETE = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.DELETE ) { + public static final CascadingAction REMOVE = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.REMOVE ) { @Override public CompletionStage cascade( EventSource session, @@ -150,6 +154,58 @@ public CompletionStage noCascade( } }; + @Internal + public static final CascadingAction CHECK_ON_FLUSH = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.CHECK_ON_FLUSH ) { + @Override + public CompletionStage cascade( + EventSource session, + Object child, + String entityName, + Void context, + boolean isCascadeDeleteEnabled) throws HibernateException { + if ( child != null ) { + return isChildTransient( session, child, entityName ).thenCompose( isTransient -> { + if ( isTransient ) { + return failedFuture( new TransientObjectException( + "persistent instance references an unsaved transient instance of '" + entityName + "' (save the transient instance before flushing)" ) ); + } + return voidFuture(); + } ); + } + return voidFuture(); + } + }; + + private static CompletionStage isChildTransient(EventSource session, Object child, String entityName) { + if ( isHibernateProxy( child ) ) { + // a proxy is always non-transient + // and ForeignKeys.isTransient() + // is not written to expect a proxy + // TODO: but the proxied entity might have been deleted! + return falseFuture(); + } + else { + final EntityEntry entry = session.getPersistenceContextInternal().getEntry( child ); + if ( entry != null ) { + // if it's associated with the session + // we are good, even if it's not yet + // inserted, since ordering problems + // are detected and handled elsewhere + boolean deleted = entry.getStatus().isDeletedOrGone(); + return completedFuture( deleted ); + } + else { + // TODO: check if it is a merged entity which has not yet been flushed + // Currently this throws if you directly reference a new transient + // instance after a call to merge() that results in its managed copy + // being scheduled for insertion, if the insert has not yet occurred. + // This is not terrible: it's more correct to "swap" the reference to + // point to the managed instance, but it's probably too heavy-handed. + return ForeignKeys.isTransient( entityName, child, null, session ); + } + } + } + /** * @see org.hibernate.Session#merge(Object) */ @@ -173,35 +229,35 @@ public CompletionStage cascade( * @see org.hibernate.Session#refresh(Object) */ public static final CascadingAction REFRESH = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.REFRESH ) { - @Override - public CompletionStage cascade( - EventSource session, - Object child, - String entityName, - RefreshContext context, - boolean isCascadeDeleteEnabled) - throws HibernateException { - LOG.tracev( "Cascading to refresh: {0}", entityName ); - return session.unwrap( ReactiveSession.class ).reactiveRefresh( child, context ); - } - }; + @Override + public CompletionStage cascade( + EventSource session, + Object child, + String entityName, + RefreshContext context, + boolean isCascadeDeleteEnabled) + throws HibernateException { + LOG.tracev( "Cascading to refresh: {0}", entityName ); + return session.unwrap( ReactiveSession.class ).reactiveRefresh( child, context ); + } + }; /** * @see org.hibernate.Session#lock(Object, org.hibernate.LockMode) */ public static final CascadingAction LOCK = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.LOCK ) { - @Override - public CompletionStage cascade( - EventSource session, - Object child, - String entityName, - LockOptions context, - boolean isCascadeDeleteEnabled) - throws HibernateException { - LOG.tracev( "Cascading to lock: {0}", entityName ); - return session.unwrap( ReactiveSession.class ).reactiveLock( child, context ); - } - }; + @Override + public CompletionStage cascade( + EventSource session, + Object child, + String entityName, + LockOptions context, + boolean isCascadeDeleteEnabled) + throws HibernateException { + LOG.tracev( "Cascading to lock: {0}", entityName ); + return session.unwrap( ReactiveSession.class ).reactiveLock( child, context ); + } + }; public abstract static class BaseCascadingAction implements CascadingAction { private final org.hibernate.engine.spi.CascadingAction delegate; @@ -225,11 +281,6 @@ public org.hibernate.engine.spi.CascadingAction delegate() { return delegate; } - @Override - public boolean requiresNoCascadeChecking() { - return delegate.requiresNoCascadeChecking(); - } - /** * @see BaseCascadingAction#noCascade(EventSource, Object, EntityPersister, Type, int) */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java index 5f28421831..008755086c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java @@ -31,22 +31,22 @@ public ReactiveCollectionRecreateAction(final PersistentCollection collection, f public CompletionStage reactiveExecute() { // this method is called when a new non-null collection is persisted // or when an existing (non-null) collection is moved to a new owner - final PersistentCollection collection = getCollection(); preRecreate(); + return ( (ReactiveCollectionPersister) getPersister() ) + .reactiveRecreate( getCollection(), getKey(), getSession() ) + .thenRun( this::afterRecreate ); + } + + private void afterRecreate() { + final PersistentCollection collection = getCollection(); final SharedSessionContractImplementor session = getSession(); - final ReactiveCollectionPersister persister = (ReactiveCollectionPersister) getPersister(); - return persister - .reactiveRecreate( collection, getKey(), session ) - .thenAccept( v -> { - // FIXME: I think we could move everything in a method reference call - session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); - evict(); - postRecreate(); - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.recreateCollection( getPersister().getRole() ); - } - } ); + session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); + evict(); + postRecreate(); + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.recreateCollection( getPersister().getRole() ); + } } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java index 21129a1e5b..eae872d561 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java @@ -26,6 +26,7 @@ import org.hibernate.stat.spi.StatisticsImplementor; import static org.hibernate.pretty.MessageHelper.collectionInfoString; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -55,51 +56,59 @@ public CompletionStage reactiveExecute() { final SharedSessionContractImplementor session = getSession(); final ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) getPersister(); final CollectionPersister persister = getPersister(); - final PersistentCollection collection = getCollection(); + final PersistentCollection collection = getCollection(); final boolean affectedByFilters = persister.isAffectedByEnabledFilters( session ); preUpdate(); - final CompletionStage updateStage; + return createUpdateStage( collection, affectedByFilters, reactivePersister, key, session, persister ) + .thenRun( () -> { + session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); + evict(); + postUpdate(); + + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.updateCollection( persister.getRole() ); + } + } ); + } + + private CompletionStage createUpdateStage( + PersistentCollection collection, + boolean affectedByFilters, + ReactiveCollectionPersister reactivePersister, + Object key, + SharedSessionContractImplementor session, + CollectionPersister persister) { if ( !collection.wasInitialized() ) { // If there were queued operations, they would have been processed // and cleared by now. // The collection should still be dirty. if ( !collection.isDirty() ) { - throw new AssertionFailure( "collection is not dirty" ); + return failedFuture( new AssertionFailure( "collection is not dirty" ) ); } //do nothing - we only need to notify the cache... - updateStage = voidFuture(); + return voidFuture(); } else if ( !affectedByFilters && collection.empty() ) { - updateStage = emptySnapshot ? voidFuture() : reactivePersister.reactiveRemove( key, session ); + return emptySnapshot ? voidFuture() : reactivePersister.reactiveRemove( key, session ); } else if ( collection.needsRecreate( persister ) ) { if ( affectedByFilters ) { - throw LOG.cannotRecreateCollectionWhileFilterIsEnabled( collectionInfoString( persister, collection, key, session ) ); + return failedFuture( LOG.cannotRecreateCollectionWhileFilterIsEnabled( collectionInfoString( persister, collection, key, session ) ) ); } - updateStage = emptySnapshot + return emptySnapshot ? reactivePersister.reactiveRecreate( collection, key, session ) : reactivePersister.reactiveRemove( key, session ) .thenCompose( v -> reactivePersister.reactiveRecreate( collection, key, session ) ); } else { - updateStage = voidFuture() - .thenCompose( v -> reactivePersister.reactiveDeleteRows( collection, key, session ) ) + return reactivePersister + .reactiveDeleteRows( collection, key, session ) .thenCompose( v -> reactivePersister.reactiveUpdateRows( collection, key, session ) ) .thenCompose( v -> reactivePersister.reactiveInsertRows( collection, key, session ) ); } - - return updateStage.thenAccept( v -> { - session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); - evict(); - postUpdate(); - - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.updateCollection( persister.getRole() ); - } - } ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java index 69168c3b19..3dc3a0dc1e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java @@ -118,6 +118,11 @@ public EntityKey getEntityKey() { return super.getEntityKey(); } + @Override + public void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects) { + super.addCollectionsByKeyToPersistenceContext( persistenceContext, objects ); + } + @Override public AbstractEntityInsertAction asAbstractEntityInsertAction() { return this; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java index ef48ba0060..4838e69717 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java @@ -11,9 +11,11 @@ import org.hibernate.action.internal.AbstractEntityInsertAction; import org.hibernate.engine.internal.NonNullableTransientDependencies; import org.hibernate.engine.internal.Nullability; -import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.ComparableExecutable; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; @@ -21,6 +23,7 @@ import org.hibernate.reactive.engine.ReactiveActionQueue; import org.hibernate.reactive.engine.ReactiveExecutable; +import static org.hibernate.engine.internal.Versioning.getVersion; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -37,6 +40,7 @@ public interface ReactiveEntityInsertAction extends ReactiveExecutable, Comparab Object getInstance(); String getEntityName(); Object[] getState(); + Object getRowId(); EntityPersister getPersister(); boolean isExecuted(); @@ -62,7 +66,7 @@ default CompletionStage reactiveNullifyTransientReferencesIfNotAlready() { if ( !areTransientReferencesNullified() ) { return new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), (SessionImplementor) getSession(), getPersister() ) .nullifyTransientReferences( getState() ) - .thenAccept( v-> { + .thenAccept( v -> { new Nullability( getSession() ).checkNullability( getState(), getPersister(), false ); setTransientReferencesNullified(); } ); @@ -79,27 +83,35 @@ default CompletionStage reactiveNullifyTransientReferencesIfNotAlready() { */ default CompletionStage reactiveMakeEntityManaged() { return reactiveNullifyTransientReferencesIfNotAlready() - .thenAccept( v -> getSession().getPersistenceContextInternal().addEntity( - getInstance(), - getPersister().isMutable() ? Status.MANAGED : Status.READ_ONLY, - getState(), - getEntityKey(), - Versioning.getVersion( getState(), getPersister() ), - LockMode.WRITE, - isExecuted(), - getPersister(), - isVersionIncrementDisabled() - )); + .thenAccept( v -> { + final Object version = getVersion( getState(), getPersister() ); + final PersistenceContext persistenceContextInternal = getSession().getPersistenceContextInternal(); + final EntityHolder entityHolder = persistenceContextInternal + .addEntityHolder( getEntityKey(), getInstance() ); + final EntityEntry entityEntry = persistenceContextInternal.addEntry( + getInstance(), + ( getPersister().isMutable() ? Status.MANAGED : Status.READ_ONLY ), + getState(), + getRowId(), + getEntityKey().getIdentifier(), + version, + LockMode.WRITE, + isExecuted(), + getPersister(), + isVersionIncrementDisabled() + ); + entityHolder.setEntityEntry( entityEntry ); + if ( isEarlyInsert() ) { + addCollectionsByKeyToPersistenceContext( persistenceContextInternal, getState() ); + } + }); } + void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects); + default CompletionStage reactiveFindNonNullableTransientEntities() { return ForeignKeys.findNonNullableTransientEntities( getPersister().getEntityName(), getInstance(), getState(), isEarlyInsert(), getSession() ); } AbstractEntityInsertAction asAbstractEntityInsertAction(); - - default int compareActionTo(ReactiveEntityInsertAction delegate) { - return asAbstractEntityInsertAction().compareTo( delegate.asAbstractEntityInsertAction() ); - } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java index f3598a09fb..062f0374f7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java @@ -124,6 +124,11 @@ public EntityKey getEntityKey() { return super.getEntityKey(); } + @Override + public void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects) { + super.addCollectionsByKeyToPersistenceContext( persistenceContext, objects ); + } + @Override public AbstractEntityInsertAction asAbstractEntityInsertAction() { return this; @@ -135,6 +140,11 @@ protected void markExecuted() { executed = true; } + @Override + public Object getRowId() { + return super.getRowId(); + } + @Override public boolean isExecuted() { return executed; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java index 66d0624e0f..33750cc0d9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java @@ -5,6 +5,11 @@ */ package org.hibernate.reactive.engine.impl; +import java.io.Serializable; +import java.util.HashMap; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + import org.hibernate.HibernateException; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.StatefulPersistenceContext; @@ -13,15 +18,13 @@ import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.session.ReactiveSession; -import java.io.Serializable; -import java.util.HashMap; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; - +import static java.lang.invoke.MethodHandles.lookup; 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.voidFuture; @@ -30,6 +33,8 @@ */ public class ReactivePersistenceContextAdapter extends StatefulPersistenceContext { + private static final Log LOG = make( Log.class, lookup() ); + private HashMap entitySnapshotsByKey; /** @@ -71,7 +76,7 @@ public void initializeNonLazyCollections() { @Deprecated @Override public Object[] getDatabaseSnapshot(Object id, EntityPersister persister) throws HibernateException { - throw new UnsupportedOperationException( "reactive persistence context" ); + throw LOG.nonReactiveMethodCall( "reactiveGetDatabaseSnapshot" ); } private static final Object[] NO_ROW = new Object[]{ StatefulPersistenceContext.NO_ROW }; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java index b43d2753cc..2772cc15df 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java @@ -5,8 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.reactive.util.impl.CompletionStages.loop; - import java.lang.invoke.MethodHandles; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -38,6 +36,9 @@ import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.session.ReactiveSession; +import static org.hibernate.reactive.engine.impl.CascadingActions.PERSIST_ON_FLUSH; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; + /** * Collects commons methods needed during the management of flush events. * @@ -80,14 +81,32 @@ private ReactiveActionQueue actionQueue(EventSource session) { * @throws HibernateException Error flushing caches to execution queues. */ protected CompletionStage flushEverythingToExecutions(FlushEvent event) throws HibernateException { - LOG.trace( "Flushing session" ); - final EventSource session = event.getSession(); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - session.getInterceptor().preFlush( persistenceContext.managedEntitiesIterator() ); + return preFlush( session, persistenceContext ) + .thenRun( () -> flushEverythingToExecutions( event, persistenceContext, session ) ); + } + protected void flushEverythingToExecutions(FlushEvent event, PersistenceContext persistenceContext, EventSource session) { + persistenceContext.setFlushing( true ); + try { + int entityCount = flushEntities( event, persistenceContext ); + int collectionCount = flushCollections( session, persistenceContext ); + + event.setNumberOfEntitiesProcessed( entityCount ); + event.setNumberOfCollectionsProcessed( collectionCount ); + } + finally { + persistenceContext.setFlushing( false); + } + + //some statistics + logFlushResults( event ); + } + + protected CompletionStage preFlush(EventSource session, PersistenceContext persistenceContext) { + session.getInterceptor().preFlush( persistenceContext.managedEntitiesIterator() ); return prepareEntityFlushes( session, persistenceContext ) .thenAccept( v -> { // we could move this inside if we wanted to @@ -97,20 +116,6 @@ protected CompletionStage flushEverythingToExecutions(FlushEvent event) th // now, any collections that are initialized // inside this block do not get updated - they // are ignored until the next flush - persistenceContext.setFlushing( true ); - try { - int entityCount = flushEntities( event, persistenceContext ); - int collectionCount = flushCollections( session, persistenceContext ); - - event.setNumberOfEntitiesProcessed(entityCount); - event.setNumberOfCollectionsProcessed(collectionCount); - } - finally { - persistenceContext.setFlushing( false ); - } - - //some statistics - logFlushResults( event ); } ); } @@ -145,7 +150,6 @@ protected void logFlushResults(FlushEvent event) { * and also apply orphan delete */ private CompletionStage prepareEntityFlushes(EventSource session, PersistenceContext persistenceContext) throws HibernateException { - LOG.debug( "Processing flush-time cascades" ); final PersistContext context = PersistContext.create(); @@ -154,7 +158,41 @@ private CompletionStage prepareEntityFlushes(EventSource session, Persiste return loop( entries, index -> flushable( entries[index].getValue() ), - index -> cascadeOnFlush( session, entries[index].getValue().getPersister(), entries[index].getKey(), context ) ); + index -> cascadeOnFlush( + session, + entries[index].getValue().getPersister(), + entries[index].getKey(), + context + ) + ) + // perform these checks after all cascade persist events have been + // processed, so that all entities which will be persisted are + // persistent when we do the check (I wonder if we could move this + // into Nullability, instead of abusing the Cascade infrastructure) + .thenCompose( v -> loop( + entries, + index -> flushable( entries[index].getValue() ), + index -> Cascade.cascade( + CascadingActions.CHECK_ON_FLUSH, + CascadePoint.BEFORE_FLUSH, + session, + entries[index].getValue().getPersister(), + entries[index].getKey(), + null + ) + ) ); + } + + private CompletionStage cascadeOnFlush( + EventSource session, + EntityPersister persister, + Object object, + PersistContext anything) + throws HibernateException { + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + persistenceContext.incrementCascadeLevel(); + return Cascade.cascade( PERSIST_ON_FLUSH, CascadePoint.BEFORE_FLUSH, session, persister, object, anything ) + .whenComplete( (unused, throwable) -> persistenceContext.decrementCascadeLevel() ); } private static boolean flushable(EntityEntry entry) { @@ -299,19 +337,6 @@ private int flushCollections(final EventSource session, final PersistenceContext return count; } - private CompletionStage cascadeOnFlush( - EventSource session, - EntityPersister persister, - Object object, - PersistContext copiedAlready) - throws HibernateException { - return new Cascade<>( - CascadingActions.PERSIST_ON_FLUSH, - CascadePoint.BEFORE_FLUSH, - persister, object, copiedAlready, session - ).cascade(); - } - /** * 1. Recreate the collection key to collection map * 2. rebuild the collection entries diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java index 54fd9b23a6..e9d68b00e3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java @@ -43,6 +43,7 @@ import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; +import static org.hibernate.engine.internal.ManagedTypeHelper.processIfManagedEntity; import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker; import static org.hibernate.engine.internal.Versioning.getVersion; import static org.hibernate.engine.internal.Versioning.seedVersion; @@ -226,6 +227,7 @@ protected CompletionStage reactivePerformSave( callbackRegistry.preCreate( entity ); processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); + processIfManagedEntity( entity, managedEntity -> managedEntity.$$_hibernate_setUseTracker( true ) ); if ( persister.getGenerator() instanceof Assigned ) { id = persister.getIdentifier( entity, source ); @@ -277,6 +279,11 @@ private CompletionStage generateEntityKey(Object id, EntityPersister return failedFuture( new NonUniqueObjectException( id, persister.getEntityName() ) ); } } + else if ( persistenceContext.containsDeletedUnloadedEntityKey( key ) ) { + return source.unwrap( ReactiveSession.class ) + .reactiveForceFlush( persistenceContext.getEntry( old ) ) + .thenApply( v -> key ); + } else { return completedFuture( key ); } @@ -331,6 +338,10 @@ protected CompletionStage reactivePerformSaveOrReplicate( false ); + if ( original.getLoadedState() != null ) { + persistenceContext.getEntityHolder( key ).setEntityEntry( original ); + } + return cascadeBeforeSave( source, persister, entity, context ) .thenCompose( v -> addInsertAction( // We have to do this after cascadeBeforeSave completes, @@ -409,7 +420,7 @@ private CompletionStage addInsertAction( boolean useIdentityColumn, EventSource source, boolean shouldDelayIdentityInserts) { - final ReactiveActionQueue actionQueue = source.unwrap(ReactiveSession.class).getReactiveActionQueue(); + final ReactiveActionQueue actionQueue = source.unwrap( ReactiveSession.class ).getReactiveActionQueue(); if ( useIdentityColumn ) { final ReactiveEntityIdentityInsertAction insert = new ReactiveEntityIdentityInsertAction( values, entity, persister, false, source, shouldDelayIdentityInserts @@ -438,11 +449,14 @@ protected CompletionStage cascadeBeforeSave( Object entity, C context) { // cascade-save to many-to-one BEFORE the parent is saved - return new Cascade<>( + return Cascade.cascade( getCascadeReactiveAction(), CascadePoint.BEFORE_INSERT_AFTER_DELETE, - persister, entity, context, source - ).cascade(); + source, + persister, + entity, + context + ); } /** @@ -459,11 +473,14 @@ protected CompletionStage cascadeAfterSave( Object entity, C context) { // cascade-save to collections AFTER the collection owner was saved - return new Cascade<>( + return Cascade.cascade( getCascadeReactiveAction(), CascadePoint.AFTER_INSERT_BEFORE_DELETE, - persister, entity, context, source - ).cascade(); + source, + persister, + entity, + context + ); } protected abstract CascadingAction getCascadeReactiveAction(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java index 2c7784d7b9..66151f03bd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java @@ -5,10 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.pretty.MessageHelper.infoString; -import static org.hibernate.reactive.engine.impl.Cascade.fetchLazyAssociationsBeforeCascade; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; @@ -17,6 +13,7 @@ import org.hibernate.TransientObjectException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.classic.Lifecycle; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.spi.EntityEntry; @@ -57,6 +54,10 @@ import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; +import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.engine.impl.Cascade.fetchLazyAssociationsBeforeCascade; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + /** * A reactive {@link org.hibernate.event.internal.DefaultDeleteEventListener}. */ @@ -82,8 +83,8 @@ public void wasJpaBootstrap(boolean wasJpaBootstrap) { * Handle the given delete event. * * @param event The delete event to be handled. - * @deprecated only the reactive version is supported * @see #reactiveOnDelete(DeleteEvent) + * @deprecated only the reactive version is supported */ @Deprecated @Override @@ -92,8 +93,8 @@ public void onDelete(DeleteEvent event) { } /** - * @deprecated only the reactive version is supported * @see #reactiveOnDelete(DeleteEvent, DeleteContext) + * @deprecated only the reactive version is supported */ @Deprecated @Override @@ -105,7 +106,6 @@ public void onDelete(DeleteEvent event, DeleteContext transientEntities) throws * Handle the given delete event. * * @param event The delete event to be handled. - * */ @Override public CompletionStage reactiveOnDelete(DeleteEvent event) throws HibernateException { @@ -115,26 +115,23 @@ public CompletionStage reactiveOnDelete(DeleteEvent event) throws Hibernat /** * Handle the given delete event. This is the cascaded form. * - * @param event The delete event. + * @param event The delete event. * @param transientEntities The cache of entities already deleted - * */ @Override - public CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities) throws HibernateException { - if ( optimizeUnloadedDelete( event ) ) { - return voidFuture(); - } - else { - final EventSource source = event.getSession(); - Object object = event.getObject(); - if ( object instanceof CompletionStage ) { - final CompletionStage stage = (CompletionStage) object; - return stage.thenCompose( objectEvent -> fetchAndDelete( event, transientEntities, source, objectEvent ) ); - } - else { - return fetchAndDelete( event, transientEntities, source, object); - } - } + public CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities) + throws HibernateException { + return !optimizeUnloadedDelete( event ) + ? fetchAndDelete( event, transientEntities ) + : voidFuture(); + } + + private CompletionStage delete(DeleteEvent event, DeleteContext transientEntities, Object entity) { + final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal(); + final EntityEntry entityEntry = persistenceContext.getEntry( entity ); + return entityEntry == null + ? deleteTransientInstance( event, transientEntities, entity ) + : deletePersistentInstance( event, transientEntities, entity, entityEntry ); } private boolean optimizeUnloadedDelete(DeleteEvent event) { @@ -157,12 +154,12 @@ && canBeDeletedWithoutLoading( source, persister ) ) { if ( persister.hasOwnedCollections() ) { // we're deleting an unloaded proxy with collections - for ( Type type : persister.getPropertyTypes() ) { //TODO: when we enable this for subclasses use getSubclassPropertyTypeClosure() + for ( Type type : persister.getPropertyTypes() ) { deleteOwnedCollections( type, id, source ); } } - ((ReactiveSession) source).getReactiveActionQueue() + ( (ReactiveSession) source ).getReactiveActionQueue() .addAction( new ReactiveEntityDeleteAction( id, persister, source ) ); } return true; @@ -174,10 +171,10 @@ && canBeDeletedWithoutLoading( source, persister ) ) { private static void deleteOwnedCollections(Type type, Object key, EventSource session) { final MappingMetamodelImplementor mappingMetamodel = session.getFactory().getMappingMetamodel(); - final ReactiveActionQueue actionQueue = ((ReactiveSession) session).getReactiveActionQueue(); + final ReactiveActionQueue actionQueue = ( (ReactiveSession) session ).getReactiveActionQueue(); if ( type.isCollectionType() ) { final String role = ( (CollectionType) type ).getRole(); - final CollectionPersister persister = mappingMetamodel.getCollectionDescriptor(role); + final CollectionPersister persister = mappingMetamodel.getCollectionDescriptor( role ); if ( !persister.isInverse() ) { actionQueue.addAction( new ReactiveCollectionRemoveAction( persister, key, session ) ); } @@ -190,11 +187,9 @@ else if ( type.isComponentType() ) { } } - private CompletionStage fetchAndDelete( - DeleteEvent event, - DeleteContext transientEntities, - EventSource source, - Object objectEvent) { + private CompletionStage fetchAndDelete(DeleteEvent event, DeleteContext transientEntities) { + final Object objectEvent = event.getObject(); + final EventSource source = event.getSession(); boolean detached = event.getEntityName() != null ? !source.contains( event.getEntityName(), objectEvent ) : !source.contains( objectEvent ); @@ -204,20 +199,21 @@ private CompletionStage fetchAndDelete( } //Object entity = persistenceContext.unproxyAndReassociate( event.getObject() ); + return ( (ReactiveSession) source ) + .reactiveFetch( objectEvent, true ) + .thenCompose( entity -> delete( event, transientEntities, entity ) ); - return ( (ReactiveSession) source ).reactiveFetch( objectEvent, true ) - .thenCompose( entity -> reactiveOnDelete( event, transientEntities, entity ) ); } - private CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities, Object entity) { - final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal(); - final EntityEntry entityEntry = persistenceContext.getEntry( entity ); - if ( entityEntry == null ) { - return deleteTransientInstance( event, transientEntities, entity ); - } - else { - return deletePersistentInstance( event, transientEntities, entity, entityEntry ); + protected boolean invokeDeleteLifecycle(EventSource session, Object entity, EntityPersister persister) { + if ( persister.implementsLifecycle() ) { + LOG.debug( "Calling onDelete()" ); + if ( ( (Lifecycle) entity ).onDelete( session ) ) { + LOG.debug( "Deletion vetoed by onDelete()" ); + return true; + } } + return false; } private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) { @@ -225,7 +221,7 @@ private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteC final EventSource source = event.getSession(); - final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity); + final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); return ForeignKeys.isTransient( persister.getEntityName(), entity, null, source.getSession() ) .thenCompose( trans -> { if ( trans ) { @@ -253,7 +249,7 @@ private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteC final EntityEntry entry = persistenceContext.addEntity( entity, persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, - persister.getValues( entity ), + persister.getValues( entity ), key, version, LockMode.NONE, @@ -276,22 +272,12 @@ private CompletionStage deletePersistentInstance( LOG.trace( "Deleting a persistent instance" ); final EventSource source = event.getSession(); if ( entityEntry.getStatus().isDeletedOrGone() - || source.getPersistenceContextInternal() - .containsDeletedUnloadedEntityKey( entityEntry.getEntityKey() ) ) { + || source.getPersistenceContextInternal().containsDeletedUnloadedEntityKey( entityEntry.getEntityKey() ) ) { LOG.trace( "Object was already deleted" ); return voidFuture(); } else { - return delete( - event, - transientEntities, - source, - entity, - entityEntry.getPersister(), - entityEntry.getId(), - entityEntry.getVersion(), - entityEntry - ); + return delete( event, transientEntities, source, entity, entityEntry.getPersister(), entityEntry.getId(), entityEntry.getVersion(), entityEntry ); } } @@ -305,20 +291,23 @@ private CompletionStage delete( Object version, EntityEntry entry) { callbackRegistry.preRemove( entity ); - return deleteEntity( - source, - entity, - entry, - event.isCascadeDeleteEnabled(), - event.isOrphanRemovalBeforeUpdates(), - persister, - transientEntities - ) - .thenAccept( v -> { - if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { - persister.resetIdentifier( entity, id, version, source ); - } - } ); + if ( !invokeDeleteLifecycle( source, entity, persister ) ) { + return deleteEntity( + source, + entity, + entry, + event.isCascadeDeleteEnabled(), + event.isOrphanRemovalBeforeUpdates(), + persister, + transientEntities + ) + .thenAccept( v -> { + if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { + persister.resetIdentifier( entity, id, version, source ); + } + } ); + } + return voidFuture(); } /** @@ -326,13 +315,13 @@ private CompletionStage delete( */ private boolean canBeDeletedWithoutLoading(EventSource source, EntityPersister persister) { return source.getInterceptor() == EmptyInterceptor.INSTANCE - && !persister.implementsLifecycle() - && !persister.hasSubclasses() //TODO: should be unnecessary, using EntityPersister.getSubclassPropertyTypeClosure(), etc - && !persister.hasCascadeDelete() - && !persister.hasNaturalIdentifier() - && !persister.hasCollectionNotReferencingPK() - && !hasRegisteredRemoveCallbacks( persister ) - && !hasCustomEventListeners( source ); + && !persister.implementsLifecycle() + && !persister.hasSubclasses() //TODO: should be unnecessary, using EntityPersister.getSubclassPropertyTypeClosure(), etc + && !persister.hasCascadeDelete() + && !persister.hasNaturalIdentifier() + && !persister.hasCollectionNotReferencingPK() + && !hasRegisteredRemoveCallbacks( persister ) + && !hasCustomEventListeners( source ); } private static boolean hasCustomEventListeners(EventSource source) { @@ -340,16 +329,16 @@ private static boolean hasCustomEventListeners(EventSource source) { // Bean Validation adds a PRE_DELETE listener // and Envers adds a POST_DELETE listener return fss.eventListenerGroup_PRE_DELETE.count() > 0 - || fss.eventListenerGroup_POST_DELETE.count() > 1 - || fss.eventListenerGroup_POST_DELETE.count() == 1 - && !(fss.eventListenerGroup_POST_DELETE.listeners().iterator().next() - instanceof PostDeleteEventListenerStandardImpl); + || fss.eventListenerGroup_POST_DELETE.count() > 1 + || fss.eventListenerGroup_POST_DELETE.count() == 1 + && !( fss.eventListenerGroup_POST_DELETE.listeners().iterator().next() + instanceof PostDeleteEventListenerStandardImpl ); } private boolean hasRegisteredRemoveCallbacks(EntityPersister persister) { final Class mappedClass = persister.getMappedClass(); return callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.PRE_REMOVE ) - || callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE ); + || callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE ); } /** @@ -385,11 +374,11 @@ private void disallowDeletionOfDetached(DeleteEvent event) { * passed to remove operation in which case cascades still need to be * performed. * - * @param session The session which is the source of the event - * @param entity The entity being delete processed - * @param persister The entity persister + * @param session The session which is the source of the event + * @param entity The entity being delete processed + * @param persister The entity persister * @param transientEntities A cache of already visited transient entities - * (to avoid infinite recursion). + * (to avoid infinite recursion). */ protected CompletionStage deleteTransientEntity( EventSource session, @@ -412,12 +401,12 @@ protected CompletionStage deleteTransientEntity( * really perform it; just schedules an action/execution with the * {@link org.hibernate.engine.spi.ActionQueue} for execution during flush. * - * @param session The originating session - * @param entity The entity to delete - * @param entityEntry The entity's entry in the {@link PersistenceContext} + * @param session The originating session + * @param entity The entity to delete + * @param entityEntry The entity's entry in the {@link PersistenceContext} * @param isCascadeDeleteEnabled Is delete cascading enabled? - * @param persister The entity persister. - * @param transientEntities A cache of already deleted entities. + * @param persister The entity persister. + * @param transientEntities A cache of already deleted entities. */ protected CompletionStage deleteEntity( final EventSource session, @@ -442,7 +431,7 @@ protected CompletionStage deleteEntity( final Object[] deletedState = createDeletedState( persister, entity, currentState, session ); entityEntry.setDeletedState( deletedState ); - session.getInterceptor().onDelete( + session.getInterceptor().onRemove( entity, entityEntry.getId(), deletedState, @@ -525,7 +514,7 @@ private Object[] createDeletedState( final String[] propertyNames = persister.getPropertyNames(); final BytecodeEnhancementMetadata enhancementMetadata = persister.getBytecodeEnhancementMetadata(); - for ( int i = 0; i < types.length; i++) { + for ( int i = 0; i < types.length; i++ ) { if ( types[i].isCollectionType() && !enhancementMetadata.isAttributeLoaded( parent, propertyNames[i] ) ) { final CollectionType collectionType = (CollectionType) types[i]; final CollectionPersister collectionDescriptor = persister.getFactory().getMappingMetamodel() @@ -533,7 +522,12 @@ private Object[] createDeletedState( if ( collectionDescriptor.needsRemove() || collectionDescriptor.hasCache() ) { final Object keyOfOwner = collectionType.getKeyOfOwner( parent, eventSource.getSession() ); // This will make sure that a CollectionEntry exists - deletedState[i] = collectionType.getCollection( keyOfOwner, eventSource.getSession(), parent, false ); + deletedState[i] = collectionType.getCollection( + keyOfOwner, + eventSource.getSession(), + parent, + false + ); } else { deletedState[i] = currentState[i]; @@ -556,13 +550,15 @@ protected CompletionStage cascadeBeforeDelete( Object entity, DeleteContext transientEntities) throws HibernateException { // cascade-delete to collections BEFORE the collection owner is deleted - return fetchLazyAssociationsBeforeCascade( CascadingActions.DELETE, persister, entity, session ) - .thenCompose( - v -> new Cascade<>( - CascadingActions.DELETE, - CascadePoint.AFTER_INSERT_BEFORE_DELETE, - persister, entity, transientEntities, session - ).cascade() + return fetchLazyAssociationsBeforeCascade( CascadingActions.REMOVE, persister, entity, session ) + .thenCompose( v -> Cascade.cascade( + CascadingActions.REMOVE, + CascadePoint.AFTER_INSERT_BEFORE_DELETE, + session, + persister, + entity, + transientEntities + ) ); } @@ -572,10 +568,13 @@ protected CompletionStage cascadeAfterDelete( Object entity, DeleteContext transientEntities) throws HibernateException { // cascade-delete to many-to-one AFTER the parent was deleted - return new Cascade<>( - CascadingActions.DELETE, + return Cascade.cascade( + CascadingActions.REMOVE, CascadePoint.BEFORE_INSERT_AFTER_DELETE, - persister, entity, transientEntities, session - ).cascade(); + session, + persister, + entity, + transientEntities + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java index 5704f3fafe..20466abd29 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java @@ -27,6 +27,7 @@ import org.hibernate.stat.spi.StatisticsImplementor; import static org.hibernate.pretty.MessageHelper.collectionInfoString; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; public class DefaultReactiveInitializeCollectionEventListener implements InitializeCollectionEventListener { @@ -47,7 +48,7 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection final CollectionEntry ce = source.getPersistenceContextInternal().getCollectionEntry( collection ); if ( ce == null ) { - throw LOG.collectionWasEvicted(); + return failedFuture( LOG.collectionWasEvicted() ); } if ( collection.wasInitialized() ) { @@ -58,10 +59,7 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection final CollectionPersister loadedPersister = ce.getLoadedPersister(); final Object loadedKey = ce.getLoadedKey(); if ( LOG.isTraceEnabled() ) { - LOG.tracev( - "Initializing collection {0}", - collectionInfoString( loadedPersister, collection, loadedKey, source ) - ); + LOG.tracev( "Initializing collection {0}", collectionInfoString( loadedPersister, collection, loadedKey, source ) ); LOG.trace( "Checking second-level cache" ); } @@ -129,8 +127,7 @@ private boolean initializeCollectionFromCache( PersistentCollection collection, SessionImplementor source) { - if ( source.getLoadQueryInfluencers().hasEnabledFilters() - && persister.isAffectedByEnabledFilters( source ) ) { + if ( source.getLoadQueryInfluencers().hasEnabledFilters() && persister.isAffectedByEnabledFilters( source ) ) { LOG.trace( "Disregarding cached version (if any) of collection due to enabled filters" ); return false; } @@ -160,12 +157,12 @@ private boolean initializeCollectionFromCache( return false; } - final CollectionCacheEntry cacheEntry = (CollectionCacheEntry) - persister.getCacheEntryStructure().destructure( ce, factory ); + final CollectionCacheEntry cacheEntry = (CollectionCacheEntry) persister + .getCacheEntryStructure().destructure( ce, factory ); final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); cacheEntry.assemble( collection, persister, persistenceContext.getCollectionOwner( id, persister ) ); - persistenceContext.getCollectionEntry( collection ).postInitialize( collection ); + persistenceContext.getCollectionEntry( collection ).postInitialize( collection, source ); return true; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java index 7a356def1c..7832298e06 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java @@ -113,14 +113,14 @@ private CompletionStage lockEntry( } private CompletionStage cascadeOnLock(LockEvent event, EntityPersister persister, Object entity) { - return new Cascade<>( + return Cascade.cascade( CascadingActions.LOCK, CascadePoint.AFTER_LOCK, + event.getSession(), persister, entity, - event.getLockOptions(), - event.getSession() - ).cascade(); + event.getLockOptions() + ); } /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java index 6a23b8d574..7f258a1d46 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java @@ -588,14 +588,14 @@ protected CompletionStage cascadeOnMerge( final Object entity, final MergeContext copyCache ) { - return new Cascade<>( + return Cascade.cascade( getCascadeReactiveAction(), CascadePoint.BEFORE_MERGE, + source, persister, entity, - copyCache, - source - ).cascade(); + copyCache + ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java index a687545363..f5e5eb1b4a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java @@ -5,9 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.pretty.MessageHelper.infoString; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -23,7 +20,6 @@ import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.internal.EvictVisitor; @@ -46,6 +42,9 @@ import org.hibernate.type.CompositeType; import org.hibernate.type.Type; +import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + /** * A reactific {@link org.hibernate.event.internal.DefaultRefreshEventListener}. */ @@ -150,11 +149,11 @@ private CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshConte return cascadeRefresh( source, persister, entity, refreshedAlready ) .thenCompose( v -> { if ( entry != null ) { - final EntityKey key = source.generateEntityKey( id, persister ); - persistenceContext.removeEntity( key ); + persistenceContext.removeEntityHolder( entry.getEntityKey() ); if ( persister.hasCollections() ) { - new EvictVisitor( source, entity ).process( entity, persister ); + new EvictVisitor( source, object ).process( object, persister ); } + persistenceContext.removeEntry( object ); } evictEntity( entity, persister, id, source ); @@ -282,14 +281,14 @@ private CompletionStage cascadeRefresh( EntityPersister persister, Object object, RefreshContext refreshedAlready) { - return new Cascade( + return Cascade.cascade( CascadingActions.REFRESH, CascadePoint.BEFORE_REFRESH, + source, persister, object, - refreshedAlready, - source - ).cascade(); + refreshedAlready + ); } private void evictCachedCollections(EntityPersister persister, Object id, EventSource source) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java index 6501f68b6b..8854f22d82 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java @@ -14,7 +14,6 @@ import org.hibernate.HibernateException; import org.hibernate.Internal; -import org.hibernate.LockOptions; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.EventType; @@ -172,6 +171,7 @@ private static CompletionStage readGeneratedValues( null, null, QueryOptions.NONE, + true, mappingProducer.resolve( directResultSetAccess, session.getLoadQueryInfluencers(), @@ -209,11 +209,10 @@ public boolean shouldReturnProxies() { ); final ReactiveRowReader rowReader = ReactiveResultsHelper.createRowReader( - executionContext, - LockOptions.NONE, + session.getSessionFactory(), RowTransformerArrayImpl.instance(), Object[].class, - jdbcValues.getValuesMapping() + jdbcValues ); final ReactiveRowProcessingState rowProcessingState = new ReactiveRowProcessingState( valuesProcessingState, executionContext, rowReader, jdbcValues ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java index d15de1779a..224410853f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java @@ -10,9 +10,9 @@ import io.vertx.core.net.impl.pool.CombinerExecutor; import io.vertx.core.net.impl.pool.Executor; import io.vertx.core.net.impl.pool.Task; + import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.session.ReactiveConnectionSupplier; -import org.hibernate.reactive.util.impl.CompletionStages; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -29,7 +29,6 @@ * @author Gavin King * @author Davide D'Alto * @author Sanne Grinovero - * */ public abstract class BlockingIdentifierGenerator implements ReactiveIdentifierGenerator { @@ -83,40 +82,41 @@ public CompletionStage generate(ReactiveConnectionSupplier connectionSuppl //(this does actually hit a synchronization, but it's extremely short) final long next = next(); if ( next != -1 ) { - return CompletionStages.completedFuture( next ); + return completedFuture( next ); } //Another special case we need to deal with; this is an unlikely configuration, but //if it were to happen we should be better off with direct execution rather than using //the co-operative executor: if ( getBlockSize() <= 1 ) { - return nextHiValue( connectionSupplier ) - .thenApply( i -> next( i ) ); + return nextHiValue( connectionSupplier ).thenApply( this::next ); } final CompletableFuture resultForThisEventLoop = new CompletableFuture<>(); final CompletableFuture result = new CompletableFuture<>(); - executor.submit( new GenerateIdAction( connectionSupplier, result ) ); final Context context = Vertx.currentContext(); - result.whenComplete( (id,t) -> { + executor.submit( new GenerateIdAction( connectionSupplier, result ) ); + result.whenComplete( (id, t) -> { final Context newContext = Vertx.currentContext(); //Need to be careful in resuming processing on the same context as the original //request, potentially having to switch back if we're no longer executing on the same: if ( newContext != context ) { if ( t != null ) { - context.runOnContext( ( v ) -> resultForThisEventLoop.completeExceptionally( t ) ); - } else { - context.runOnContext( ( v ) -> resultForThisEventLoop.complete( id ) ); + context.runOnContext( v -> resultForThisEventLoop.completeExceptionally( t ) ); + } + else { + context.runOnContext( v -> resultForThisEventLoop.complete( id ) ); } } else { if ( t != null ) { resultForThisEventLoop.completeExceptionally( t ); - } else { + } + else { resultForThisEventLoop.complete( id ); } } - }); + } ); return resultForThisEventLoop; } @@ -126,8 +126,8 @@ private final class GenerateIdAction implements Executor.Action private final CompletableFuture result; public GenerateIdAction(ReactiveConnectionSupplier connectionSupplier, CompletableFuture result) { - this.connectionSupplier = Objects.requireNonNull(connectionSupplier); - this.result = Objects.requireNonNull(result); + this.connectionSupplier = Objects.requireNonNull( connectionSupplier ); + this.result = Objects.requireNonNull( result ); } @Override @@ -137,21 +137,22 @@ public Task execute(GeneratorState state) { // We don't need to update or initialize the hi // value in the table, so just increment the lo // value and return the next id in the block - completedFuture( local ) - .whenComplete( this::acceptAsReturnValue ); + completedFuture( local ).whenComplete( this::acceptAsReturnValue ); return null; - } else { + } + else { nextHiValue( connectionSupplier ) .whenComplete( (newlyGeneratedHi, throwable) -> { if ( throwable != null ) { result.completeExceptionally( throwable ); - } else { + } + else { //We ignore the state argument as we actually use the field directly //for convenience, but they are the same object. executor.submit( stateIgnored -> { result.complete( next( newlyGeneratedHi ) ); return null; - }); + } ); } } ); return null; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java index fd7cda2c59..6edfed3dfb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java @@ -16,6 +16,8 @@ import java.util.Objects; import java.util.concurrent.CompletionStage; +import static java.util.function.Function.identity; + /** * @author Gavin King */ @@ -46,7 +48,7 @@ public void initialize(SqlStringGenerationContext context) { @Override public CompletionStage generate(ReactiveConnectionSupplier session, Object entity) { - return reactiveGenerator.generate( session, entity ).thenApply( id -> id ); + return reactiveGenerator.generate( session, entity ).thenApply( identity() ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java index 6a8741de0e..f1c45e42f0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java @@ -81,18 +81,6 @@ public Generator createIdentifierGenerator(String strategy, Type type, Propertie throw new MappingException( String.format( "Not an id generator [entity-name=%s]", entityName ) ); } - //TODO: deleteme, after update to ORM - @Override - public Class getIdentifierGeneratorClass(String strategy) { - try { - return super.getIdentifierGeneratorClass( strategy ); - } - catch ( MappingException ignored ) { - // happens because the class does not implement Generator - return generatorClassForName( strategy ); - } - } - protected Class generatorClassForName(String strategy) { try { return serviceRegistry.getService( ClassLoaderService.class ).classForName( strategy ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java index cb71be1b8e..3fdd5acf11 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java @@ -44,7 +44,7 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; -import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl; +import org.hibernate.sql.results.internal.RowTransformerArrayImpl; import org.hibernate.type.StandardBasicTypes; import org.jboss.logging.Logger; @@ -181,7 +181,13 @@ CompletionStage loadDatabaseSnapshot(Object id, SharedSessionContractI // FIXME: use JdbcServices return StandardReactiveSelectExecutor.INSTANCE - .list( jdbcSelect, jdbcParameterBindings, new BaseExecutionContext( session ), RowTransformerDatabaseSnapshotImpl.instance(), ReactiveListResultsConsumer.UniqueSemantic.FILTER ) + .list( + jdbcSelect, + jdbcParameterBindings, + new BaseExecutionContext( session ), + RowTransformerArrayImpl.instance(), + ReactiveListResultsConsumer.UniqueSemantic.FILTER + ) .thenApply( list -> { assert list != null; final int size = list.size(); @@ -191,7 +197,7 @@ CompletionStage loadDatabaseSnapshot(Object id, SharedSessionContractI return null; } - final Object[] entitySnapshot = (Object[]) list.get( 0 ); + final Object[] entitySnapshot = list.get( 0 ); // The result of this method is treated like the entity state array which doesn't include the id // So we must exclude it from the array if ( entitySnapshot.length == 1 ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java index a0f1135a76..c973d9713d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java @@ -11,13 +11,15 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.reactive.sql.results.internal.ReactiveRowTransformerArrayImpl; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcParametersList; -import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl; import org.hibernate.sql.results.spi.RowTransformer; /** * A reactive load plan for loading an array of state by a single restrictive part. + * + * @see org.hibernate.loader.ast.internal.SingleIdArrayLoadPlan */ public class ReactiveSingleIdArrayLoadPlan extends ReactiveSingleIdLoadPlan { @@ -33,7 +35,6 @@ public ReactiveSingleIdArrayLoadPlan( @Override protected RowTransformer> getRowTransformer() { - return RowTransformerDatabaseSnapshotImpl.instance(); + return ReactiveRowTransformerArrayImpl.asRowTransformer(); } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java index bdb9d692ad..684894cf28 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java @@ -30,11 +30,11 @@ public class ReactiveSingleIdEntityLoaderStandardImpl extends SingleIdEntityL public ReactiveSingleIdEntityLoaderStandardImpl( EntityMappingType entityDescriptor, - SessionFactoryImplementor sessionFactory) { + LoadQueryInfluencers loadQueryInfluencers) { super( entityDescriptor, - sessionFactory, - (lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, sessionFactory ) + loadQueryInfluencers, + (lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, influencers.getSessionFactory() ) ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java index 2fd588727a..5d5c03fb56 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java @@ -25,8 +25,10 @@ public class ReactiveStandardBatchLoaderFactory implements BatchLoaderFactory { @Override public EntityBatchLoader createEntityBatchLoader( - int domainBatchSize, EntityMappingType entityDescriptor, - SessionFactoryImplementor factory) { + int domainBatchSize, + EntityMappingType entityDescriptor, + LoadQueryInfluencers loadQueryInfluencers) { + SessionFactoryImplementor factory = loadQueryInfluencers.getSessionFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); // NOTE : don't use the EntityIdentifierMapping here because it will not be known until later diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java index f7dca4f5c0..26a3cbf12b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java @@ -5,16 +5,44 @@ */ package org.hibernate.reactive.metamodel.mapping.internal; +import java.util.function.BiConsumer; +import java.util.function.IntFunction; + +import org.hibernate.cache.MutableCacheKeyBuilder; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.IndexedConsumer; +import org.hibernate.metamodel.mapping.AssociationKey; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.SelectableConsumer; +import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl; +import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.reactive.sql.results.graph.collection.internal.ReactiveCollectionDomainResult; import org.hibernate.reactive.sql.results.graph.collection.internal.ReactiveEagerCollectionFetch; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.ast.spi.SqlAstCreationState; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableGroupProducer; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; +import org.hibernate.type.descriptor.java.JavaType; public class ReactivePluralAttributeMapping extends PluralAttributeMappingImpl implements PluralAttributeMapping { @@ -58,4 +86,524 @@ protected Fetch buildEagerCollectionFetch( creationState ); } + + @Override + public ForeignKeyDescriptor getKeyDescriptor() { + if ( !super.getKeyDescriptor().isEmbedded() ) { + // Hibernate ORM has a check like this that will fail if ForeignKeyDescriptor is not an instance of + // SimpleForeignKeyDescriptor: see LoadSelectBuilder#applySubSelectRestriction line 1115 + return new ReactiveSimpleForeignKeyDescriptor( (SimpleForeignKeyDescriptor) super.getKeyDescriptor() ); + } + return new ReactiveForeignKeyDescriptor( super.getKeyDescriptor() ); + } + + private static DomainResult convert(DomainResult keyDomainResult) { + if ( keyDomainResult instanceof EmbeddableForeignKeyResultImpl ) { + return new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) keyDomainResult ); + } + return keyDomainResult; + } + + private static class ReactiveSimpleForeignKeyDescriptor extends SimpleForeignKeyDescriptor { + + protected ReactiveSimpleForeignKeyDescriptor(SimpleForeignKeyDescriptor original) { + super( original ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( super.createKeyDomainResult( navigablePath, targetTableGroup, fetchParent, creationState ) ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + Nature fromSide, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( super.createKeyDomainResult( navigablePath, targetTableGroup, fromSide, fetchParent, creationState ) ); + } + } + + private static class ReactiveForeignKeyDescriptor implements ForeignKeyDescriptor { + + private final ForeignKeyDescriptor delegate; + + public ReactiveForeignKeyDescriptor(ForeignKeyDescriptor delegate) { + this.delegate = delegate; + } + + @Override + public String getPartName() { + return delegate.getPartName(); + } + + @Override + public String getKeyTable() { + return delegate.getKeyTable(); + } + + @Override + public String getTargetTable() { + return delegate.getTargetTable(); + } + + @Override + public ValuedModelPart getKeyPart() { + return delegate.getKeyPart(); + } + + @Override + public ValuedModelPart getTargetPart() { + return delegate.getTargetPart(); + } + + @Override + public boolean isKeyPart(ValuedModelPart modelPart) { + return delegate.isKeyPart( modelPart ); + } + + @Override + public ValuedModelPart getPart(Nature nature) { + return delegate.getPart( nature ); + } + + @Override + public Side getKeySide() { + return delegate.getKeySide(); + } + + @Override + public Side getTargetSide() { + return delegate.getTargetSide(); + } + + @Override + public Side getSide(Nature nature) { + return delegate.getSide( nature ); + } + + @Override + public String getContainingTableExpression() { + return delegate.getContainingTableExpression(); + } + + @Override + public int compare(Object key1, Object key2) { + return delegate.compare( key1, key2 ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( delegate.createKeyDomainResult( + navigablePath, + targetTableGroup, + fetchParent, + creationState + ) ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + Nature fromSide, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( delegate.createKeyDomainResult( + navigablePath, + targetTableGroup, + fromSide, + fetchParent, + creationState + ) ); + } + + @Override + public DomainResult createTargetDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return delegate.createTargetDomainResult( navigablePath, targetTableGroup, fetchParent, creationState ); + } + + @Override + public DomainResult createDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + String resultVariable, + DomainResultCreationState creationState) { + return delegate.createDomainResult( navigablePath, targetTableGroup, resultVariable, creationState ); + } + + @Override + public Predicate generateJoinPredicate( + TableGroup targetSideTableGroup, + TableGroup keySideTableGroup, + SqlAstCreationState creationState) { + return delegate.generateJoinPredicate( targetSideTableGroup, keySideTableGroup, creationState ); + } + + @Override + public Predicate generateJoinPredicate( + TableReference targetSideReference, + TableReference keySideReference, + SqlAstCreationState creationState) { + return delegate.generateJoinPredicate( targetSideReference, keySideReference, creationState ); + } + + @Override + public boolean isSimpleJoinPredicate(Predicate predicate) { + return delegate.isSimpleJoinPredicate( predicate ); + } + + @Override + public SelectableMapping getSelectable(int columnIndex) { + return delegate.getSelectable( columnIndex ); + } + + @Override + public int forEachSelectable(int offset, SelectableConsumer consumer) { + return delegate.forEachSelectable( offset, consumer ); + } + + @Override + public Object getAssociationKeyFromSide( + Object targetObject, + Nature nature, + SharedSessionContractImplementor session) { + return delegate.getAssociationKeyFromSide( targetObject, nature, session ); + } + + @Override + public Object getAssociationKeyFromSide( + Object targetObject, + Side side, + SharedSessionContractImplementor session) { + return delegate.getAssociationKeyFromSide( targetObject, side, session ); + } + + @Override + public int visitKeySelectables(int offset, SelectableConsumer consumer) { + return delegate.visitKeySelectables( offset, consumer ); + } + + @Override + public int visitKeySelectables(SelectableConsumer consumer) { + return delegate.visitKeySelectables( consumer ); + } + + @Override + public int visitTargetSelectables(int offset, SelectableConsumer consumer) { + return delegate.visitTargetSelectables( offset, consumer ); + } + + @Override + public int visitTargetSelectables(SelectableConsumer consumer) { + return delegate.visitTargetSelectables( consumer ); + } + + @Override + public ForeignKeyDescriptor withKeySelectionMapping( + ManagedMappingType declaringType, + TableGroupProducer declaringTableGroupProducer, + IntFunction selectableMappingAccess, + MappingModelCreationProcess creationProcess) { + return delegate.withKeySelectionMapping( + declaringType, + declaringTableGroupProducer, + selectableMappingAccess, + creationProcess + ); + } + + @Override + public ForeignKeyDescriptor withTargetPart(ValuedModelPart targetPart) { + return delegate.withTargetPart( targetPart ); + } + + @Override + public AssociationKey getAssociationKey() { + return delegate.getAssociationKey(); + } + + @Override + public boolean hasConstraint() { + return delegate.hasConstraint(); + } + + @Override + public boolean isEmbedded() { + return delegate.isEmbedded(); + } + + @Override + public boolean isVirtual() { + return delegate.isVirtual(); + } + + @Override + public NavigableRole getNavigableRole() { + return delegate.getNavigableRole(); + } + + @Override + public MappingType getPartMappingType() { + return delegate.getPartMappingType(); + } + + @Override + public JavaType getJavaType() { + return delegate.getJavaType(); + } + + @Override + public boolean isEntityIdentifierMapping() { + return delegate.isEntityIdentifierMapping(); + } + + @Override + public boolean hasPartitionedSelectionMapping() { + return delegate.hasPartitionedSelectionMapping(); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState) { + delegate.applySqlSelections( navigablePath, tableGroup, creationState ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + delegate.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); + } + + @Override + public int forEachSelectable(SelectableConsumer consumer) { + return delegate.forEachSelectable( consumer ); + } + + @Override + public AttributeMapping asAttributeMapping() { + return delegate.asAttributeMapping(); + } + + @Override + public EntityMappingType asEntityMappingType() { + return delegate.asEntityMappingType(); + } + + @Override + public BasicValuedModelPart asBasicValuedModelPart() { + return delegate.asBasicValuedModelPart(); + } + + @Override + public int breakDownJdbcValues( + Object domainValue, + JdbcValueConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.breakDownJdbcValues( domainValue, valueConsumer, session ); + } + + @Override + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); + } + + @Override + public int decompose( + Object domainValue, + JdbcValueConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.decompose( domainValue, valueConsumer, session ); + } + + @Override + public int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.decompose( domainValue, offset, x, y, valueConsumer, session ); + } + + @Override + public EntityMappingType findContainingEntityMapping() { + return delegate.findContainingEntityMapping(); + } + + @Override + public boolean areEqual(Object one, Object other, SharedSessionContractImplementor session) { + return delegate.areEqual( one, other, session ); + } + + @Override + public int getJdbcTypeCount() { + return delegate.getJdbcTypeCount(); + } + + @Override + public int forEachJdbcType(IndexedConsumer action) { + return delegate.forEachJdbcType( action ); + } + + @Override + public Object disassemble(Object value, SharedSessionContractImplementor session) { + return delegate.disassemble( value, session ); + } + + @Override + public void addToCacheKey( + MutableCacheKeyBuilder cacheKey, + Object value, + SharedSessionContractImplementor session) { + delegate.addToCacheKey( cacheKey, value, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, x, y, valuesConsumer, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + int offset, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, valuesConsumer, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + int offset, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, x, y, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + int offset, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, offset, x, y, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + int offset, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, offset, valuesConsumer, session ); + } + + @Override + public JdbcMapping getJdbcMapping(int index) { + return delegate.getJdbcMapping( index ); + } + + @Override + public JdbcMapping getSingleJdbcMapping() { + return delegate.getSingleJdbcMapping(); + } + + @Override + public int forEachJdbcType(int offset, IndexedConsumer action) { + return delegate.forEachJdbcType( offset, action ); + } + + @Override + public void forEachInsertable(SelectableConsumer consumer) { + delegate.forEachInsertable( consumer ); + } + + @Override + public void forEachNonFormula(SelectableConsumer consumer) { + delegate.forEachNonFormula( consumer ); + } + + @Override + public void forEachUpdatable(SelectableConsumer consumer) { + delegate.forEachUpdatable( consumer ); + } + + @Override + public MappingType getMappedType() { + return delegate.getMappedType(); + } + + @Override + public JavaType getExpressibleJavaType() { + return delegate.getExpressibleJavaType(); + } + + @Override + public X treatAs(Class targetType) { + return delegate.treatAs( targetType ); + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java index 09acc9bb98..47b4199de1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java @@ -9,6 +9,7 @@ import org.hibernate.metamodel.mapping.AttributeMetadata; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchSelectImpl; import org.hibernate.reactive.sql.results.internal.ReactiveEntityDelayedFetchImpl; @@ -19,6 +20,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; import org.hibernate.sql.results.graph.entity.EntityFetch; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; @@ -81,16 +83,25 @@ protected EntityFetch buildEntityDelayedFetch( ToOneAttributeMapping fetchedAttribute, NavigablePath navigablePath, DomainResult keyResult, - boolean selectByUniqueKey) { + boolean selectByUniqueKey, + DomainResultCreationState creationState) { return new ReactiveEntityDelayedFetchImpl( fetchParent, fetchedAttribute, navigablePath, - keyResult, - selectByUniqueKey + convert( keyResult ), + selectByUniqueKey, + creationState ); } + private static DomainResult convert(DomainResult keyResult) { + if ( keyResult instanceof EmbeddableForeignKeyResultImpl ) { + return new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) keyResult ); + } + return keyResult; + } + @Override public ReactiveToOneAttributeMapping copy( ManagedMappingType declaringType, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java index bfb19ea0da..4c1c67d9ec 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java @@ -15,6 +15,7 @@ import org.hibernate.FetchMode; import org.hibernate.LockOptions; +import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -27,7 +28,9 @@ import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ManagedMappingType; @@ -35,6 +38,7 @@ import org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; @@ -54,11 +58,14 @@ import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.metamodel.mapping.internal.ReactivePluralAttributeMapping; import org.hibernate.reactive.metamodel.mapping.internal.ReactiveToOneAttributeMapping; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch; import org.hibernate.reactive.sql.results.internal.ReactiveEntityResultImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.type.BasicType; import org.hibernate.type.EntityType; @@ -155,7 +162,7 @@ private static ReactiveSingleIdEntityLoader buildSingleIdEntityLoader( } } - return new ReactiveSingleIdEntityLoaderStandardImpl<>( entityDescriptor, factory ); + return new ReactiveSingleIdEntityLoaderStandardImpl<>( entityDescriptor, new LoadQueryInfluencers( factory ) ); } private static ReactiveSingleIdEntityLoader createBatchingIdEntityLoader( @@ -312,4 +319,45 @@ public AttributeMapping buildPluralAttributeMapping( ReactivePluralAttributeMapping::new ); } + + public EntityIdentifierMapping convertEntityIdentifierMapping(EntityIdentifierMapping entityIdentifierMapping) { + if ( entityIdentifierMapping instanceof NonAggregatedIdentifierMappingImpl ) { + return new ReactiveNonAggregatedIdentifierMappingImpl( (NonAggregatedIdentifierMappingImpl) entityIdentifierMapping ); + } + return entityIdentifierMapping; + } + + private static class ReactiveNonAggregatedIdentifierMappingImpl extends NonAggregatedIdentifierMappingImpl { + + public ReactiveNonAggregatedIdentifierMappingImpl( + EntityPersister entityPersister, + RootClass bootEntityDescriptor, + String rootTableName, + String[] rootTableKeyColumnNames, + MappingModelCreationProcess creationProcess) { + super( entityPersister, bootEntityDescriptor, rootTableName, rootTableKeyColumnNames, creationProcess ); + } + + public ReactiveNonAggregatedIdentifierMappingImpl(NonAggregatedIdentifierMappingImpl entityIdentifierMapping) { + super( entityIdentifierMapping ); + } + + @Override + public Fetch generateFetch( + FetchParent fetchParent, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + return new ReactiveNonAggregatedIdentifierMappingFetch( + fetchablePath, + this, + fetchParent, + fetchTiming, + selected, + creationState + ); + } + } } 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 b9bfdfcb24..8eca0a6f4d 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 @@ -388,7 +388,7 @@ public CompletionStage reactiveLoadEntityIdByNaturalId(Object[] orderedN } @Override - protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) { + protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) { throw LOG.nonReactiveMethodCall( "getReactiveUniqueKeyLoader" ); } 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 22454b546b..e16523523e 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 @@ -8,6 +8,7 @@ import java.sql.PreparedStatement; import java.util.List; import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; import org.hibernate.FetchMode; import org.hibernate.HibernateException; @@ -31,6 +32,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; @@ -279,6 +281,18 @@ public void merge( throw LOG.nonReactiveMethodCall( "mergeReactive" ); } + @Override + protected EntityIdentifierMapping generateIdentifierMapping( + Supplier templateInstanceCreator, + PersistentClass bootEntityDescriptor, + MappingModelCreationProcess creationProcess) { + return reactiveDelegate.convertEntityIdentifierMapping( super.generateIdentifierMapping( + templateInstanceCreator, + bootEntityDescriptor, + creationProcess + ) ); + } + /** * Process properties generated with an insert * @@ -436,7 +450,7 @@ public CompletionStage reactiveLoadByUniqueKey(String propertyName, Obje } @Override - protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) { + protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) { throw LOG.nonReactiveMethodCall( "getReactiveUniqueKeyLoader" ); } 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 7792a27e96..51eb33fad2 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 @@ -210,7 +210,6 @@ public Object insert(Object[] fields, Object object, SharedSessionContractImplem throw LOG.nonReactiveMethodCall( "insertReactive" ); } - /** * @see #insertReactive(Object[], Object, SharedSessionContractImplementor) */ @@ -423,7 +422,7 @@ public CompletionStage reactiveLoadByUniqueKey(String propertyName, Obje } @Override - protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) { + protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) { throw new UnsupportedOperationException( "use the reactive method: #getReactiveUniqueKeyLoader(String)" ); } 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 858e2d2098..821f2c7f36 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 @@ -40,6 +40,7 @@ import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; @@ -132,22 +133,35 @@ private static CompletionStage executeQueryInterpreter( .thenCompose( subSelectFetchKeyHandler -> session .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE - .executeQuery( jdbcSelect, - jdbcParameterBindings, - ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), - rowTransformer, - null, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareQueryStatement( sql, false, null ), - resultsConsumer + .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, + JdbcParameterBindings jdbcParameterBindings) { + final Expression fetchExpression = sqmInterpretation.selectStatement.getQueryPart() + .getFetchClauseExpression(); + return fetchExpression != null + ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) + : -1; + } + @Override public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, DomainQueryExecutionContext executionContext) { throw new UnsupportedOperationException(); 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 587fd95c76..f3a24808d0 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 @@ -38,12 +38,14 @@ import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; +import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.DeleteEvent; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.FlushEvent; import org.hibernate.event.spi.InitializeCollectionEvent; +import org.hibernate.event.spi.InitializeCollectionEventListener; import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEventListener; import org.hibernate.event.spi.LockEvent; @@ -689,7 +691,8 @@ public CompletionStage reactiveInitializeCollection(PersistentCollection eventListenerGroupInitCollection = fastSessionServices.eventListenerGroup_INIT_COLLECTION; + return eventListenerGroupInitCollection .fireEventOnEachListener( event, (DefaultReactiveInitializeCollectionEventListener l) -> l::onReactiveInitializeCollection diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java index 51e75108ea..bcf33c9422 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java @@ -5,17 +5,13 @@ */ package org.hibernate.reactive.sql.exec.internal; -import java.io.Serializable; -import java.sql.PreparedStatement; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; -import java.util.function.Function; import org.hibernate.CacheMode; -import org.hibernate.LockOptions; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; import org.hibernate.engine.spi.PersistenceContext; @@ -36,14 +32,16 @@ import org.hibernate.reactive.sql.results.spi.ReactiveValuesMappingProducer; import org.hibernate.sql.exec.SqlExecLogger; import org.hibernate.sql.exec.internal.JdbcExecHelper; +import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter; +import org.hibernate.sql.results.jdbc.internal.CachedJdbcValuesMetadata; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.RowTransformer; @@ -78,19 +76,63 @@ public CompletionStage> list( RowTransformer rowTransformer, Class domainResultType, ReactiveListResultsConsumer.UniqueSemantic uniqueSemantic) { - return executeQuery( + return list( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, - executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer()::prepareStatement, + uniqueSemantic, + -1 + ); + } + + /** + * @since 2.4 (and ORM 6.6) + */ + public CompletionStage> list( + JdbcOperationQuerySelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Class requestedJavaType, + ReactiveListResultsConsumer.UniqueSemantic uniqueSemantic, + int resultCountEstimate) { + // Only do auto flushing for top level queries + return executeQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + requestedJavaType, + resultCountEstimate, ReactiveListResultsConsumer.instance( uniqueSemantic ) ); } + /** + * @since 2.4 (and Hibernate ORM 6.6) + */ + public CompletionStage executeQuery( + JdbcOperationQuerySelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Class domainResultType, + int resultCountEstimate, + ReactiveResultsConsumer resultsConsumer) { + return executeQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + domainResultType, + resultCountEstimate, + StandardStatementCreator.getStatementCreator( null ), + resultsConsumer + ); + } + @Override public CompletionStage executeQuery( JdbcOperationQuerySelect jdbcSelect, @@ -98,7 +140,8 @@ public CompletionStage executeQuery( ExecutionContext executionContext, RowTransformer rowTransformer, Class domainResultType, - Function statementCreator, + int resultCountEstimate, + JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer) { final PersistenceContext persistenceContext = executionContext.getSession().getPersistenceContext(); @@ -110,7 +153,7 @@ public CompletionStage executeQuery( persistenceContext.setDefaultReadOnly( readOnly ); } - return doExecuteQuery( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, statementCreator, resultsConsumer ) + return doExecuteQuery( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, resultCountEstimate, statementCreator, resultsConsumer ) .thenCompose( list -> ( (ReactivePersistenceContextAdapter) persistenceContext ) // only initialize non-lazy collections after everything else has been refreshed .reactiveInitializeNonLazyCollections() @@ -129,10 +172,11 @@ private CompletionStage doExecuteQuery( ExecutionContext executionContext, RowTransformer transformer, Class domainResultType, - Function statementCreator, + int resultCountEstimate, + JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer) { - final ReactiveDeferredResultSetAccess deferredResultSetAccess = new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator ); + final ReactiveDeferredResultSetAccess deferredResultSetAccess = new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); return resolveJdbcValuesSource( executionContext.getQueryIdentifier( deferredResultSetAccess.getFinalSql() ), @@ -175,17 +219,10 @@ public boolean shouldReturnProxies() { ); final ReactiveRowReader rowReader = ReactiveResultsHelper.createRowReader( - executionContext, - // If follow-on locking is used, we must omit the lock options here, - // because these lock options are only for Initializers. - // If we wouldn't omit this, the follow-on lock requests would be no-ops, - // because the EntityEntries would already have the desired lock mode - deferredResultSetAccess.usesFollowOnLocking() - ? LockOptions.NONE - : executionContext.getQueryOptions().getLockOptions(), + executionContext.getSession().getSessionFactory(), rowTransformer, domainResultType, - jdbcValues.getValuesMapping() + jdbcValues ); final ReactiveRowProcessingState rowProcessingState = new ReactiveRowProcessingState( @@ -195,6 +232,8 @@ public boolean shouldReturnProxies() { jdbcValues ); + rowReader.startLoading( rowProcessingState ); + return resultsConsumer .consume( jdbcValues, @@ -237,7 +276,12 @@ private static RowTransformer rowTransformer( return rowTransformer; } - public CompletionStage resolveJdbcValuesSource(String queryIdentifier, JdbcOperationQuerySelect jdbcSelect, boolean canBeCached, ExecutionContext executionContext, ReactiveResultSetAccess resultSetAccess) { + public CompletionStage resolveJdbcValuesSource( + String queryIdentifier, + JdbcOperationQuerySelect jdbcSelect, + boolean canBeCached, + ExecutionContext executionContext, + ReactiveDeferredResultSetAccess resultSetAccess) { final SharedSessionContractImplementor session = executionContext.getSession(); final SessionFactoryImplementor factory = session.getFactory(); final boolean queryCacheEnabled = factory.getSessionFactoryOptions().isQueryCacheEnabled(); @@ -252,7 +296,7 @@ public CompletionStage resolveJdbcValuesSource(String q if ( cacheable && cacheMode.isGetEnabled() ) { SqlExecLogger.SQL_EXEC_LOGGER.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() ); final Set querySpaces = jdbcSelect.getAffectedTableNames(); - if ( querySpaces == null || querySpaces.size() == 0 ) { + if ( querySpaces == null || querySpaces.isEmpty() ) { SqlExecLogger.SQL_EXEC_LOGGER.tracef( "Unexpected querySpaces is empty" ); } else { @@ -313,41 +357,70 @@ public CompletionStage resolveJdbcValuesSource(String q if ( queryResultsCacheKey == null ) { return mappingProducer .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, queryResultsCacheKey, queryIdentifier, executionContext.getQueryOptions(), jdbcValuesMapping, null, executionContext ) ); + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + null, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + null, + executionContext + ) ); } else { // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - JdbcValuesMetadata metadataForCache = capturingMetadata.resolveMetadataForCache(); return mappingProducer .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, queryResultsCacheKey, queryIdentifier, executionContext.getQueryOptions(), jdbcValuesMapping, metadataForCache, executionContext ) ); + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); } } else { + // TODO: Implements JdbcValuesCacheHit for reactive, see JdbcSelectExecutorStandardImpl#resolveJdbcValuesSource // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - final CompletionStage stage; if ( cachedResults.isEmpty() || !( cachedResults.get( 0 ) instanceof JdbcValuesMetadata ) ) { - stage = mappingProducer.reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ); + return mappingProducer.reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); } else { - stage = mappingProducer.reactiveResolve( (JdbcValuesMetadata) cachedResults.get( 0 ), session.getLoadQueryInfluencers(), factory ); + return mappingProducer + .reactiveResolve( (JdbcValuesMetadata) cachedResults.get( 0 ), session.getLoadQueryInfluencers(), factory ) + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); } - return stage.thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( - resultSetAccess, - queryResultsCacheKey, - queryIdentifier, - executionContext.getQueryOptions(), - jdbcValuesMapping, - capturingMetadata, - executionContext - ) ); } } /** - * see {@link org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.CapturingJdbcValuesMetadata} + * see {@link CachedJdbcValuesMetadata} */ public static class CapturingJdbcValuesMetadata implements JdbcValuesMetadata { private final ReactiveResultSetAccess resultSetAccess; @@ -423,7 +496,7 @@ public BasicType resolveType( return basicType; } - public JdbcValuesMetadata resolveMetadataForCache() { + public CachedJdbcValuesMetadata resolveMetadataForCache() { if ( columnNames == null ) { return null; } @@ -431,60 +504,6 @@ public JdbcValuesMetadata resolveMetadataForCache() { } } - private static class CachedJdbcValuesMetadata implements JdbcValuesMetadata, Serializable { - private final String[] columnNames; - private final BasicType[] types; - - public CachedJdbcValuesMetadata(String[] columnNames, BasicType[] types) { - this.columnNames = columnNames; - this.types = types; - } - - @Override - public int getColumnCount() { - return columnNames.length; - } - - @Override - public int resolveColumnPosition(String columnName) { - final int position = ArrayHelper.indexOf( columnNames, columnName ) + 1; - if ( position == 0 ) { - throw new IllegalStateException( "Unexpected resolving of unavailable column: " + columnName ); - } - return position; - } - - @Override - public String resolveColumnName(int position) { - final String name = columnNames[position - 1]; - if ( name == null ) { - throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position ); - } - return name; - } - - @Override - public BasicType resolveType( - int position, - JavaType explicitJavaType, - TypeConfiguration typeConfiguration) { - final BasicType type = types[position - 1]; - if ( type == null ) { - throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position ); - } - if ( explicitJavaType == null || type.getJavaTypeDescriptor() == explicitJavaType ) { - //noinspection unchecked - return (BasicType) type; - } - else { - return typeConfiguration.getBasicTypeRegistry().resolve( - explicitJavaType, - type.getJdbcType() - ); - } - } - } - private static class Statistics { private final ExecutionContext executionContext; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java index 034a92aa69..09ecd0b10b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java @@ -5,18 +5,14 @@ */ package org.hibernate.reactive.sql.exec.spi; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; +import org.hibernate.LockMode; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.sql.results.internal.ReactiveInitializersList; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; -import org.hibernate.spi.NavigablePath; import org.hibernate.sql.exec.internal.BaseExecutionContext; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; import org.hibernate.sql.results.graph.entity.EntityFetch; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; @@ -29,15 +25,14 @@ */ public class ReactiveRowProcessingState extends BaseExecutionContext implements RowProcessingState { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final JdbcValuesSourceProcessingStateStandardImpl resultSetProcessingState; - private final ReactiveInitializersList initializers; - private final ReactiveRowReader rowReader; private final ReactiveValuesResultSet jdbcValues; private final ExecutionContext executionContext; + private final boolean needsResolveState; + + private final InitializerData[] initializerData; public ReactiveRowProcessingState( JdbcValuesSourceProcessingStateStandardImpl resultSetProcessingState, @@ -48,8 +43,10 @@ public ReactiveRowProcessingState( this.resultSetProcessingState = resultSetProcessingState; this.executionContext = executionContext; this.rowReader = rowReader; - this.initializers = rowReader.getReactiveInitializersList(); this.jdbcValues = jdbcValues; + this.needsResolveState = !isQueryCacheHit() + && getQueryOptions().isResultCachingEnabled() == Boolean.TRUE; + this.initializerData = new InitializerData[rowReader.getInitializerCount()]; } public CompletionStage next() { @@ -61,6 +58,42 @@ public JdbcValuesSourceProcessingState getJdbcValuesSourceProcessingState() { return resultSetProcessingState; } + @Override + public Object getEntityId() { + return executionContext.getEntityId(); + } + + @Override + public LockMode determineEffectiveLockMode(String alias) { + if ( jdbcValues.usesFollowOnLocking() ) { + // If follow-on locking is used, we must omit the lock options here, + // because these lock options are only for Initializers. + // If we wouldn't omit this, the follow-on lock requests would be no-ops, + // because the EntityEntrys would already have the desired lock mode + return LockMode.NONE; + } + final LockMode effectiveLockMode = resultSetProcessingState.getQueryOptions().getLockOptions() + .getEffectiveLockMode( alias ); + return effectiveLockMode == LockMode.NONE + ? jdbcValues.getValuesMapping().determineDefaultLockMode( alias, effectiveLockMode ) + : effectiveLockMode; + } + + @Override + public boolean needsResolveState() { + return needsResolveState; + } + + @Override + public T getInitializerData(int initializerId) { + return (T) initializerData[initializerId]; + } + + @Override + public void setInitializerData(int initializerId, InitializerData state) { + initializerData[initializerId] = state; + } + @Override public RowReader getRowReader() { return rowReader; @@ -82,19 +115,12 @@ public boolean isQueryCacheHit() { return false; } - public void finishRowProcessing() { - } - @Override - public Initializer resolveInitializer(NavigablePath path) { - return this.initializers.resolveInitializer( path ); + public void finishRowProcessing(boolean wasAdded) { + jdbcValues.finishRowProcessing( this, wasAdded ); } public QueryOptions getQueryOptions() { return this.executionContext.getQueryOptions(); } - - public boolean hasCollectionInitializers() { - return this.initializers.hasCollectionInitializers(); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java index b41cd90aba..ab68e34be9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java @@ -5,16 +5,16 @@ */ package org.hibernate.reactive.sql.exec.spi; -import java.sql.PreparedStatement; import java.util.List; import java.util.concurrent.CompletionStage; -import java.util.function.Function; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; +import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.spi.RowTransformer; /** @@ -22,13 +22,37 @@ */ public interface ReactiveSelectExecutor { + /** + * @since 2.4 (and Hibernate ORM 6.6) + */ + default CompletionStage executeQuery( + JdbcOperationQuerySelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Class domainResultType, + int resultCountEstimate, + ReactiveResultsConsumer resultsConsumer) { + return executeQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + domainResultType, + resultCountEstimate, + StandardStatementCreator.getStatementCreator( null ), + resultsConsumer + ); + } + CompletionStage executeQuery( JdbcOperationQuerySelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, Class domainResultType, - Function statementCreator, + int resultCountEstimate, + JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer); CompletionStage> list( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java index 4323e04bdf..f1dfa276b7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java @@ -20,6 +20,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.exception.DataException; import org.hibernate.exception.LockTimeoutException; +import org.hibernate.query.spi.Limit; import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.results.internal.ReactiveResultSetAccess; import org.hibernate.sql.ast.spi.SqlSelection; @@ -28,8 +29,8 @@ import org.hibernate.sql.results.caching.QueryCachePutManager; import org.hibernate.sql.results.caching.internal.QueryCachePutManagerEnabledImpl; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.jdbc.internal.CachedJdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -46,6 +47,8 @@ public class ReactiveValuesResultSet { private final ReactiveResultSetAccess resultSetAccess; private final JdbcValuesMapping valuesMapping; private final ExecutionContext executionContext; + private final boolean usesFollowOnLocking; + private final int resultCountEstimate; private final SqlSelection[] sqlSelections; private final BitSet initializedIndexes; private final Object[] currentRowJdbcValues; @@ -54,19 +57,29 @@ public class ReactiveValuesResultSet { // Contains the size of the row to cache, or if the value is negative, // represents the inverted index of the single value to cache private final int rowToCacheSize; + private int resultCount; public ReactiveValuesResultSet( ReactiveResultSetAccess resultSetAccess, QueryKey queryCacheKey, String queryIdentifier, QueryOptions queryOptions, + boolean usesFollowOnLocking, JdbcValuesMapping valuesMapping, - JdbcValuesMetadata metadataForCache, + CachedJdbcValuesMetadata metadataForCache, ExecutionContext executionContext) { - this.queryCachePutManager = resolveQueryCachePutManager( executionContext, queryOptions, queryCacheKey, queryIdentifier, metadataForCache ); + this.queryCachePutManager = resolveQueryCachePutManager( + executionContext, + queryOptions, + queryCacheKey, + queryIdentifier, + metadataForCache + ); this.resultSetAccess = resultSetAccess; this.valuesMapping = valuesMapping; this.executionContext = executionContext; + this.usesFollowOnLocking = usesFollowOnLocking; + this.resultCountEstimate = determineResultCountEstimate( resultSetAccess, queryOptions ); final int rowSize = valuesMapping.getRowSize(); this.sqlSelections = new SqlSelection[rowSize]; @@ -116,12 +129,27 @@ public ReactiveValuesResultSet( } } + private int determineResultCountEstimate( + ReactiveResultSetAccess resultSetAccess, + QueryOptions queryOptions) { + final Limit limit = queryOptions.getLimit(); + if ( limit != null && limit.getMaxRows() != null ) { + return limit.getMaxRows(); + } + + final int resultCountEstimate = resultSetAccess.getResultCountEstimate(); + if ( resultCountEstimate > 0 ) { + return resultCountEstimate; + } + return -1; + } + private static QueryCachePutManager resolveQueryCachePutManager( ExecutionContext executionContext, QueryOptions queryOptions, QueryKey queryCacheKey, String queryIdentifier, - JdbcValuesMetadata metadataForCache) { + CachedJdbcValuesMetadata metadataForCache) { if ( queryCacheKey != null ) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final QueryResultsCache queryCache = factory.getCache() @@ -182,13 +210,20 @@ public Object[] getCurrentRowValuesArray() { public void finishUp(SharedSessionContractImplementor session) { if ( queryCachePutManager != null ) { - queryCachePutManager.finishUp( session ); + queryCachePutManager.finishUp( resultCount, session ); } resultSetAccess.release(); } + public boolean usesFollowOnLocking() { + return usesFollowOnLocking; + } + public void finishRowProcessing(RowProcessingState rowProcessingState, boolean wasAdded) { if ( queryCachePutManager != null ) { + if ( wasAdded ) { + resultCount++; + } final Object objectToCache; if ( valueIndexesToCacheIndexes == null ) { objectToCache = Arrays.copyOf( currentRowJdbcValues, currentRowJdbcValues.length ); @@ -246,4 +281,8 @@ private CompletionStage readCurrentRowValues(boolean hasResults) { return true; } ); } + + public int getResultCountEstimate() { + return resultCountEstimate; + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java index 84bef80450..8580a8b68a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java @@ -21,7 +21,7 @@ CompletionStage reactiveAssemble( JdbcValuesSourceProcessingOptions options); /** - * Convenience form of {@link #assemble(RowProcessingState, JdbcValuesSourceProcessingOptions)} + * Convenience form of {@link #assemble(RowProcessingState)} */ default CompletionStage reactiveAssemble(ReactiveRowProcessingState rowProcessingState) { return reactiveAssemble( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java index bff5088325..c20b4775dd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java @@ -6,18 +6,62 @@ package org.hibernate.reactive.sql.results.graph; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; import org.hibernate.Incubating; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + /** * @see org.hibernate.sql.results.graph.Initializer */ @Incubating -public interface ReactiveInitializer { +public interface ReactiveInitializer { + + /** + * The current data of this initializer. + */ + Data getData(RowProcessingState rowProcessingState); + + CompletionStage reactiveResolveInstance(Data data); + + /** + * @see org.hibernate.sql.results.graph.internal.AbstractInitializer#resolveKey(InitializerData) + */ + default CompletionStage reactiveResolveKey(Data data) { + data.setState( Initializer.State.KEY_RESOLVED ); + return forEachReactiveSubInitializer( ReactiveInitializer::reactiveResolveKey, data ); + } + + default CompletionStage reactiveResolveKey(RowProcessingState rowProcessingState) { + Data data = getData( rowProcessingState ); + return reactiveResolveKey( data ); + } + + default CompletionStage reactiveResolveInstance(Object instance, Data data) { + return reactiveResolveKey( data ); + } + + default CompletionStage reactiveResolveInstance(Object instance, RowProcessingState rowProcessingState) { + return reactiveResolveInstance( instance, getData( rowProcessingState ) ); + } + + default CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { + return reactiveResolveInstance( getData( rowProcessingState ) ); + } + + CompletionStage reactiveInitializeInstance(Data data); - CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState); + default CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { + return reactiveInitializeInstance( getData( rowProcessingState ) ); + } - CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState); + CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data); + Object getResolvedInstance(Data data); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java index 6a60e89dba..e47715aff1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java @@ -5,16 +5,22 @@ */ package org.hibernate.reactive.sql.results.graph.collection.internal; +import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.collection.CollectionInitializer; import org.hibernate.sql.results.graph.collection.internal.CollectionDomainResult; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; public class ReactiveCollectionDomainResult extends CollectionDomainResult { @@ -48,4 +54,92 @@ public Fetch generateFetchableFetch( } return fetch; } + + @Override + public CollectionInitializer createInitializer( + InitializerParent parent, + AssemblerCreationState creationState) { + CollectionInitializer initializer = super.createInitializer( parent, creationState ); + return new ReactiveCollectionInitializerAdapter<>( initializer ); + } + + public static class ReactiveCollectionInitializerAdapter + implements CollectionInitializer { + + private final CollectionInitializer delegate; + + public ReactiveCollectionInitializerAdapter(CollectionInitializer initializer) { + this.delegate = initializer; + } + + @Override + public NavigablePath getNavigablePath() { + return delegate.getNavigablePath(); + } + + @Override + public PluralAttributeMapping getInitializedPart() { + return delegate.getInitializedPart(); + } + + @Override + public T getData(RowProcessingState rowProcessingState) { + return delegate.getData( rowProcessingState ); + } + + @Override + public void startLoading(RowProcessingState rowProcessingState) { + delegate.startLoading( rowProcessingState ); + } + + @Override + public void resolveKey(T data) { + delegate.resolveKey( data ); + } + + @Override + public void resolveInstance(T data) { + delegate.resolveInstance( data ); + } + + @Override + public void resolveState(T data) { + + } + + @Override + public void initializeInstance(T data) { + delegate.initializeInstance( data ); + } + + @Override + public void finishUpRow(T data) { + delegate.finishUpRow( data ); + } + + @Override + public boolean isPartOfKey() { + return delegate.isPartOfKey(); + } + + @Override + public boolean isEager() { + return delegate.isEager(); + } + + @Override + public boolean hasEagerSubInitializers() { + return delegate.hasEagerSubInitializers(); + } + + @Override + public boolean isResultInitializer() { + return delegate.isResultInitializer(); + } + + @Override + public PersistentCollection getCollectionInstance(T data) { + return delegate.getCollectionInstance( data ); + } + } } 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 new file mode 100644 index 0000000000..50034533b1 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java @@ -0,0 +1,40 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import org.hibernate.engine.FetchTiming; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; + +public class ReactiveEmbeddableFetchImpl extends EmbeddableFetchImpl { + + public ReactiveEmbeddableFetchImpl( + NavigablePath navigablePath, + EmbeddableValuedFetchable embeddedPartDescriptor, + FetchParent fetchParent, + FetchTiming fetchTiming, + boolean hasTableGroup, + DomainResultCreationState creationState) { + super( navigablePath, embeddedPartDescriptor, fetchParent, fetchTiming, hasTableGroup, creationState ); + } + + public ReactiveEmbeddableFetchImpl(EmbeddableFetchImpl original) { + super( original ); + } + + @Override + public EmbeddableInitializer createInitializer( + InitializerParent parent, + AssemblerCreationState creationState) { + return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), parent, creationState, true ); + } +} 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 new file mode 100644 index 0000000000..a708481cf8 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java @@ -0,0 +1,27 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; + +public class ReactiveEmbeddableForeignKeyResultImpl extends EmbeddableForeignKeyResultImpl { + + public ReactiveEmbeddableForeignKeyResultImpl(EmbeddableForeignKeyResultImpl original) { + super( original ); + } + + @Override + public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return getReferencedModePart() instanceof NonAggregatedIdentifierMapping + ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) + : new EmbeddableInitializerImpl( this, 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 new file mode 100644 index 0000000000..3e760d9b0f --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -0,0 +1,91 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.basic.BasicFetch; +import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +public class ReactiveEmbeddableInitializerImpl extends EmbeddableInitializerImpl + implements ReactiveInitializer { + + private static class ReactiveEmbeddableInitializerData extends EmbeddableInitializerData { + + public ReactiveEmbeddableInitializerData( + EmbeddableInitializerImpl initializer, + RowProcessingState rowProcessingState) { + super( initializer, rowProcessingState ); + } + + public EmbeddableMappingType.ConcreteEmbeddableType getConcreteEmbeddableType() { + return super.concreteEmbeddableType; + } + } + + public ReactiveEmbeddableInitializerImpl( + EmbeddableResultGraphNode resultDescriptor, + BasicFetch discriminatorFetch, + InitializerParent parent, + AssemblerCreationState creationState, + boolean isResultInitializer) { + super( resultDescriptor, discriminatorFetch, parent, creationState, isResultInitializer ); + } + + @Override + protected InitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEmbeddableInitializerData( this, rowProcessingState ); + } + + @Override + public CompletionStage reactiveResolveInstance(EmbeddableInitializerData data) { + super.resolveInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage reactiveInitializeInstance(EmbeddableInitializerData data) { + super.initializeInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final ReactiveEmbeddableInitializerData embeddableInitializerData = (ReactiveEmbeddableInitializerData) data; + final RowProcessingState rowProcessingState = embeddableInitializerData.getRowProcessingState(); + if ( embeddableInitializerData.getConcreteEmbeddableType() == null ) { + return loop( subInitializers, subInitializer -> loop( subInitializer, initializer -> consumer + .apply( (ReactiveInitializer) initializer, rowProcessingState ) + ) ); + } + else { + Initializer[] initializers = subInitializers[embeddableInitializerData.getSubclassId()]; + return loop( 0, initializers.length, i -> { + ReactiveInitializer reactiveInitializer = (ReactiveInitializer) initializers[i]; + return consumer.apply( reactiveInitializer, rowProcessingState ); + } ); + } + } + + @Override + public Object getResolvedInstance(EmbeddableInitializerData data) { + return super.getResolvedInstance( data ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java new file mode 100644 index 0000000000..2af2645260 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java @@ -0,0 +1,69 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingFetch; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; + +public class ReactiveNonAggregatedIdentifierMappingFetch extends ReactiveEmbeddableFetchImpl { + public ReactiveNonAggregatedIdentifierMappingFetch( + NavigablePath navigablePath, + NonAggregatedIdentifierMapping embeddedPartDescriptor, + FetchParent fetchParent, + FetchTiming fetchTiming, + boolean hasTableGroup, + DomainResultCreationState creationState) { + super( navigablePath, embeddedPartDescriptor, fetchParent, fetchTiming, hasTableGroup, creationState ); + } + + public ReactiveNonAggregatedIdentifierMappingFetch(NonAggregatedIdentifierMappingFetch fetch) { + super( fetch ); + } + + @Override + public EmbeddableInitializer createInitializer( + InitializerParent parent, + AssemblerCreationState creationState) { + return new ReactiveNonAggregatedIdentifierMappingInitializer( this, parent, creationState, false ); + } + + @Override + public Fetch generateFetchableFetch( + Fetchable fetchable, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + Fetch fetch = super.generateFetchableFetch( + fetchable, + fetchablePath, + fetchTiming, + selected, + resultVariable, + creationState + ); + if ( fetch instanceof EmbeddableFetchImpl ) { + return new ReactiveEmbeddableFetchImpl( (EmbeddableFetchImpl) fetch ); + } + if ( fetch instanceof EntityFetchJoinedImpl ) { + return new ReactiveEntityFetchJoinedImpl( (EntityFetchJoinedImpl) fetch ); + } + return fetch; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java new file mode 100644 index 0000000000..0096e8d0fb --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java @@ -0,0 +1,108 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +public class ReactiveNonAggregatedIdentifierMappingInitializer extends NonAggregatedIdentifierMappingInitializer + implements ReactiveInitializer { + + public ReactiveNonAggregatedIdentifierMappingInitializer( + EmbeddableResultGraphNode resultDescriptor, + InitializerParent parent, + AssemblerCreationState creationState, + boolean isResultInitializer) { + super( + resultDescriptor, + parent, + creationState, + isResultInitializer, + ReactiveNonAggregatedIdentifierMappingInitializer::convertFetch + ); + } + + private static Fetch convertFetch(Fetch fetch) { + if ( fetch instanceof EntityFetchJoinedImpl ) { + return new ReactiveEntityFetchJoinedImpl( (EntityFetchJoinedImpl) fetch ); + } + return fetch; + } + + @Override + public CompletionStage reactiveResolveKey(NonAggregatedIdentifierMappingInitializerData data) { + if ( data.getState() != State.UNINITIALIZED ) { + return voidFuture(); + } + // We need to possibly wrap the processing state if the embeddable is within an aggregate + data.setInstance( null ); + data.setState( State.KEY_RESOLVED ); + if ( getInitializers().length == 0 ) { + // Resolve the component early to know if the key is missing or not + return reactiveResolveInstance( data ); + } + else { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final boolean[] dataIsMissing = {false}; + return loop( getInitializers(), initializer -> { + if ( dataIsMissing[0] ) { + return voidFuture(); + } + final InitializerData subData = ( (ReactiveInitializer) initializer ) + .getData( rowProcessingState ); + return ( (ReactiveInitializer) initializer ) + .reactiveResolveKey( subData ) + .thenAccept( v -> { + if ( subData.getState() == State.MISSING ) { + data.setState( State.MISSING ); + dataIsMissing[0] = true; + } + } ); + } ); + } + } + + @Override + public CompletionStage reactiveResolveInstance(NonAggregatedIdentifierMappingInitializerData data) { + super.resolveInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage reactiveInitializeInstance(NonAggregatedIdentifierMappingInitializerData data) { + super.initializeInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + return loop( getInitializers(), initializer -> consumer + .apply( (ReactiveInitializer) initializer, rowProcessingState ) + ); + } + + @Override + public Object getResolvedInstance(NonAggregatedIdentifierMappingInitializerData data) { + return super.getResolvedInstance( data ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java deleted file mode 100644 index 82aaa435df..0000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java +++ /dev/null @@ -1,219 +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.sql.results.graph.entity; - -import java.lang.invoke.MethodHandles; -import java.util.concurrent.CompletionStage; - -import org.hibernate.LockMode; -import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.engine.spi.Status; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; -import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; -import org.hibernate.spi.NavigablePath; -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.Fetch; -import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer; -import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; -import org.hibernate.stat.spi.StatisticsImplementor; - -import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; -import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; -import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; -import static org.hibernate.internal.log.LoggingHelper.toLoggableString; -import static org.hibernate.reactive.util.impl.CompletionStages.loop; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -public abstract class ReactiveAbstractEntityInitializer extends AbstractEntityInitializer implements ReactiveInitializer { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - protected ReactiveAbstractEntityInitializer( - EntityResultGraphNode resultDescriptor, - NavigablePath navigablePath, - LockMode lockMode, - Fetch identifierFetch, - Fetch discriminatorFetch, - DomainResult rowIdResult, - AssemblerCreationState creationState) { - this( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - null, - creationState - ); - } - - protected ReactiveAbstractEntityInitializer( - EntityResultGraphNode resultDescriptor, - NavigablePath navigablePath, - LockMode lockMode, - Fetch identifierFetch, - Fetch discriminatorFetch, - DomainResult rowIdResult, - FetchParentAccess parentAccess, - AssemblerCreationState creationState) { - super( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - parentAccess, - creationState - ); - } - - @Override - public void resolveInstance(RowProcessingState rowProcessingState) { - super.resolveInstance( rowProcessingState ); - } - - @Override - public void initializeInstance(RowProcessingState rowProcessingState) { - throw LOG.nonReactiveMethodCall( "reactiveInitializeInstance" ); - } - - @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - super.resolveInstance( rowProcessingState ); - return voidFuture(); - } - - @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state == State.KEY_RESOLVED || state == State.RESOLVED ) { - return initializeEntity( getEntityInstanceForNotify(), rowProcessingState ) - .thenAccept( v -> state = State.INITIALIZED ); - } - return voidFuture(); - } - - protected CompletionStage initializeEntity(Object toInitialize, RowProcessingState rowProcessingState) { - if ( !skipInitialization( toInitialize, rowProcessingState ) ) { - assert consistentInstance( toInitialize, rowProcessingState ); - return initializeEntityInstance( toInitialize, rowProcessingState ); - } - return voidFuture(); - } - - - protected CompletionStage reactiveExtractConcreteTypeStateValues(RowProcessingState rowProcessingState) { - final Object[] values = new Object[getConcreteDescriptor().getNumberOfAttributeMappings()]; - final DomainResultAssembler[] concreteAssemblers = getAssemblers()[getConcreteDescriptor().getSubclassId()]; - return loop( 0, values.length, i -> { - final DomainResultAssembler assembler = concreteAssemblers[i]; - if ( assembler instanceof ReactiveDomainResultsAssembler) { - return ( (ReactiveDomainResultsAssembler) assembler ) - .reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) - .thenAccept( obj -> values[i] = obj ); - } - else { - values[i] = assembler == null ? UNFETCHED_PROPERTY : assembler.assemble( rowProcessingState ); - return voidFuture(); - } - } ).thenApply( unused -> values ); - } - - private CompletionStage initializeEntityInstance(Object toInitialize, RowProcessingState rowProcessingState) { - final Object entityIdentifier = getEntityKey().getIdentifier(); - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isTraceEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( - "(%s) Beginning Initializer#initializeInstance process for entity %s", - getSimpleConcreteImplName(), - toLoggableString( getNavigablePath(), entityIdentifier ) - ); - } - - getEntityDescriptor().setIdentifier( toInitialize, entityIdentifier, session ); - return reactiveExtractConcreteTypeStateValues( rowProcessingState ) - .thenCompose( entityState -> loop( 0, entityState.length, i -> { - if ( entityState[i] instanceof CompletionStage ) { - return ( (CompletionStage) entityState[i] ) - .thenAccept( state -> entityState[i] = state ); - } - return voidFuture(); - } ).thenAccept( v -> setResolvedEntityState( entityState ) ) - ) - .thenAccept( v -> { - if ( isPersistentAttributeInterceptable(toInitialize) ) { - PersistentAttributeInterceptor persistentAttributeInterceptor = - asPersistentAttributeInterceptable( toInitialize ).$$_hibernate_getInterceptor(); - if ( persistentAttributeInterceptor == null - || persistentAttributeInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - // if we do this after the entity has been initialized the - // BytecodeLazyAttributeInterceptor#isAttributeLoaded(String fieldName) would return false; - getConcreteDescriptor().getBytecodeEnhancementMetadata() - .injectInterceptor( toInitialize, entityIdentifier, session ); - } - } - getConcreteDescriptor().setValues( toInitialize, getResolvedEntityState() ); - persistenceContext.addEntity( getEntityKey(), toInitialize ); - - // Also register possible unique key entries - registerPossibleUniqueKeyEntries( toInitialize, session ); - - final Object version = getVersionAssembler() != null ? getVersionAssembler().assemble( rowProcessingState ) : null; - final Object rowId = getRowIdAssembler() != null ? getRowIdAssembler().assemble( rowProcessingState ) : null; - - // from the perspective of Hibernate, an entity is read locked as soon as it is read - // so regardless of the requested lock mode, we upgrade to at least the read level - final LockMode lockModeToAcquire = getLockMode() == LockMode.NONE ? LockMode.READ : getLockMode(); - - final EntityEntry entityEntry = persistenceContext.addEntry( - toInitialize, - Status.LOADING, - getResolvedEntityState(), - rowId, - getEntityKey().getIdentifier(), - version, - lockModeToAcquire, - true, - getConcreteDescriptor(), - false - ); - - updateCaches( toInitialize, rowProcessingState, session, persistenceContext, entityIdentifier, version ); - registerNaturalIdResolution( persistenceContext, entityIdentifier ); - takeSnapshot( rowProcessingState, session, persistenceContext, entityEntry ); - getConcreteDescriptor().afterInitialize( toInitialize, session ); - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Done materializing entityInstance : %s", - getSimpleConcreteImplName(), - toLoggableString( getNavigablePath(), entityIdentifier ) - ); - } - - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - if ( !rowProcessingState.isQueryCacheHit() ) { - statistics.loadEntity( getConcreteDescriptor().getEntityName() ); - } - } - } ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java index be90edbb61..eb57f92fa8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java @@ -5,21 +5,19 @@ */ package org.hibernate.reactive.sql.results.graph.entity.internal; - import java.util.concurrent.CompletionStage; -import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.type.descriptor.java.JavaType; -import static java.lang.invoke.MethodHandles.lookup; -import static org.hibernate.reactive.logging.impl.LoggerFactory.make; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; /** * @see org.hibernate.sql.results.graph.entity.internal.EntityAssembler @@ -27,19 +25,7 @@ public class ReactiveEntityAssembler extends EntityAssembler implements ReactiveDomainResultsAssembler { public ReactiveEntityAssembler(JavaType javaType, EntityInitializer initializer) { - super(javaType, initializer); - } - - @Override - public Object assemble(RowProcessingState rowProcessingState) { - throw make( Log.class, lookup() ) - .nonReactiveMethodCall( "reactiveAssemble" ); - } - - @Override - public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { - throw make( Log.class, lookup() ) - .nonReactiveMethodCall( "reactiveAssemble" ); + super( javaType, initializer ); } @Override @@ -47,9 +33,14 @@ public CompletionStage reactiveAssemble(ReactiveRowProcessingState rowPr // Ensure that the instance really is initialized // This is important for key-many-to-ones that are part of a collection key fk, // as the instance is needed for resolveKey before initializing the instance in RowReader - return ( (ReactiveInitializer) getInitializer() ) - .reactiveResolveInstance( rowProcessingState ) - .thenApply( v -> getInitializer().getEntityInstance() ); - + final ReactiveInitializer reactiveInitializer = (ReactiveInitializer) getInitializer(); + final InitializerData data = reactiveInitializer.getData( rowProcessingState ); + final Initializer.State state = data.getState(); + if ( state == Initializer.State.KEY_RESOLVED ) { + return reactiveInitializer + .reactiveResolveInstance( data ) + .thenApply( v -> reactiveInitializer.getResolvedInstance( data ) ); + } + return completedFuture( reactiveInitializer.getResolvedInstance( data ) ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java index e761ce183d..1a16607255 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java @@ -6,7 +6,9 @@ package org.hibernate.reactive.sql.results.graph.entity.internal; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; +import org.hibernate.FetchNotFoundException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; @@ -19,53 +21,111 @@ import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.basic.BasicFetch; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.type.Type; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +import static org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.determineConcreteEntityDescriptor; -public class ReactiveEntityDelayedFetchInitializer extends EntityDelayedFetchInitializer implements ReactiveInitializer { +public class ReactiveEntityDelayedFetchInitializer extends EntityDelayedFetchInitializer + implements ReactiveInitializer { private final ToOneAttributeMapping referencedModelPart; public ReactiveEntityDelayedFetchInitializer( - FetchParentAccess parentAccess, + InitializerParent parent, NavigablePath fetchedNavigable, ToOneAttributeMapping referencedModelPart, boolean selectByUniqueKey, - DomainResultAssembler identifierAssembler) { - super( parentAccess, fetchedNavigable, referencedModelPart, selectByUniqueKey, identifierAssembler ); + DomainResult keyResult, + BasicFetch discriminatorResult, + AssemblerCreationState creationState) { + super( + parent, + fetchedNavigable, + referencedModelPart, + selectByUniqueKey, + convert(keyResult), + discriminatorResult, + creationState + ); this.referencedModelPart = referencedModelPart; } - @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( isProcessed() ) { - return voidFuture(); + private static DomainResult convert(DomainResult keyResult) { + return keyResult instanceof EmbeddableForeignKeyResultImpl + ? new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) keyResult ) + : keyResult; + } + + public static class ReactiveEntityDelayedFetchInitializerData extends EntityDelayedFetchInitializerData { + + public ReactiveEntityDelayedFetchInitializerData(RowProcessingState rowProcessingState) { + super( rowProcessingState ); } - setProcessed( true ); + public Object getEntityIdentifier() { + return entityIdentifier; + } - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { + public void setEntityIdentifier(Object entityIdentifier) { + this.entityIdentifier = entityIdentifier; + } + } + + @Override + protected InitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEntityDelayedFetchInitializerData( rowProcessingState ); + } + + @Override + public CompletionStage reactiveResolveInstance(EntityDelayedFetchInitializerData initializerData) { + if ( initializerData.getState() != State.KEY_RESOLVED ) { return voidFuture(); } - setIdentifier( getIdentifierAssembler().assemble( rowProcessingState ) ); + ReactiveEntityDelayedFetchInitializerData data = (ReactiveEntityDelayedFetchInitializerData) initializerData; + data.setState( State.RESOLVED ); + + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + data.setEntityIdentifier( getIdentifierAssembler().assemble( rowProcessingState ) ); CompletionStage stage = voidFuture(); - if ( getIdentifier() == null ) { - setEntityInstance( null ); + if ( data.getEntityIdentifier() == null ) { + data.setInstance( null ); + data.setState( State.MISSING ); } else { final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityPersister concreteDescriptor = referencedModelPart.getEntityMappingType().getEntityPersister(); + + final EntityPersister entityPersister = referencedModelPart.getEntityMappingType().getEntityPersister(); + final EntityPersister concreteDescriptor; + if ( getDiscriminatorAssembler() != null ) { + concreteDescriptor = determineConcreteEntityDescriptor( rowProcessingState, getDiscriminatorAssembler(), entityPersister ); + if ( concreteDescriptor == null ) { + // If we find no discriminator it means there's no entity in the target table + if ( !referencedModelPart.isOptional() ) { + throw new FetchNotFoundException( entityPersister.getEntityName(), data.getEntityIdentifier() ); + } + data.setInstance( null ); + data.setState( State.MISSING ); + return voidFuture(); + } + } + else { + concreteDescriptor = entityPersister; + } + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); if ( isSelectByUniqueKey() ) { final String uniqueKeyPropertyName = referencedModelPart.getReferencedPropertyName(); @@ -75,58 +135,67 @@ public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState final EntityUniqueKey euk = new EntityUniqueKey( concreteDescriptor.getEntityName(), uniqueKeyPropertyName, - getIdentifier(), + data.getEntityIdentifier(), uniqueKeyPropertyType, session.getFactory() ); - setEntityInstance( persistenceContext.getEntity( euk ) ); - if ( getEntityInstance() == null ) { + data.setInstance( persistenceContext.getEntity( euk ) ); + if ( data.getInstance() == null ) { // For unique-key mappings, we always use bytecode-laziness if possible, // because we can't generate a proxy based on the unique key yet if ( referencedModelPart.isLazy() ) { - setEntityInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); + data.setInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); } else { stage = stage .thenCompose( v -> ( (ReactiveEntityPersister) concreteDescriptor ) - .reactiveLoadByUniqueKey( uniqueKeyPropertyName, getIdentifier(), session ) ) - .thenAccept( this::setEntityInstance ) + .reactiveLoadByUniqueKey( + uniqueKeyPropertyName, + data.getEntityIdentifier(), + session + ) ) + .thenAccept( data::setInstance ) .thenAccept( v -> { // If the entity was not in the Persistence Context, but was found now, // add it to the Persistence Context - if ( getEntityInstance() != null ) { - persistenceContext.addEntity( euk, getEntityInstance() ); + if ( data.getInstance() != null ) { + persistenceContext.addEntity( euk, data.getInstance() ); } } ); } } stage = stage.thenAccept( v -> { - if ( getEntityInstance() != null ) { - setEntityInstance( persistenceContext.proxyFor( getEntityInstance() ) ); + if ( data.getInstance() != null ) { + data.setInstance( persistenceContext.proxyFor( data.getInstance() ) ); } } ); } else { - final EntityKey entityKey = new EntityKey( getIdentifier(), concreteDescriptor ); + final EntityKey entityKey = new EntityKey( data.getEntityIdentifier(), concreteDescriptor ); final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); if ( holder != null && holder.getEntity() != null ) { - setEntityInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); + data.setInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); } // For primary key based mappings we only use bytecode-laziness if the attribute is optional, // because the non-optionality implies that it is safe to have a proxy else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) { - setEntityInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); + data.setInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); } else { stage = stage.thenCompose( v -> ReactiveQueryExecutorLookup .extract( session ) - .reactiveInternalLoad( concreteDescriptor.getEntityName(), getIdentifier(), false, false ) - .thenAccept( this::setEntityInstance ) + .reactiveInternalLoad( + concreteDescriptor.getEntityName(), + data.getEntityIdentifier(), + false, + false + ) + .thenAccept( data::setInstance ) ); } stage = stage .thenAccept( v -> { - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( getEntityInstance() ); + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( data.getInstance() ); if ( lazyInitializer != null ) { lazyInitializer.setUnwrap( referencedModelPart.isUnwrapProxy() && concreteDescriptor.isInstrumented() ); } @@ -137,7 +206,24 @@ else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) { } @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { + public CompletionStage reactiveInitializeInstance(EntityDelayedFetchInitializerData data) { + // No-op by default + return voidFuture(); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final ReactiveInitializer initializer = (ReactiveInitializer) getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + return consumer.apply( initializer, data.getRowProcessingState() ); + } return voidFuture(); } + + @Override + public Object getResolvedInstance(EntityDelayedFetchInitializerData data) { + return super.getResolvedInstance( data ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java index ffc2f047c6..7bf7bfb5f4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java @@ -5,9 +5,19 @@ */ package org.hibernate.reactive.sql.results.graph.entity.internal; +import org.hibernate.engine.FetchTiming; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableFetchImpl; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch; +import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingFetch; import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; public class ReactiveEntityFetchJoinedImpl extends EntityFetchJoinedImpl { @@ -16,19 +26,57 @@ public ReactiveEntityFetchJoinedImpl(EntityFetchJoinedImpl entityFetch) { } @Override - public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { - return new ReactiveEntityJoinedFetchInitializer( - getEntityResult(), - getReferencedModePart(), - getNavigablePath(), - creationState.determineEffectiveLockMode( getSourceAlias() ), - getNotFoundAction(), + public EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + Fetch identifierFetch = convert( getEntityResult().getIdentifierFetch() ); + return new ReactiveEntityInitializerImpl( + this, + getSourceAlias(), + identifierFetch, + getEntityResult().getDiscriminatorFetch(), getKeyResult(), getEntityResult().getRowIdResult(), - getEntityResult().getIdentifierFetch(), - getEntityResult().getDiscriminatorFetch(), - parentAccess, + getNotFoundAction(), + isAffectedByFilter(), + parent, + false, + creationState + ); + } + + private static Fetch convert(Fetch fetch) { + if ( fetch instanceof ReactiveEmbeddableFetchImpl ) { + return fetch; + } + if ( fetch instanceof EmbeddableFetchImpl ) { + return new ReactiveEmbeddableFetchImpl( (EmbeddableFetchImpl) fetch ); + } + return fetch; + } + + @Override + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + return new ReactiveEntityAssembler( getFetchedMapping().getJavaType(), entityInitializer ); + } + + @Override + public Fetch generateFetchableFetch( + Fetchable fetchable, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + Fetch fetch = super.generateFetchableFetch( + fetchable, + fetchablePath, + fetchTiming, + selected, + resultVariable, creationState ); + if ( fetch instanceof NonAggregatedIdentifierMappingFetch ) { + return new ReactiveNonAggregatedIdentifierMappingFetch( (NonAggregatedIdentifierMappingFetch) fetch ); + } + return fetch; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java index 7caff34606..0cae0a79df 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java @@ -6,7 +6,7 @@ package org.hibernate.reactive.sql.results.graph.entity.internal; import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; @@ -18,14 +18,15 @@ public ReactiveEntityFetchSelectImpl(EntityFetchSelectImpl original) { } @Override - public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { + public EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { return ReactiveEntitySelectFetchInitializerBuilder.createInitializer( - parentAccess, + parent, getFetchedMapping(), getReferencedMappingContainer().getEntityPersister(), getKeyResult(), getNavigablePath(), isSelectByUniqueKey(), + isAffectedByFilter(), creationState ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java new file mode 100644 index 0000000000..a00287eab5 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -0,0 +1,853 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.entity.internal; + +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +import org.hibernate.Hibernate; +import org.hibernate.LockMode; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityHolder; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.EntityUniqueKey; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.LazyInitializer; +import org.hibernate.proxy.map.MapProxy; +import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +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.Fetch; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; +import org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import org.hibernate.stat.spi.StatisticsImplementor; +import org.hibernate.type.Type; + +import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; +import static org.hibernate.metamodel.mapping.ForeignKeyDescriptor.Nature.TARGET; +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.trueFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +public class ReactiveEntityInitializerImpl extends EntityInitializerImpl + implements ReactiveInitializer { + + public static class ReactiveEntityInitializerData extends EntityInitializerData { + + public ReactiveEntityInitializerData(EntityInitializerImpl initializer, RowProcessingState rowProcessingState) { + super( initializer, rowProcessingState ); + } + + public void setEntityInstanceForNotify(Object instance) { + super.entityInstanceForNotify = instance; + } + + public Object getEntityInstanceForNotify() { + return super.entityInstanceForNotify; + } + + public EntityPersister getConcreteDescriptor() { + return super.concreteDescriptor; + } + + public void setConcreteDescriptor(EntityPersister entityPersister) { + super.concreteDescriptor = entityPersister; + } + + public EntityHolder getEntityHolder() { + return super.entityHolder; + } + + public void setEntityHolder(EntityHolder entityHolder) { + super.entityHolder = entityHolder; + } + + public EntityKey getEntityKey() { + return super.entityKey; + } + + public void setEntityKey(EntityKey entityKey) { + super.entityKey = entityKey; + } + + public String getUniqueKeyAttributePath() { + return super.uniqueKeyAttributePath; + } + + public Type[] getUniqueKeyPropertyTypes() { + return super.uniqueKeyPropertyTypes; + } + + public boolean getShallowCached() { + return super.shallowCached; + } + + public LockMode getLockMode() { + return super.lockMode; + } + } + + public ReactiveEntityInitializerImpl( + EntityResultGraphNode resultDescriptor, + String sourceAlias, + Fetch identifierFetch, + Fetch discriminatorFetch, + DomainResult keyResult, + DomainResult rowIdResult, + NotFoundAction notFoundAction, + boolean affectedByFilter, + InitializerParent parent, + boolean isResultInitializer, + AssemblerCreationState creationState) { + super( + resultDescriptor, + sourceAlias, + identifierFetch , + discriminatorFetch, + keyResult, + rowIdResult, + notFoundAction, + affectedByFilter, + parent, + isResultInitializer, + creationState + ); + } + + @Override + protected void resolveEntityKey(EntityInitializerData original, Object id) { + ReactiveEntityInitializerData data = (ReactiveEntityInitializerData) original; + if ( data.getConcreteDescriptor() == null ) { + data.setConcreteDescriptor( determineConcreteEntityDescriptor( + data.getRowProcessingState(), + getDiscriminatorAssembler(), + getEntityDescriptor() + ) ); + assert data.getConcreteDescriptor() != null; + } + data.setEntityKey( new EntityKey( id, data.getConcreteDescriptor() ) ); + } + + @Override + public CompletionStage reactiveResolveInstance(Object instance, EntityInitializerData original) { + ReactiveEntityInitializerData data = (ReactiveEntityInitializerData) original; + if ( instance == null ) { + setMissing( data ); + return voidFuture(); + } + data.setInstance( instance ); + final LazyInitializer lazyInitializer = extractLazyInitializer( data.getInstance() ); + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + if ( lazyInitializer == null ) { + // Entity is most probably initialized + data.setEntityInstanceForNotify( data.getInstance() ); + data.setConcreteDescriptor( session.getEntityPersister( null, data.getInstance() ) ); + resolveEntityKey( data, data.getConcreteDescriptor().getIdentifier( data.getInstance(), session ) ); + data.setEntityHolder( session.getPersistenceContextInternal().getEntityHolder( data.getEntityKey() ) ); + if ( data.getEntityHolder() == null ) { + // Entity was most probably removed in the same session without setting the reference to null + return reactiveResolveKey( data ) + .thenRun( () -> { + assert data.getState() == State.MISSING; + assert getInitializedPart() instanceof ToOneAttributeMapping + && ( (ToOneAttributeMapping) getInitializedPart() ).getSideNature() == TARGET; + } ); + } + // If the entity initializer is null, we know the entity is fully initialized, + // otherwise it will be initialized by some other initializer + data.setState( data.getEntityHolder().getEntityInitializer() == null ? State.INITIALIZED : State.RESOLVED ); + } + else if ( lazyInitializer.isUninitialized() ) { + data.setState( State.RESOLVED ); + // Read the discriminator from the result set if necessary + EntityPersister persister = getDiscriminatorAssembler() == null + ? getEntityDescriptor() + : determineConcreteEntityDescriptor( rowProcessingState, getDiscriminatorAssembler(), getEntityDescriptor() ); + data.setConcreteDescriptor( persister ); + assert data.getConcreteDescriptor() != null; + resolveEntityKey( data, lazyInitializer.getIdentifier() ); + data.setEntityHolder( session.getPersistenceContextInternal().claimEntityHolderIfPossible( + data.getEntityKey(), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this + ) ); + // Resolve and potentially create the entity instance + data.setEntityInstanceForNotify( resolveEntityInstance( data ) ); + lazyInitializer.setImplementation( data.getEntityInstanceForNotify() ); + registerLoadingEntity( data, data.getEntityInstanceForNotify() ); + } + else { + data.setState( State.INITIALIZED ); + data.setEntityInstanceForNotify( lazyInitializer.getImplementation() ); + data.setConcreteDescriptor( session.getEntityPersister( null, data.getEntityInstanceForNotify() ) ); + resolveEntityKey( data, lazyInitializer.getIdentifier() ); + data.setEntityHolder( session.getPersistenceContextInternal().getEntityHolder( data.getEntityKey() ) ); + } + return reactiveInitializeStage( data, rowProcessingState ) + .thenCompose( v -> { + upgradeLockMode( data ); + if ( data.getState() == State.INITIALIZED ) { + registerReloadedEntity( data ); + resolveInstanceSubInitializers( data ); + if ( rowProcessingState.needsResolveState() ) { + // We need to read result set values to correctly populate the query cache + resolveState( data ); + } + return voidFuture(); + } + else { + return reactiveResolveKeySubInitializers( data ); + } + } ); + } + + private CompletionStage reactiveInitializeStage( + ReactiveEntityInitializerData data, + RowProcessingState rowProcessingState) { + if ( getIdentifierAssembler() != null ) { + final Initializer initializer = getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ) + .reactiveResolveInstance( data.getEntityKey().getIdentifier(), rowProcessingState ); + } + else { + initializer.resolveInstance( data.getEntityKey().getIdentifier(), rowProcessingState ); + } + } + } + return voidFuture(); + } + + @Override + public CompletionStage reactiveResolveInstance(EntityInitializerData original) { + ReactiveEntityInitializerData data = (ReactiveEntityInitializerData) original; + if ( data.getState() != State.KEY_RESOLVED ) { + return voidFuture(); + } + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + data.setState( State.RESOLVED ); + if ( data.getEntityKey() == null ) { + assert getIdentifierAssembler() != null; + final Object id = getIdentifierAssembler().assemble( rowProcessingState ); + if ( id == null ) { + setMissing( data ); + return voidFuture(); + } + resolveEntityKey( data, id ); + } + final PersistenceContext persistenceContext = rowProcessingState.getSession() + .getPersistenceContextInternal(); + data.setEntityHolder( persistenceContext.claimEntityHolderIfPossible( + data.getEntityKey(), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this + ) ); + + if ( useEmbeddedIdentifierInstanceAsEntity( data ) ) { + data.setEntityInstanceForNotify( rowProcessingState.getEntityId() ); + data.setInstance( data.getEntityInstanceForNotify() ); + } + else { + return reactiveResolveEntityInstance1( data ) + .thenAccept( v -> { + if ( data.getUniqueKeyAttributePath() != null ) { + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); + final EntityUniqueKey euk = new EntityUniqueKey( + concreteDescriptor.getEntityName(), + data.getUniqueKeyAttributePath(), + rowProcessingState.getEntityUniqueKey(), + data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], + session.getFactory() + ); + session.getPersistenceContextInternal().addEntity( euk, data.getInstance() ); + } + postResolveInstance( data ); + } ); + } + postResolveInstance( data ); + return voidFuture(); + } + + private void postResolveInstance(ReactiveEntityInitializerData data) { + if ( data.getInstance() != null ) { + upgradeLockMode( data ); + if ( data.getState() == State.INITIALIZED ) { + registerReloadedEntity( data ); + if ( data.getRowProcessingState().needsResolveState() ) { + // We need to read result set values to correctly populate the query cache + resolveState( data ); + } + } + if ( data.getShallowCached() ) { + initializeSubInstancesFromParent( data ); + } + } + } + + @Override + public CompletionStage reactiveInitializeInstance(EntityInitializerData data) { + if ( data.getState() != State.RESOLVED ) { + return voidFuture(); + } + if ( !skipInitialization( data ) ) { + assert consistentInstance( data ); + return reactiveInitializeEntityInstance( (ReactiveEntityInitializerData) data ); + } + data.setState( State.INITIALIZED ); + return voidFuture(); + } + + protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object entityIdentifier = data.getEntityKey().getIdentifier(); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + + return reactiveExtractConcreteTypeStateValues( data ) + .thenAccept( resolvedEntityState -> { + + preLoad( data, resolvedEntityState ); + + if ( isPersistentAttributeInterceptable( data.getEntityInstanceForNotify() ) ) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = + asPersistentAttributeInterceptable( data.getEntityInstanceForNotify() ).$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor == null + || persistentAttributeInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // if we do this after the entity has been initialized the + // BytecodeLazyAttributeInterceptor#isAttributeLoaded(String fieldName) would return false; + data.getConcreteDescriptor().getBytecodeEnhancementMetadata() + .injectInterceptor( data.getEntityInstanceForNotify(), entityIdentifier, session ); + } + } + data.getConcreteDescriptor().setPropertyValues( data.getEntityInstanceForNotify(), resolvedEntityState ); + + persistenceContext.addEntity( data.getEntityKey(), data.getEntityInstanceForNotify() ); + + // Also register possible unique key entries + registerPossibleUniqueKeyEntries( data, resolvedEntityState, session ); + + final Object version = getVersionAssembler() != null ? getVersionAssembler().assemble( rowProcessingState ) : null; + final Object rowId = getRowIdAssembler() != null ? getRowIdAssembler().assemble( rowProcessingState ) : null; + + // from the perspective of Hibernate, an entity is read locked as soon as it is read + // so regardless of the requested lock mode, we upgrade to at least the read level + final LockMode lockModeToAcquire = data.getLockMode() == LockMode.NONE ? LockMode.READ : data.getLockMode(); + + final EntityEntry entityEntry = persistenceContext.addEntry( + data.getEntityInstanceForNotify(), + Status.LOADING, + resolvedEntityState, + rowId, + data.getEntityKey().getIdentifier(), + version, + lockModeToAcquire, + true, + data.getConcreteDescriptor(), + false + ); + data.getEntityHolder().setEntityEntry( entityEntry ); + + registerNaturalIdResolution( data, persistenceContext, resolvedEntityState ); + + takeSnapshot( data, session, persistenceContext, entityEntry, resolvedEntityState ); + + data.getConcreteDescriptor().afterInitialize( data.getEntityInstanceForNotify(), session ); + + assert data.getConcreteDescriptor().getIdentifier( data.getEntityInstanceForNotify(), session ) != null; + + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + if ( !rowProcessingState.isQueryCacheHit() ) { + statistics.loadEntity( data.getConcreteDescriptor().getEntityName() ); + } + } + updateCaches( + data, + session, + session.getPersistenceContextInternal(), + resolvedEntityState, + version + ); + } ); + } + + protected CompletionStage reactiveExtractConcreteTypeStateValues(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object[] values = new Object[data.getConcreteDescriptor().getNumberOfAttributeMappings()]; + final DomainResultAssembler[] concreteAssemblers = getAssemblers()[data.getConcreteDescriptor().getSubclassId()]; + return loop( 0, values.length, i -> { + final DomainResultAssembler assembler = concreteAssemblers[i]; + if ( assembler instanceof ReactiveEntityAssembler ) { + return ( (ReactiveEntityAssembler) assembler ) + .reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + .thenAccept( assembled -> values[i] = assembled ); + } + values[i] = assembler == null ? UNFETCHED_PROPERTY : assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( v -> values ); + } + + protected CompletionStage reactiveResolveEntityInstance1(ReactiveEntityInitializerData data) { + final Object proxy = data.getEntityHolder().getProxy(); + final boolean unwrapProxy = proxy != null && getInitializedPart() instanceof ToOneAttributeMapping + && ( (ToOneAttributeMapping) getInitializedPart() ).isUnwrapProxy() + && getConcreteDescriptor( data ).getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + final Object entityFromExecutionContext; + if ( !unwrapProxy && isProxyInstance( proxy ) ) { + if ( ( entityFromExecutionContext = getEntityFromExecutionContext( data ) ) != null ) { + data.setEntityInstanceForNotify( entityFromExecutionContext ); + data.setInstance( data.getEntityInstanceForNotify() ); + // If the entity comes from the execution context, it is treated as not initialized + // so that we can refresh the data as requested + registerReloadedEntity( data ); + } + else { + data.setInstance( proxy ); + if ( Hibernate.isInitialized( data.getInstance() ) ) { + data.setState( State.INITIALIZED ); + data.setEntityInstanceForNotify( Hibernate.unproxy( data.getInstance() ) ); + } + else { + final LazyInitializer lazyInitializer = extractLazyInitializer( data.getInstance() ); + assert lazyInitializer != null; + return reactiveResolveEntityInstance2( data ) + .thenAccept( entityInstance -> { + data.setEntityInstanceForNotify( entityInstance ); + lazyInitializer.setImplementation( data.getEntityInstanceForNotify() ); + ensureEntityIsInitialized( data ); + } ); + } + } + } + else { + final Object existingEntity = data.getEntityHolder().getEntity(); + if ( existingEntity != null ) { + data.setEntityInstanceForNotify( existingEntity ); + data.setInstance( data.getEntityInstanceForNotify() ); + if ( data.getEntityHolder().getEntityInitializer() == null ) { + assert data.getEntityHolder().isInitialized() == isExistingEntityInitialized( existingEntity ); + if ( data.getEntityHolder().isInitialized() ) { + data.setState( State.INITIALIZED ); + } + else if ( isResultInitializer() ) { + registerLoadingEntity( data, data.getInstance() ); + } + } + else if ( data.getEntityHolder().getEntityInitializer() != this ) { + data.setState( State.INITIALIZED ); + } + } + else if ( ( entityFromExecutionContext = getEntityFromExecutionContext( data ) ) != null ) { + // This is the entity to refresh, so don't set the state to initialized + data.setEntityInstanceForNotify( entityFromExecutionContext ); + data.setInstance( data.getEntityInstanceForNotify() ); + if ( isResultInitializer() ) { + registerLoadingEntity( data, data.getInstance() ); + } + } + else { + assert data.getEntityHolder().getEntityInitializer() == this; + // look to see if another initializer from a parent load context or an earlier + // initializer is already loading the entity + return reactiveResolveEntityInstance2( data ) + .thenAccept( entityInstance -> { + data.setEntityInstanceForNotify( entityInstance ); + data.setInstance( data.getEntityInstanceForNotify() ); + final Initializer idInitializer; + if ( data.getEntityHolder().getEntityInitializer() == this && data.getState() != State.INITIALIZED + && getIdentifierAssembler() != null + && ( idInitializer = getIdentifierAssembler().getInitializer() ) != null ) { + // If this is the owning initializer and the returned object is not initialized, + // this means that the entity instance was just instantiated. + // In this case, we want to call "assemble" and hence "initializeInstance" on the initializer + // for possibly non-aggregated identifier mappings, so inject the virtual id representation + idInitializer.initializeInstance( data.getRowProcessingState() ); + } + ensureEntityIsInitialized( data ); + } ); + } + } + ensureEntityIsInitialized( data ); + return voidFuture(); + } + + private void ensureEntityIsInitialized(ReactiveEntityInitializerData data) { + // todo: ensure we initialize the entity + assert !data.getShallowCached() || data.getState() == State.INITIALIZED : "Forgot to initialize the entity"; + } + + protected CompletionStage reactiveResolveEntityInstance2(ReactiveEntityInitializerData data) { + if ( data.getEntityHolder().getEntityInitializer() == this ) { + assert data.getEntityHolder().getEntity() == null; + return reactiveResolveEntityInstance( data ); + } + else { + // the entity is already being loaded elsewhere + return completedFuture( data.getEntityHolder().getEntity() ); + } + } + + protected CompletionStage reactiveResolveEntityInstance(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object resolved = resolveToOptionalInstance( data ); + if ( resolved != null ) { + registerLoadingEntity( data, resolved ); + return completedFuture( resolved ); + } + else { + if ( rowProcessingState.isQueryCacheHit() && getEntityDescriptor().useShallowQueryCacheLayout() ) { + // We must load the entity this way, because the query cache entry contains only the primary key + data.setState( State.INITIALIZED ); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + assert data.getEntityHolder().getEntityInitializer() == this; + // If this initializer owns the entity, we have to remove the entity holder, + // because the subsequent loading process will claim the entity + session.getPersistenceContextInternal().removeEntityHolder( data.getEntityKey() ); + return ( (ReactiveSession) session ).reactiveInternalLoad( + data.getConcreteDescriptor().getEntityName(), + data.getEntityKey().getIdentifier(), + true, + false + ); + } + // We have to query the second level cache if reference cache entries are used + else if ( getEntityDescriptor().canUseReferenceCacheEntries() ) { + final Object cached = resolveInstanceFromCache( data ); + if ( cached != null ) { + // EARLY EXIT!!! + // because the second level cache has reference cache entries, the entity is initialized + data.setState( State.INITIALIZED ); + return completedFuture( cached ); + } + } + final Object instance = instantiateEntity( data ); + registerLoadingEntity( data, instance ); + return completedFuture( instance ); + } + } + + // FIXME: I could change the scope of this method in ORM + private Object resolveToOptionalInstance(ReactiveEntityInitializerData data) { + if ( isResultInitializer() ) { + // this isEntityReturn bit is just for entity loaders, not hql/criteria + final JdbcValuesSourceProcessingOptions processingOptions = + data.getRowProcessingState().getJdbcValuesSourceProcessingState().getProcessingOptions(); + return matchesOptionalInstance( data, processingOptions ) ? processingOptions.getEffectiveOptionalObject() : null; + } + else { + return null; + } + } + + // FIXME: I could change the scope of this method in ORM + private boolean isProxyInstance(Object proxy) { + return proxy != null + && ( proxy instanceof MapProxy || getEntityDescriptor().getJavaType().getJavaTypeClass().isInstance( proxy ) ); + } + + // FIXME: I could change the scope of this method in ORM + private Object resolveInstanceFromCache(ReactiveEntityInitializerData data) { + return CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( + data.getRowProcessingState().getSession().asEventSource(), + null, + data.getLockMode(), + getEntityDescriptor(), + data.getEntityKey() + ); + } + + // FIXME: I could change the scope of this method in ORM + private boolean matchesOptionalInstance( + ReactiveEntityInitializerData data, + JdbcValuesSourceProcessingOptions processingOptions) { + final Object optionalEntityInstance = processingOptions.getEffectiveOptionalObject(); + final Object requestedEntityId = processingOptions.getEffectiveOptionalId(); + return requestedEntityId != null + && optionalEntityInstance != null + && requestedEntityId.equals( data.getEntityKey().getIdentifier() ); + } + + private boolean isExistingEntityInitialized(Object existingEntity) { + return Hibernate.isInitialized( existingEntity ); + } + + @Override + public CompletionStage reactiveResolveKey(EntityInitializerData data) { + return reactiveResolveKey( (ReactiveEntityInitializerData) data, false ); + } + + protected CompletionStage reactiveResolveKey(ReactiveEntityInitializerData data, boolean entityKeyOnly) { + // todo (6.0) : atm we do not handle sequential selects + // - see AbstractEntityPersister#hasSequentialSelect and + // AbstractEntityPersister#getSequentialSelect in 5.2 + if ( data.getState() != State.UNINITIALIZED ) { + return voidFuture(); + } + data.setState( State.KEY_RESOLVED ); + + // reset row state + data.setConcreteDescriptor( null ); + data.setEntityKey( null ); + data.setInstance( null ); + data.setEntityInstanceForNotify( null ); + data.setEntityHolder( null ); + + final Object[] id = new Object[1]; + return initializeId( data, id, entityKeyOnly ) + .thenCompose( initialized -> { + if ( initialized ) { + resolveEntityKey( data, id[0] ); + if ( !entityKeyOnly ) { + // Resolve the entity instance early as we have no key many-to-one + return reactiveResolveInstance( data ) + .thenCompose( v -> { + if ( !data.getShallowCached() ) { + if ( data.getState() == State.INITIALIZED ) { + if ( data.getEntityHolder().getEntityInitializer() == null ) { + // The entity is already part of the persistence context, + // so let's figure out the loaded state and only run sub-initializers if necessary + return reactiveResolveInstanceSubInitializers( data ); + } + // If the entity is initialized and getEntityInitializer() == this, + // we already processed a row for this entity before, + // but we still have to call resolveKeySubInitializers to activate sub-initializers, + // because a row might contain data that sub-initializers want to consume + else { + // todo: try to diff the eagerness of the sub-initializers to avoid further processing + return reactiveResolveKeySubInitializers( data ); + } + } + else { + return reactiveResolveKeySubInitializers( data ); + } + } + return voidFuture(); + } ); + } + } + return voidFuture(); + } ); + } + + + protected CompletionStage reactiveResolveInstanceSubInitializers(ReactiveEntityInitializerData data) { + final Initializer[] initializers = getSubInitializers()[data.getConcreteDescriptor().getSubclassId()]; + if ( initializers.length == 0 ) { + return voidFuture(); + } + final EntityEntry entityEntry = data.getEntityHolder().getEntityEntry(); + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + assert entityEntry == rowProcessingState.getSession() + .getPersistenceContextInternal() + .getEntry( data.getEntityInstanceForNotify() ); + final Object[] loadedState = entityEntry.getLoadedState(); + final Object[] state; + if ( loadedState == null ) { + if ( entityEntry.getStatus() == Status.READ_ONLY ) { + state = data.getConcreteDescriptor().getValues( data.getEntityInstanceForNotify() ); + } + else { + // This branch is entered when a load happens while a cache entry is assembling. + // The EntityEntry has the LOADING state, but the loaded state is still empty. + assert entityEntry.getStatus() == Status.LOADING; + // Just skip any initialization in this case as the cache entry assembling will take care of it + return voidFuture(); + } + } + else { + state = loadedState; + } + return loop( 0, initializers.length, i -> { + final Initializer initializer = initializers[i]; + if ( initializer != null ) { + final Object subInstance = state[i]; + if ( subInstance == UNFETCHED_PROPERTY ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ) + .reactiveResolveKey( rowProcessingState ); + } + else { + // Go through the normal initializer process + initializer.resolveKey( rowProcessingState ); + } + } + else { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ) + .reactiveResolveInstance( subInstance, rowProcessingState ); + } + else { + initializer.resolveInstance( subInstance, rowProcessingState ); + } + } + } + return voidFuture(); + } ); + } + + protected CompletionStage reactiveResolveKeySubInitializers(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + return loop( + getSubInitializers()[data.getConcreteDescriptor().getSubclassId()], + initializer -> { + if ( initializer != null ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveResolveKey( rowProcessingState ); + } + initializer.resolveKey( rowProcessingState ); + } + return voidFuture(); + } + ); + } + + /** + * Return {@code true} if the identifier has been initialized + */ + private CompletionStage initializeId(ReactiveEntityInitializerData data, Object[] id, boolean entityKeyOnly) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + if ( getIdentifierAssembler() == null ) { + id[0] = rowProcessingState.getEntityId(); + assert id[0] != null : "Initializer requires a not null id for loading"; + return trueFuture(); + } + else { + //noinspection unchecked + final Initializer initializer = (Initializer) getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + final InitializerData subData = initializer.getData( rowProcessingState ); + return ( (ReactiveInitializer) initializer ) + .reactiveResolveKey( subData ) + .thenCompose( v -> { + if ( subData.getState() == State.MISSING ) { + setMissing( data ); + return falseFuture(); + } + else { + data.setConcreteDescriptor( determineConcreteEntityDescriptor( + rowProcessingState, + getDiscriminatorAssembler(), + getEntityDescriptor() + ) ); + assert data.getConcreteDescriptor() != null; + if ( isKeyManyToOne() ) { + if ( !data.getShallowCached() && !entityKeyOnly ) { + resolveKeySubInitializers( data ); + } + return falseFuture(); + } + } + id[0] = getIdentifierAssembler().assemble( rowProcessingState ); + if ( id[0] == null ) { + setMissing( data ); + return falseFuture(); + } + return trueFuture(); + } ); + } + id[0] = getIdentifierAssembler().assemble( rowProcessingState ); + if ( id[0] == null ) { + setMissing( data ); + return falseFuture(); + } + return trueFuture(); + } + } + + @Override + protected EntityInitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEntityInitializerData( this, rowProcessingState ); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + return voidFuture() + .thenCompose( v -> { + if ( getKeyAssembler() != null ) { + final Initializer initializer = getKeyAssembler().getInitializer(); + if ( initializer != null ) { + return consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ); + } + } + return voidFuture(); + } ) + .thenCompose( v -> { + if ( getIdentifierAssembler() != null ) { + final Initializer initializer = getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ); + } + } + return voidFuture(); + } ) + .thenCompose( v -> { + final ReactiveEntityInitializerDataAdaptor entityInitializerData = new ReactiveEntityInitializerDataAdaptor( + (EntityInitializerData) data ); + if ( entityInitializerData.getConcreteDescriptor() == null ) { + return loop( getSubInitializers(), initializers -> + loop( initializers, initializer -> { + if ( initializer != null ) { + return consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ); + } + return voidFuture(); + } ) + ); + } + else { + Initializer[] subInitializers = getSubInitializers()[entityInitializerData.getConcreteDescriptor() + .getSubclassId()]; + return loop( subInitializers, initializer -> consumer + .apply( (ReactiveInitializer) initializer, rowProcessingState ) + ); + } + } ); + } + + private static class ReactiveEntityInitializerDataAdaptor extends EntityInitializerData { + + public ReactiveEntityInitializerDataAdaptor(EntityInitializerData delegate) { + super( delegate ); + } + + public EntityPersister getConcreteDescriptor() { + return concreteDescriptor; + } + } + + @Override + public Object getResolvedInstance(EntityInitializerData data) { + return super.getResolvedInstance( data ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java deleted file mode 100644 index 4cb7d5b001..0000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java +++ /dev/null @@ -1,133 +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.sql.results.graph.entity.internal; - -import org.hibernate.FetchNotFoundException; -import org.hibernate.Hibernate; -import org.hibernate.LockMode; -import org.hibernate.annotations.NotFoundAction; -import org.hibernate.internal.log.LoggingHelper; -import org.hibernate.metamodel.mapping.AttributeMapping; -import org.hibernate.reactive.sql.results.graph.entity.ReactiveAbstractEntityInitializer; -import org.hibernate.spi.NavigablePath; -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.Fetch; -import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; -import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; - -/** - * Basically a copy of {@link org.hibernate.sql.results.graph.entity.internal.EntityJoinedFetchInitializer}. - * - * We could delegate but it would require override a huge amount of methods. - * - * @see org.hibernate.sql.results.graph.entity.internal.EntityJoinedFetchInitializer - */ -public class ReactiveEntityJoinedFetchInitializer extends ReactiveAbstractEntityInitializer { - - private static final String CONCRETE_NAME = ReactiveEntityJoinedFetchInitializer.class.getSimpleName(); - - private final DomainResultAssembler keyAssembler; - private final NotFoundAction notFoundAction; - - public ReactiveEntityJoinedFetchInitializer( - EntityResultGraphNode resultDescriptor, - EntityValuedFetchable referencedFetchable, - NavigablePath navigablePath, - LockMode lockMode, - NotFoundAction notFoundAction, - DomainResult keyResult, - DomainResult rowIdResult, - Fetch identifierFetch, - Fetch discriminatorFetch, - FetchParentAccess parentAccess, - AssemblerCreationState creationState) { - super( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - parentAccess, - creationState - ); - assert getInitializedPart() == referencedFetchable; - this.notFoundAction = notFoundAction; - - this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState ); - } - - @Override - public void resolveKey(RowProcessingState rowProcessingState) { - if ( isParentShallowCached() ) { - state = State.MISSING; - } - else if ( state == State.UNINITIALIZED ) { - if ( shouldSkipInitializer( rowProcessingState ) ) { - state = State.MISSING; - return; - } - - super.resolveKey( rowProcessingState ); - - // super processes the foreign-key target column. here we - // need to also look at the foreign-key value column to check - // for a dangling foreign-key - - if ( keyAssembler != null ) { - final Object fkKeyValue = keyAssembler.assemble( rowProcessingState ); - if ( fkKeyValue != null ) { - if ( state == State.MISSING ) { - if ( notFoundAction != NotFoundAction.IGNORE ) { - throw new FetchNotFoundException( - getEntityDescriptor().getEntityName(), - fkKeyValue - ); - } - else { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "Ignoring dangling foreign-key due to `@NotFound(IGNORE); association will be null - %s", - getNavigablePath() - ); - } - } - } - } - } - } - - @Override - public void initializeInstanceFromParent(Object parentInstance, RowProcessingState rowProcessingState) { - final AttributeMapping attributeMapping = getInitializedPart().asAttributeMapping(); - final Object instance = attributeMapping != null - ? attributeMapping.getValue( parentInstance ) - : parentInstance; - setEntityInstance( instance ); - setEntityInstanceForNotify( Hibernate.unproxy( instance ) ); - state = State.INITIALIZED; - initializeSubInstancesFromParent( rowProcessingState ); - } - - @Override - protected String getSimpleConcreteImplName() { - return CONCRETE_NAME; - } - - @Override - public boolean isResultInitializer() { - return false; - } - - @Override - public String toString() { - return "ReactiveEntityJoinedFetchInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java deleted file mode 100644 index fde931db79..0000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java +++ /dev/null @@ -1,64 +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.sql.results.graph.entity.internal; - - -import org.hibernate.LockMode; -import org.hibernate.reactive.sql.results.graph.entity.ReactiveAbstractEntityInitializer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.basic.BasicFetch; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; - -/** - * @see org.hibernate.sql.results.graph.entity.internal.EntityResultInitializer - */ -public class ReactiveEntityResultInitializer extends ReactiveAbstractEntityInitializer { - private static final String CONCRETE_NAME = ReactiveEntityResultInitializer.class.getSimpleName(); - - public ReactiveEntityResultInitializer( - EntityResultGraphNode resultDescriptor, - NavigablePath navigablePath, - LockMode lockMode, - Fetch identifierFetch, - BasicFetch discriminatorFetch, - DomainResult rowIdResult, - AssemblerCreationState creationState) { - super( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - null, - creationState - ); - } - - @Override - protected String getSimpleConcreteImplName() { - return CONCRETE_NAME; - } - - @Override - public String toString() { - return CONCRETE_NAME + "(" + getNavigablePath() + ")"; - } - - @Override - public boolean isPartOfKey() { - // The entity result itself can never be part of the key - return false; - } - - @Override - public boolean isResultInitializer() { - return true; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java index 88300f7fb8..61ad620db4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java @@ -10,114 +10,82 @@ import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.spi.EntityIdentifierNavigablePath; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; /** * @see org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchByUniqueKeyInitializer */ -public class ReactiveEntitySelectFetchByUniqueKeyInitializer extends ReactiveEntitySelectFetchInitializer { +public class ReactiveEntitySelectFetchByUniqueKeyInitializer + extends ReactiveEntitySelectFetchInitializer { private final ToOneAttributeMapping fetchedAttribute; public ReactiveEntitySelectFetchByUniqueKeyInitializer( - FetchParentAccess parentAccess, + InitializerParent parent, ToOneAttributeMapping fetchedAttribute, NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, - DomainResultAssembler keyAssembler) { - super( parentAccess, fetchedAttribute, fetchedNavigable, concreteDescriptor, keyAssembler ); + DomainResult keyResult, + boolean affectedByFilter, + AssemblerCreationState creationState) { + super( + parent, + fetchedAttribute, + fetchedNavigable, + concreteDescriptor, + keyResult, + affectedByFilter, + creationState + ); this.fetchedAttribute = fetchedAttribute; } @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.UNINITIALIZED ) { - return voidFuture(); - } - state = State.RESOLVED; - - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { - state = State.INITIALIZED; - return voidFuture(); - } - - entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - state = State.INITIALIZED; - return voidFuture(); - } - - NavigablePath[] np = { getNavigablePath().getParent() }; - if ( np[0] == null ) { - return voidFuture(); - } - return whileLoop( () -> { - CompletionStage loop = voidFuture(); - // Defer the select by default to the initialize phase - // We only need to select in this phase if this is part of an identifier or foreign key - if ( np[0] instanceof EntityIdentifierNavigablePath - || ForeignKeyDescriptor.PART_NAME.equals( np[0].getLocalName() ) - || ForeignKeyDescriptor.TARGET_PART_NAME.equals( np[0].getLocalName() ) ) { - loop = reactiveInitializeInstance( rowProcessingState ); - } - return loop.thenApply( v -> { - np[0] = np[0].getParent(); - return np[0] != null; - } ); - } ); - } - - @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.RESOLVED ) { - return voidFuture(); - } - state = State.INITIALIZED; - + public CompletionStage reactiveInitialize(EntitySelectFetchInitializerData actual) { + final ReactiveEntitySelectFetchInitializerData data = (ReactiveEntitySelectFetchInitializerData) actual; final String entityName = concreteDescriptor.getEntityName(); final String uniqueKeyPropertyName = fetchedAttribute.getReferencedPropertyName(); - final SharedSessionContractImplementor session = rowProcessingState.getSession(); + + final SharedSessionContractImplementor session = data.getRowProcessingState().getSession(); + final EntityUniqueKey euk = new EntityUniqueKey( entityName, uniqueKeyPropertyName, - entityIdentifier, + data.getEntityIdentifier(), concreteDescriptor.getPropertyType( uniqueKeyPropertyName ), session.getFactory() ); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - setEntityInstance( persistenceContext.getEntity( euk ) ); - if ( entityInstance == null ) { + data.setInstance( persistenceContext.getEntity( euk ) ); + if ( data.getInstance() == null ) { return ( (ReactiveEntityPersister) concreteDescriptor ) - .reactiveLoadByUniqueKey( uniqueKeyPropertyName, entityIdentifier, session ) - .thenAccept( this::setEntityInstance ) + .reactiveLoadByUniqueKey( uniqueKeyPropertyName, data.getEntityIdentifier(), session ) + .thenAccept( data::setInstance ) .thenAccept( v -> { // If the entity was not in the Persistence Context, but was found now, // add it to the Persistence Context - if ( entityInstance != null ) { - persistenceContext.addEntity( euk, entityInstance ); + if ( data.getInstance() != null ) { + persistenceContext.addEntity( euk, data.getInstance() ); } } ); } - if ( entityInstance != null ) { - setEntityInstance( persistenceContext.proxyFor( entityInstance ) ); + if ( data.getInstance() != null ) { + data.setInstance( persistenceContext.proxyFor( data.getInstance() ) ); } return voidFuture(); } - private void setEntityInstance(Object instance) { - entityInstance = instance; + @Override + public Object getResolvedInstance(EntitySelectFetchInitializerData data) { + return super.getResolvedInstance( data ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java index 1a9d14f4ca..b27c0cc933 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java @@ -7,7 +7,9 @@ import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; +import org.hibernate.EntityFilterException; import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; import org.hibernate.annotations.NotFoundAction; @@ -15,268 +17,203 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; -import org.hibernate.spi.EntityIdentifierNavigablePath; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; -import static org.hibernate.internal.log.LoggingHelper.toLoggableString; -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; -import static org.hibernate.sql.results.graph.entity.EntityLoadingLogging.ENTITY_LOADING_LOGGER; /** * @see org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer */ -public class ReactiveEntitySelectFetchInitializer extends EntitySelectFetchInitializer implements ReactiveInitializer { +public class ReactiveEntitySelectFetchInitializer + extends EntitySelectFetchInitializer implements ReactiveInitializer { - private static final String CONCRETE_NAME = ReactiveEntitySelectFetchInitializer.class.getSimpleName(); + public static class ReactiveEntitySelectFetchInitializerData + extends EntitySelectFetchInitializer.EntitySelectFetchInitializerData { + + public ReactiveEntitySelectFetchInitializerData(EntitySelectFetchInitializer initializer, RowProcessingState rowProcessingState) { + super( initializer, rowProcessingState ); + } + + public Object getEntityIdentifier() { + return entityIdentifier; + } + + public void setEntityIdentifier(Object entityIdentifier) { + super.entityIdentifier = entityIdentifier; + } + } private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final boolean isEnhancedForLazyLoading; public ReactiveEntitySelectFetchInitializer( - FetchParentAccess parentAccess, - ToOneAttributeMapping toOneMapping, + InitializerParent parent, + ToOneAttributeMapping fetchedAttribute, NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, - DomainResultAssembler keyAssembler) { - super( parentAccess, toOneMapping, fetchedNavigable, concreteDescriptor, keyAssembler ); + DomainResult keyResult, + boolean affectedByFilter, + AssemblerCreationState creationState) { + super( + parent, + fetchedAttribute, + fetchedNavigable, + concreteDescriptor, + keyResult, + affectedByFilter, + creationState + ); this.isEnhancedForLazyLoading = concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); } + @Override + protected InitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEntitySelectFetchInitializerData( this, rowProcessingState ); + } + @Override public void resolveInstance(RowProcessingState rowProcessingState) { super.resolveInstance( rowProcessingState ); } @Override - public void initializeInstance(RowProcessingState rowProcessingState) { + public void initializeInstance(EntitySelectFetchInitializerData data) { throw LOG.nonReactiveMethodCall( "reactiveInitializeInstance" ); } @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.UNINITIALIZED ) { - return voidFuture(); - } - state = State.RESOLVED; - - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { - state = State.INITIALIZED; - return voidFuture(); - } - - entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - state = State.INITIALIZED; - return voidFuture(); - } - - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isTraceEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( - "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", - StringHelper.collapse( this.getClass().getName() ), - getNavigablePath(), - entityIdentifier - ); - } - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityKey entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); - - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); - if ( holder != null ) { - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Found existing loading entry [%s] - using loading instance", - CONCRETE_NAME, - toLoggableString( - getNavigablePath(), - entityIdentifier - ) - ); - } - entityInstance = holder.getEntity(); - if ( holder.getEntityInitializer() == null ) { - if ( entityInstance != null && Hibernate.isInitialized( entityInstance ) ) { - state = State.INITIALIZED; - return voidFuture(); - } - } - else if ( holder.getEntityInitializer() != this ) { - // the entity is already being loaded elsewhere - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - holder.getEntityInitializer() - ); - } - state = State.INITIALIZED; - return voidFuture(); - } - else if ( entityInstance == null ) { - state = State.INITIALIZED; - return voidFuture(); - } - } - - NavigablePath[] np = { getNavigablePath().getParent() }; - if ( np[0] == null ) { - return voidFuture(); - } - return whileLoop( () -> { - CompletionStage loop = voidFuture(); - // Defer the select by default to the initialize phase - // We only need to select in this phase if this is part of an identifier or foreign key - if ( np[0] instanceof EntityIdentifierNavigablePath - || ForeignKeyDescriptor.PART_NAME.equals( np[0].getLocalName() ) - || ForeignKeyDescriptor.TARGET_PART_NAME.equals( np[0].getLocalName() ) ) { - loop = reactiveInitializeInstance( rowProcessingState ); - } - return loop.thenApply( v -> { - np[0] = np[0].getParent(); - return np[0] != null; - } ); - } ); + protected void initialize(EntitySelectFetchInitializerData data) { + throw LOG.nonReactiveMethodCall( "reactiveInitialize" ); } @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.RESOLVED ) { - return voidFuture(); - } - state = State.INITIALIZED; - - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { - initializeState(); - return voidFuture(); - } - - entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - initializeState(); - return voidFuture(); - } - - if ( ENTITY_LOADING_LOGGER.isTraceEnabled() ) { - ENTITY_LOADING_LOGGER.tracef( - "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", - StringHelper.collapse( this.getClass().getName() ), - getNavigablePath(), - entityIdentifier - ); + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + Initializer initializer = getKeyAssembler().getInitializer(); + if ( initializer != null ) { + return consumer.apply( (ReactiveInitializer) initializer, data.getRowProcessingState() ); } + return voidFuture(); + } + protected CompletionStage reactiveInitialize(EntitySelectFetchInitializerData ormData) { + ReactiveEntitySelectFetchInitializerData data = (ReactiveEntitySelectFetchInitializerData) ormData; + final RowProcessingState rowProcessingState = data.getRowProcessingState(); final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final String entityName = concreteDescriptor.getEntityName(); - final EntityKey entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); + final EntityKey entityKey = new EntityKey( data.getEntityIdentifier(), concreteDescriptor ); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); if ( holder != null ) { - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Found existing loading entry [%s] - using loading instance", - CONCRETE_NAME, - toLoggableString( - getNavigablePath(), - entityIdentifier - ) - ); - } - entityInstance = holder.getEntity(); + data.setInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); if ( holder.getEntityInitializer() == null ) { - if ( entityInstance != null && Hibernate.isInitialized( entityInstance ) ) { - initializeState(); + if ( data.getInstance() != null && Hibernate.isInitialized( data.getInstance() ) ) { + data.setState( State.INITIALIZED ); return voidFuture(); } } else if ( holder.getEntityInitializer() != this ) { // the entity is already being loaded elsewhere - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - holder.getEntityInitializer() - ); - } - initializeState(); + data.setState( State.INITIALIZED ); return voidFuture(); } - else if ( entityInstance == null ) { - initializeState(); + else if ( data.getInstance() == null ) { + // todo: maybe mark this as resolved instead? + assert holder.getProxy() == null : "How to handle this case?"; + data.setState( State.INITIALIZED ); return voidFuture(); } } + data.setState( State.INITIALIZED ); + final String entityName = concreteDescriptor.getEntityName(); - if ( ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - ENTITY_LOADING_LOGGER.debugf( - "(%s) Invoking session#internalLoad for entity (%s) : %s", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - entityIdentifier - ); - } - - return ReactiveQueryExecutorLookup.extract( session ) - .reactiveInternalLoad( entityName, entityIdentifier, true, toOneMapping().isInternalLoadNullable() ) - .thenCompose( instance -> { - entityInstance = instance; - - if ( entityInstance == null ) { - if ( toOneMapping().getNotFoundAction() == NotFoundAction.EXCEPTION ) { - return failedFuture( new FetchNotFoundException( entityName, entityIdentifier ) ); + return ( (ReactiveSession) session ).reactiveInternalLoad( + entityName, + data.getEntityIdentifier(), + true, + toOneMapping.isInternalLoadNullable() + ) + .thenAccept( instance -> { + data.setInstance( instance ); + + if ( instance == null ) { + if ( toOneMapping.getNotFoundAction() != NotFoundAction.IGNORE ) { + if ( affectedByFilter ) { + throw new EntityFilterException( + entityName, + data.getEntityIdentifier(), + toOneMapping.getNavigableRole().getFullPath() + ); + } + if ( toOneMapping.getNotFoundAction() == NotFoundAction.EXCEPTION ) { + throw new FetchNotFoundException( entityName, data.getEntityIdentifier() ); + } } - } - - if ( ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - ENTITY_LOADING_LOGGER.debugf( - "(%s) Entity [%s] : %s has being loaded by session.internalLoad.", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - entityIdentifier + rowProcessingState.getSession().getPersistenceContextInternal().claimEntityHolderIfPossible( + new EntityKey( data.getEntityIdentifier(), concreteDescriptor ), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this ); } - final boolean unwrapProxy = toOneMapping().isUnwrapProxy() && isEnhancedForLazyLoading; - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( entityInstance ); + final boolean unwrapProxy = toOneMapping.isUnwrapProxy() && isEnhancedForLazyLoading; + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( data.getInstance() ); if ( lazyInitializer != null ) { lazyInitializer.setUnwrap( unwrapProxy ); } - initializeState(); - return voidFuture(); } ); } - protected void initializeState() { - state = State.INITIALIZED; + @Override + public CompletionStage reactiveResolveInstance(Data original) { + if ( original.getState() != State.KEY_RESOLVED ) { + return voidFuture(); + } + + ReactiveEntitySelectFetchInitializerData data = (ReactiveEntitySelectFetchInitializerData) original; + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + data.setEntityIdentifier( keyAssembler.assemble( rowProcessingState ) ); + + if ( data.getEntityIdentifier() == null ) { + data.setState( State.MISSING ); + data.setInstance( null ); + return voidFuture(); + } + + data.setState( State.INITIALIZED ); + return reactiveInitialize( data ); } - protected ToOneAttributeMapping toOneMapping() { - return (ToOneAttributeMapping) getInitializedPart(); + @Override + public CompletionStage reactiveInitializeInstance(Data data) { + if ( data.getState() != State.RESOLVED ) { + return voidFuture(); + } + data.setState( State.INITIALIZED ); + Hibernate.initialize( data.getInstance() ); + return voidFuture(); + } + + @Override + public Object getResolvedInstance(Data data) { + return super.getResolvedInstance( data ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java index 34d25c6bc0..c4cb878380 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java @@ -11,11 +11,13 @@ import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.BatchEntityInsideEmbeddableSelectFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.BatchEntitySelectFetchInitializer; @@ -26,74 +28,94 @@ */ public class ReactiveEntitySelectFetchInitializerBuilder { - public static EntityInitializer createInitializer( - FetchParentAccess parentAccess, + public static EntityInitializer createInitializer( + InitializerParent parent, ToOneAttributeMapping fetchedAttribute, EntityPersister entityPersister, - DomainResult keyResult, + DomainResult originalKeyResult, NavigablePath navigablePath, boolean selectByUniqueKey, + boolean affectedByFilter, AssemblerCreationState creationState) { + final DomainResult keyResult = originalKeyResult instanceof EmbeddableForeignKeyResultImpl + ? new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) originalKeyResult ) + : originalKeyResult; if ( selectByUniqueKey ) { return new ReactiveEntitySelectFetchByUniqueKeyInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } - final BatchMode batchMode = determineBatchMode( entityPersister, parentAccess, creationState ); + final BatchMode batchMode = determineBatchMode( entityPersister, parent, creationState ); switch ( batchMode ) { case NONE: - return new ReactiveEntitySelectFetchInitializer( - parentAccess, + return new ReactiveEntitySelectFetchInitializer<>( + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); case BATCH_LOAD: - if ( parentAccess.isEmbeddableInitializer() ) { + if ( parent.isEmbeddableInitializer() ) { return new BatchEntityInsideEmbeddableSelectFetchInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } else { return new BatchEntitySelectFetchInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } case BATCH_INITIALIZE: return new BatchInitializeEntitySelectFetchInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } throw new IllegalStateException( "Should be unreachable" ); } // FIXME: Use the one in ORM - private static BatchMode determineBatchMode( + public static BatchMode determineBatchMode( EntityPersister entityPersister, - FetchParentAccess parentAccess, + InitializerParent parent, AssemblerCreationState creationState) { - if ( !entityPersister.isBatchLoadable() || creationState.isScrollResult() ) { + if ( !entityPersister.isBatchLoadable() ) { return BatchMode.NONE; } - while ( parentAccess.isEmbeddableInitializer() ) { - final EmbeddableInitializer embeddableInitializer = parentAccess.asEmbeddableInitializer(); + if ( creationState.isDynamicInstantiation() ) { + if ( canBatchInitializeBeUsed( entityPersister ) ) { + return BatchMode.BATCH_INITIALIZE; + } + return BatchMode.NONE; + } + while ( parent.isEmbeddableInitializer() ) { + final EmbeddableInitializer embeddableInitializer = parent.asEmbeddableInitializer(); final EmbeddableValuedModelPart initializedPart = embeddableInitializer.getInitializedPart(); // For entity identifier mappings we can't batch load, // because the entity identifier needs the instance in the resolveKey phase, @@ -101,29 +123,40 @@ private static BatchMode determineBatchMode( if ( initializedPart.isEntityIdentifierMapping() // todo: check if the virtual check is necessary || initializedPart.isVirtual() - // If the parent embeddable has a custom instantiator, we can't inject entities later through setValues() - || !( initializedPart.getMappedType().getRepresentationStrategy().getInstantiator() instanceof StandardEmbeddableInstantiator ) ) { + || initializedPart.getMappedType().isPolymorphic() + // If the parent embeddable has a custom instantiator, + // we can't inject entities later through setValues() + || !( initializedPart.getMappedType().getRepresentationStrategy().getInstantiator() + instanceof StandardEmbeddableInstantiator ) ) { return entityPersister.hasSubclasses() ? BatchMode.NONE : BatchMode.BATCH_INITIALIZE; } - parentAccess = parentAccess.getFetchParentAccess(); - if ( parentAccess == null ) { + parent = parent.getParent(); + if ( parent == null ) { break; } } - if ( parentAccess != null ) { - assert parentAccess.getInitializedPart() instanceof EntityValuedModelPart; - final EntityPersister parentPersister = parentAccess.asEntityInitializer().getEntityDescriptor(); + if ( parent != null ) { + assert parent.getInitializedPart() instanceof EntityValuedModelPart; + final EntityPersister parentPersister = parent.asEntityInitializer().getEntityDescriptor(); final EntityDataAccess cacheAccess = parentPersister.getCacheAccessStrategy(); if ( cacheAccess != null ) { // Do batch initialization instead of batch loading if the parent entity is cacheable // to avoid putting entity state into the cache at a point when the association is not yet set - return BatchMode.BATCH_INITIALIZE; + if ( canBatchInitializeBeUsed( entityPersister ) ) { + return BatchMode.BATCH_INITIALIZE; + } + return BatchMode.NONE; } } return BatchMode.BATCH_LOAD; } - enum BatchMode { + private static boolean canBatchInitializeBeUsed(EntityPersister entityPersister) { + // we need to create a Proxy in order to use batch initialize + return entityPersister.getRepresentationStrategy().getProxyFactory() != null; + } + + private enum BatchMode { NONE, BATCH_LOAD, BATCH_INITIALIZE 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 09a2afaa2d..da94cc3293 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 @@ -6,13 +6,11 @@ package org.hibernate.reactive.sql.results.internal; 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.CompletionException; import java.util.concurrent.CompletionStage; -import java.util.function.Function; import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; @@ -32,6 +30,7 @@ import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; @@ -50,7 +49,6 @@ public class ReactiveDeferredResultSetAccess extends DeferredResultSetAccess imp private CompletionStage resultSetStage; - private Integer columnCount; private ResultSet resultSet; @@ -58,8 +56,9 @@ public ReactiveDeferredResultSetAccess( JdbcOperationQuerySelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, - Function statementCreator) { - super( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator ); + JdbcSelectExecutor.StatementCreator statementCreator, + int resultCountEstimate) { + super( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); this.executionContext = executionContext; this.sqlStatementLogger = executionContext.getSession().getJdbcServices().getSqlStatementLogger(); } 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 094fb28adf..2412f3941b 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 @@ -53,6 +53,11 @@ public void release() { .release( resultSet, resultSetSource ); } + @Override + public int getResultCountEstimate() { + return super.getResultCountEstimate(); + } + @Override public BasicType resolveType(int position, JavaType explicitJavaType, SessionFactoryImplementor sessionFactory) { return super.resolveType( position, explicitJavaType, sessionFactory ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java index 5fc1a50b7f..7adeab775f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java @@ -6,13 +6,16 @@ package org.hibernate.reactive.sql.results.internal; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityAssembler; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityDelayedFetchInitializer; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.FetchParent; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchImpl; public class ReactiveEntityDelayedFetchImpl extends EntityDelayedFetchImpl { @@ -21,20 +24,26 @@ public ReactiveEntityDelayedFetchImpl( ToOneAttributeMapping fetchedAttribute, NavigablePath navigablePath, DomainResult keyResult, - boolean selectByUniqueKey) { - super( fetchParent, fetchedAttribute, navigablePath, keyResult, selectByUniqueKey ); + boolean selectByUniqueKey, + DomainResultCreationState creationState) { + super( fetchParent, fetchedAttribute, navigablePath, keyResult, selectByUniqueKey, creationState ); } @Override - public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { - return new ReactiveEntityDelayedFetchInitializer( parentAccess, - getNavigablePath(), - getEntityValuedModelPart(), - isSelectByUniqueKey(), - getKeyResult().createResultAssembler( - parentAccess, - creationState - ) + public EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return new ReactiveEntityDelayedFetchInitializer( + parent, + getNavigablePath(), + getEntityValuedModelPart(), + isSelectByUniqueKey(), + getKeyResult(), + getDiscriminatorFetch(), + creationState ); } + + @Override + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + return new ReactiveEntityAssembler( getFetchedMapping().getJavaType(), entityInitializer ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java index 1d2adc4ee3..819ba05c63 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java @@ -5,18 +5,31 @@ */ package org.hibernate.reactive.sql.results.internal; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.EntityValuedModelPart; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityAssembler; -import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityResultInitializer; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityInitializerImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingFetch; import org.hibernate.sql.results.graph.entity.internal.EntityResultImpl; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + public class ReactiveEntityResultImpl extends EntityResultImpl { + private static final Log LOG = make( Log.class, lookup() ); + public ReactiveEntityResultImpl( NavigablePath navigablePath, EntityValuedModelPart entityValuedModelPart, @@ -26,23 +39,51 @@ public ReactiveEntityResultImpl( } @Override - public DomainResultAssembler createResultAssembler( - FetchParentAccess parentAccess, + public DomainResultAssembler createResultAssembler( + InitializerParent parent, AssemblerCreationState creationState) { - final Initializer initializer = creationState.resolveInitializer( - getNavigablePath(), - getReferencedModePart(), - () -> new ReactiveEntityResultInitializer( - this, - getNavigablePath(), - getLockMode( creationState ), - getIdentifierFetch(), - getDiscriminatorFetch(), - getRowIdResult(), - creationState - ) + return new ReactiveEntityAssembler( + this.getResultJavaType(), + creationState.resolveInitializer( this, parent, this ).asEntityInitializer() ); + } - return new ReactiveEntityAssembler( this.getResultJavaType(), initializer.asEntityInitializer() ); + @Override + public Initializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return new ReactiveEntityInitializerImpl( + this, + getSourceAlias(), + getIdentifierFetch(), + getDiscriminatorFetch(), + null, + getRowIdResult(), + NotFoundAction.EXCEPTION, + false, + null, + true, + creationState + ); + } + + @Override + public Fetch generateFetchableFetch( + Fetchable fetchable, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + Fetch fetch = super.generateFetchableFetch( + fetchable, + fetchablePath, + fetchTiming, + selected, + resultVariable, + creationState + ); + if ( fetch instanceof NonAggregatedIdentifierMappingFetch ) { + return new ReactiveNonAggregatedIdentifierMappingFetch( (NonAggregatedIdentifierMappingFetch) fetch ); + } + return fetch; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java index e752a49ca0..312f510f5a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java @@ -8,19 +8,11 @@ import java.util.ArrayList; import java.util.Map; -import java.util.concurrent.CompletionStage; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; - -import static org.hibernate.reactive.util.impl.CompletionStages.loop; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * @see org.hibernate.sql.results.internal.InitializersList @@ -28,74 +20,16 @@ public final class ReactiveInitializersList { private final Initializer[] initializers; - private final Initializer[] sortedNonCollectionsFirst; private final Initializer[] sortedForResolveInstance; private final boolean hasCollectionInitializers; - private final Map initializerMap; private ReactiveInitializersList( Initializer[] initializers, - Initializer[] sortedNonCollectionsFirst, Initializer[] sortedForResolveInstance, - boolean hasCollectionInitializers, - Map initializerMap) { + boolean hasCollectionInitializers) { this.initializers = initializers; - this.sortedNonCollectionsFirst = sortedNonCollectionsFirst; this.sortedForResolveInstance = sortedForResolveInstance; this.hasCollectionInitializers = hasCollectionInitializers; - this.initializerMap = initializerMap; - } - - public Initializer resolveInitializer(final NavigablePath path) { - return initializerMap.get( path ); - } - - public void finishUpRow(final RowProcessingState rowProcessingState) { - for ( Initializer init : initializers ) { - init.finishUpRow( rowProcessingState ); - } - } - - public void startLoading(final RowProcessingState rowProcessingState) { - for ( int i = initializers.length - 1; i >= 0; i-- ) { - initializers[i].startLoading( rowProcessingState ); - } - } - - public CompletionStage initializeInstance(final ReactiveRowProcessingState rowProcessingState) { - return loop( initializers, initializer -> { - if ( initializer instanceof ReactiveInitializer ) { - return ( (ReactiveInitializer) initializer ).reactiveInitializeInstance( rowProcessingState ); - } - else { - initializer.initializeInstance( rowProcessingState ); - return voidFuture(); - } - } ); - } - - public void endLoading(final ExecutionContext executionContext) { - for ( Initializer initializer : initializers ) { - initializer.endLoading( executionContext ); - } - } - - public void resolveKeys(final RowProcessingState rowProcessingState) { - for ( Initializer init : sortedNonCollectionsFirst ) { - init.resolveKey( rowProcessingState ); - } - } - - public CompletionStage resolveInstances(final ReactiveRowProcessingState rowProcessingState) { - return loop( sortedNonCollectionsFirst, initializer -> { - if ( initializer instanceof ReactiveInitializer ) { - return ( (ReactiveInitializer) initializer ).reactiveResolveInstance( rowProcessingState ); - } - else { - initializer.resolveInstance( rowProcessingState ); - return voidFuture(); - } - } ); } public boolean hasCollectionInitializers() { @@ -103,17 +37,23 @@ public boolean hasCollectionInitializers() { } static class Builder { - private final ArrayList initializers = new ArrayList<>(); + private final ArrayList> initializers; int nonCollectionInitializersNum = 0; int resolveFirstNum = 0; - public Builder() {} + public Builder() { + initializers = new ArrayList<>(); + } + + public Builder(int size) { + initializers = new ArrayList<>( size ); + } - public void addInitializer(final Initializer initializer) { + public void addInitializer(final Initializer initializer) { initializers.add( initializer ); //in this method we perform these checks merely to learn the sizing hints, //so to not need dynamically scaling collections. - //This implies performing both checks twice but since they're cheap it's preferrable + //This implies performing both checks twice but since they're cheap it's preferable //to multiple allocations; not least this allows using arrays, which makes iteration //cheaper during the row processing - which is very hot. if ( !initializer.isCollectionInitializer() ) { @@ -124,26 +64,17 @@ public void addInitializer(final Initializer initializer) { } } - private static boolean initializeFirst(final Initializer initializer) { + private static boolean initializeFirst(final Initializer initializer) { return !( initializer instanceof EntityDelayedFetchInitializer ) && !( initializer instanceof EntitySelectFetchInitializer ); } - ReactiveInitializersList build(final Map initializerMap) { + ReactiveInitializersList build(final Map> initializerMap) { final int size = initializers.size(); - final Initializer[] sortedNonCollectionsFirst = new Initializer[size]; - final Initializer[] sortedForResolveInstance = new Initializer[size]; - int nonCollectionIdx = 0; - int collectionIdx = nonCollectionInitializersNum; + final Initializer[] sortedForResolveInstance = new Initializer[size]; int resolveFirstIdx = 0; int resolveLaterIdx = resolveFirstNum; - final Initializer[] originalSortInitializers = toArray( initializers ); - for ( Initializer initializer : originalSortInitializers ) { - if ( initializer.isCollectionInitializer() ) { - sortedNonCollectionsFirst[collectionIdx++] = initializer; - } - else { - sortedNonCollectionsFirst[nonCollectionIdx++] = initializer; - } + final Initializer[] originalSortInitializers = toArray( initializers ); + for ( Initializer initializer : originalSortInitializers ) { if ( initializeFirst( initializer ) ) { sortedForResolveInstance[resolveFirstIdx++] = initializer; } @@ -152,18 +83,11 @@ ReactiveInitializersList build(final Map initializer } } final boolean hasCollectionInitializers = ( nonCollectionInitializersNum != initializers.size() ); - return new ReactiveInitializersList( - originalSortInitializers, - sortedNonCollectionsFirst, - sortedForResolveInstance, - hasCollectionInitializers, - initializerMap - ); + return new ReactiveInitializersList( originalSortInitializers, sortedForResolveInstance, hasCollectionInitializers ); } - private Initializer[] toArray(final ArrayList initializers) { - return initializers.toArray( new Initializer[0] ); + private Initializer[] toArray(final ArrayList> initializers) { + return initializers.toArray( new Initializer[initializers.size()] ); } - } } 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 f99a13d535..75a265ed5d 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 @@ -37,6 +37,15 @@ public interface ReactiveResultSetAccess extends JdbcValuesMetadata { SessionFactoryImplementor getFactory(); void release(); + /** + * The estimate for the amount of results that can be expected for pre-sizing collections. + * May return zero or negative values if the count can not be reasonably estimated. + * @since 6.6 + */ + default int getResultCountEstimate() { + return -1; + } + default int getColumnCount() { try { return getResultSet().getMetaData().getColumnCount(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java index d3038621c9..72d5df0c2f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java @@ -5,27 +5,10 @@ */ package org.hibernate.reactive.sql.results.internal; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -import org.hibernate.LockMode; -import org.hibernate.LockOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.results.ResultsLogger; -import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParent; -import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.Initializer; -import org.hibernate.sql.results.graph.InitializerProducer; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingResolution; import org.hibernate.sql.results.spi.RowTransformer; /** @@ -34,126 +17,12 @@ public class ReactiveResultsHelper { public static ReactiveRowReader createRowReader( - ExecutionContext executionContext, - LockOptions lockOptions, + SessionFactoryImplementor sessionFactory, RowTransformer rowTransformer, Class transformedResultJavaType, - JdbcValuesMapping jdbcValuesMapping) { - final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); - - final Map initializerMap = new LinkedHashMap<>(); - final ReactiveInitializersList.Builder initializersBuilder = new ReactiveInitializersList.Builder(); - - final List> assemblers = jdbcValuesMapping.resolveAssemblers( - creationState( executionContext, lockOptions, sessionFactory, initializerMap, initializersBuilder ) - ); - - logInitializers( initializerMap ); - - final ReactiveInitializersList initializersList = initializersBuilder.build( initializerMap ); - return new ReactiveStandardRowReader<>( assemblers, initializersList, rowTransformer, transformedResultJavaType ); - } - - private static AssemblerCreationState creationState( - ExecutionContext executionContext, - LockOptions lockOptions, - SessionFactoryImplementor sessionFactory, - Map initializerMap, - ReactiveInitializersList.Builder initializersBuilder) { - return new AssemblerCreationState() { - - @Override - public boolean isScrollResult() { - return executionContext.isScrollResult(); - } - - @Override - public LockMode determineEffectiveLockMode(String identificationVariable) { - return lockOptions.getEffectiveLockMode( identificationVariable ); - } - - @Override - public Initializer resolveInitializer( - NavigablePath navigablePath, - ModelPart fetchedModelPart, - Supplier producer) { - return resolveInitializer( - navigablePath, - fetchedModelPart, - null, - null, - (resultGraphNode, parentAccess, creationState) -> producer.get() - ); - } - - @Override - public

Initializer resolveInitializer( - P resultGraphNode, - FetchParentAccess parentAccess, - InitializerProducer

producer) { - return resolveInitializer( - resultGraphNode.getNavigablePath(), - resultGraphNode.getReferencedModePart(), - resultGraphNode, - parentAccess, - producer - ); - } - - public Initializer resolveInitializer( - NavigablePath navigablePath, - ModelPart fetchedModelPart, - T resultGraphNode, - FetchParentAccess parentAccess, - InitializerProducer producer) { - - final Initializer existing = initializerMap.get( navigablePath ); - if ( existing != null ) { - if ( fetchedModelPart.getNavigableRole().equals( - existing.getInitializedPart().getNavigableRole() ) ) { - ResultsLogger.RESULTS_MESSAGE_LOGGER.tracef( - "Returning previously-registered initializer : %s", - existing - ); - return existing; - } - } - - final Initializer initializer = producer.createInitializer( resultGraphNode, parentAccess, this ); - ResultsLogger.RESULTS_MESSAGE_LOGGER.tracef( "Registering initializer : %s", initializer ); - - initializerMap.put( navigablePath, initializer ); - initializersBuilder.addInitializer( initializer ); - - return initializer; - } - - @Override - public SqlAstCreationContext getSqlAstCreationContext() { - return sessionFactory; - } - - @Override - public ExecutionContext getExecutionContext() { - return executionContext; - } - }; - } - - private static void logInitializers(Map initializerMap) { - if ( ! ResultsLogger.RESULTS_MESSAGE_LOGGER.isDebugEnabled() ) { - return; - } - - ResultsLogger.RESULTS_MESSAGE_LOGGER.debug( "Initializer list" ); - initializerMap.forEach( (navigablePath, initializer) -> { - ResultsLogger.RESULTS_MESSAGE_LOGGER.debugf( - " %s -> %s@%s (%s)", - navigablePath, - initializer, - initializer.hashCode(), - initializer.getInitializedPart() - ); - } ); + ReactiveValuesResultSet resultSet) { + final JdbcValuesMappingResolution jdbcValuesMappingResolution = resultSet + .getValuesMapping().resolveAssemblers( sessionFactory ); + return new ReactiveStandardRowReader<>( jdbcValuesMappingResolution, rowTransformer, transformedResultJavaType ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java new file mode 100644 index 0000000000..3fedd16840 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java @@ -0,0 +1,40 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.internal; + +import java.util.concurrent.CompletionStage; + +import org.hibernate.sql.results.internal.RowTransformerArrayImpl; +import org.hibernate.sql.results.spi.RowTransformer; + +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; + +/** + * @see org.hibernate.sql.results.internal.RowTransformerArrayImpl + */ +public class ReactiveRowTransformerArrayImpl implements RowTransformer> { + + /** + * Singleton access + * + * @see #instance() + */ + private static final RowTransformerArrayImpl INSTANCE = new RowTransformerArrayImpl(); + + public static RowTransformerArrayImpl instance() { + return INSTANCE; + } + + // I'm not sure why I need this + public static RowTransformer> asRowTransformer() { + return (RowTransformer) INSTANCE; + } + + @Override + public CompletionStage transformRow(Object[] objects) { + return completedFuture( objects ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java index 7fda677754..87aa57103c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java @@ -5,21 +5,28 @@ */ package org.hibernate.reactive.sql.results.internal; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionStage; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingResolution; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.sql.results.spi.RowTransformer; import org.hibernate.type.descriptor.java.JavaType; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.sql.results.LoadingLogger.LOGGER; @@ -30,23 +37,86 @@ */ public class ReactiveStandardRowReader implements ReactiveRowReader { - private final List> resultAssemblers; - private final ReactiveInitializersList initializers; + private static final Log LOG = make( Log.class, lookup() ); + + private final DomainResultAssembler[] resultAssemblers; + private final Initializer[] resultInitializers; + private final InitializerData[] resultInitializersData; + private final Initializer[] initializers; + private final InitializerData[] initializersData; + private final Initializer[] sortedForResolveInstance; + private final InitializerData[] sortedForResolveInstanceData; + private final boolean hasCollectionInitializers; private final RowTransformer rowTransformer; private final Class domainResultJavaType; + private final ComponentType componentType; + private final Class resultElementClass; + private final int assemblerCount; public ReactiveStandardRowReader( - List> resultAssemblers, - ReactiveInitializersList initializers, + JdbcValuesMappingResolution jdbcValuesMappingResolution, + RowTransformer rowTransformer, + Class domainResultJavaType) { + this( + jdbcValuesMappingResolution.getDomainResultAssemblers(), + jdbcValuesMappingResolution.getResultInitializers(), + jdbcValuesMappingResolution.getInitializers(), + jdbcValuesMappingResolution.getSortedForResolveInstance(), + jdbcValuesMappingResolution.hasCollectionInitializers(), + rowTransformer, + domainResultJavaType + ); + } + + public ReactiveStandardRowReader( + DomainResultAssembler[] resultAssemblers, + Initializer[] resultInitializers, + Initializer[] initializers, + Initializer[] sortedForResolveInitializers, + boolean hasCollectionInitializers, RowTransformer rowTransformer, Class domainResultJavaType) { this.resultAssemblers = resultAssemblers; - this.initializers = initializers; + this.resultInitializers = (Initializer[]) resultInitializers; + this.resultInitializersData = new InitializerData[resultInitializers.length]; + this.initializers = (Initializer[]) initializers; + this.initializersData = new InitializerData[initializers.length]; + this.sortedForResolveInstance = (Initializer[]) sortedForResolveInitializers; + this.sortedForResolveInstanceData = new InitializerData[sortedForResolveInstance.length]; + this.hasCollectionInitializers = hasCollectionInitializers; this.rowTransformer = rowTransformer; - this.assemblerCount = resultAssemblers.size(); this.domainResultJavaType = domainResultJavaType; + this.assemblerCount = resultAssemblers.length; + if ( domainResultJavaType == null + || domainResultJavaType == Object[].class + || domainResultJavaType == Object.class + || !domainResultJavaType.isArray() + || resultAssemblers.length == 1 + && domainResultJavaType == resultAssemblers[0].getAssembledJavaType().getJavaTypeClass() ) { + this.resultElementClass = Object.class; + this.componentType = ComponentType.OBJECT; + } + else { + this.resultElementClass = domainResultJavaType.getComponentType(); + this.componentType = ComponentType.determineComponentType( domainResultJavaType ); + } + } + + @Override + public int getInitializerCount() { + return initializers.length; + } + + @Override + public boolean hasCollectionInitializers() { + return hasCollectionInitializers; + } + + @Override + public R readRow(RowProcessingState processingState) { + throw LOG.nonReactiveMethodCall( "reactiveReadRow" ); } @Override @@ -55,76 +125,356 @@ public CompletionStage reactiveReadRow(ReactiveRowProcessingState rowProcessi return coordinateInitializers( rowProcessingState ) .thenCompose( v -> { - final Object[] resultRow = new Object[assemblerCount]; - return loop( 0, assemblerCount, i -> { - final DomainResultAssembler assembler = resultAssemblers.get( i ); - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); - if ( assembler instanceof ReactiveDomainResultsAssembler ) { - return ( (ReactiveDomainResultsAssembler) assembler ) - .reactiveAssemble( rowProcessingState, options ) - .thenAccept( obj -> resultRow[i] = obj ); - } - resultRow[i] = assembler.assemble( rowProcessingState, options ); - return voidFuture(); - } ) - .thenApply( ignore -> { - afterRow( rowProcessingState ); - return rowTransformer.transformRow( resultRow ); - } ); + // Copied from Hibernate ORM: + // "The following is ugly, but unfortunately necessary to not hurt performance. + // This implementation was micro-benchmarked and discussed with Francesco Nigro, + // who hinted that using this style instead of the reflective Array.getLength(), Array.set() + // is easier for the JVM to optimize" + switch ( componentType ) { + case BOOLEAN: + return booleanComponent( resultAssemblers, rowProcessingState, options ); + case BYTE: + return byteComponent( resultAssemblers, rowProcessingState, options ); + case CHAR: + return charComponent( resultAssemblers, rowProcessingState, options ); + case SHORT: + return shortComponent( resultAssemblers, rowProcessingState, options ); + case INT: + return intComponent( resultAssemblers, rowProcessingState, options ); + case LONG: + return longComponent( resultAssemblers, rowProcessingState, options ); + case FLOAT: + return floatComponent( resultAssemblers, rowProcessingState, options ); + case DOUBLE: + return doubleComponent( resultAssemblers, rowProcessingState, options ); + default: + return objectComponent( resultAssemblers, rowProcessingState, options ); + } } ); } - @Override - public Class getDomainResultResultJavaType() { - return domainResultJavaType; + private CompletionStage booleanComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final boolean[] resultRow = new boolean[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (boolean) obj ); + } + resultRow[i] = (boolean) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage byteComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final byte[] resultRow = new byte[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (byte) obj ); + } + resultRow[i] = (byte) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage charComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final char[] resultRow = new char[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (char) obj ); + } + resultRow[i] = (char) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage shortComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final short[] resultRow = new short[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (short) obj ); + } + resultRow[i] = (short) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage intComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final int[] resultRow = new int[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (int) obj ); + } + resultRow[i] = (int) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage longComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final long[] resultRow = new long[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (long) obj ); + } + resultRow[i] = (long) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage floatComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final float[] resultRow = new float[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (float) obj ); + } + resultRow[i] = (float) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage doubleComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final double[] resultRow = new double[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (double) obj ); + } + resultRow[i] = (double) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage objectComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, resultAssemblers.length ); + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = obj ); + } + resultRow[i] = assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return rowTransformer.transformRow( resultRow ); + } ); } @Override - public Class getResultJavaType() { - if ( resultAssemblers.size() == 1 ) { - return resultAssemblers.get( 0 ).getAssembledJavaType().getJavaTypeClass(); - } + public EntityKey resolveSingleResultEntityKey(RowProcessingState rowProcessingState) { + return null; + } - return Object[].class; + @Override + public Class getDomainResultResultJavaType() { + return domainResultJavaType; } @Override public List> getResultJavaTypes() { - List> javaTypes = new ArrayList<>( resultAssemblers.size() ); - for ( DomainResultAssembler resultAssembler : resultAssemblers ) { + List> javaTypes = new ArrayList<>( resultAssemblers.length ); + for ( DomainResultAssembler resultAssembler : resultAssemblers ) { javaTypes.add( resultAssembler.getAssembledJavaType() ); } return javaTypes; } - @Override - public List getInitializers() { - throw new UnsupportedOperationException(); + private void afterRow(RowProcessingState rowProcessingState) { + LOGGER.trace( "ReactiveStandardRowReader#afterRow" ); + finishUpRow(); } - @Override - public ReactiveInitializersList getReactiveInitializersList() { - return initializers; + private void finishUpRow() { + for ( InitializerData data : initializersData ) { + data.setState( Initializer.State.UNINITIALIZED ); + } } - @Override - public R readRow(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { - throw LOG.nonReactiveMethodCall( "reactiveRowReader" ); + private CompletionStage coordinateInitializers(RowProcessingState rowProcessingState) { + return loop( 0, resultInitializers.length, i -> resolveKey( resultInitializers[i], resultInitializersData[i] ) ) + .thenCompose( v -> loop( 0, sortedForResolveInstance.length, i -> resolveInstance( sortedForResolveInstance[i], sortedForResolveInstanceData[i] ) ) ) + .thenCompose( v -> loop( 0, initializers.length, i -> initializeInstance( initializers[i], initializersData[i] ) ) ); } - private void afterRow(RowProcessingState rowProcessingState) { - LOGGER.trace( "ReactiveStandardRowReader#afterRow" ); - initializers.finishUpRow( rowProcessingState ); + private CompletionStage resolveKey(Initializer initializer, InitializerData initializerData) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveResolveKey( initializerData ); + } + initializer.resolveKey( initializerData ); + return voidFuture(); } - private CompletionStage coordinateInitializers(ReactiveRowProcessingState rowProcessingState) { - initializers.resolveKeys( rowProcessingState ); - return initializers.resolveInstances( rowProcessingState ) - .thenCompose( v -> initializers.initializeInstance( rowProcessingState ) ); + private CompletionStage resolveInstance(Initializer initializer, InitializerData initializerData) { + if ( initializerData.getState() == Initializer.State.KEY_RESOLVED ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveResolveInstance( initializerData ); + } + initializer.resolveInstance( initializerData ); + } + return voidFuture(); + } + + private CompletionStage initializeInstance(Initializer initializer, InitializerData initializerData) { + if ( initializerData.getState() == Initializer.State.RESOLVED ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveInitializeInstance( initializerData ); + } + initializer.initializeInstance( initializerData ); + } + return voidFuture(); } @Override - public void finishUp(JdbcValuesSourceProcessingState processingState) { - initializers.endLoading( processingState.getExecutionContext() ); + public void startLoading(RowProcessingState processingState) { + for ( int i = 0; i < resultInitializers.length; i++ ) { + final Initializer initializer = resultInitializers[i]; + initializer.startLoading( processingState ); + resultInitializersData[i] = initializer.getData( processingState ); + } + for ( int i = 0; i < sortedForResolveInstance.length; i++ ) { + sortedForResolveInstanceData[i] = sortedForResolveInstance[i].getData( processingState ); + } + for ( int i = 0; i < initializers.length; i++ ) { + initializersData[i] = initializers[i].getData( processingState ); + } + } + + @Override + public void finishUp(RowProcessingState rowProcessingState) { + for ( int i = 0; i < initializers.length; i++ ) { + initializers[i].endLoading( initializersData[i] ); + } + } + + enum ComponentType { + BOOLEAN( boolean.class ), + BYTE( byte.class ), + SHORT( short.class ), + CHAR( char.class ), + INT( int.class ), + LONG( long.class ), + FLOAT( float.class ), + DOUBLE( double.class ), + OBJECT( Object.class ); + + private final Class componentType; + + ComponentType(Class componentType) { + this.componentType = componentType; + } + + public static ComponentType determineComponentType(Class resultType) { + if ( resultType == boolean[].class ) { + return BOOLEAN; + } + if ( resultType == byte[].class ) { + return BYTE; + } + if ( resultType == short[].class ) { + return SHORT; + } + if ( resultType == char[].class ) { + return CHAR; + } + if ( resultType == int[].class ) { + return INT; + } + if ( resultType == long[].class ) { + return LONG; + } + if ( resultType == float[].class ) { + return FLOAT; + } + if ( resultType == double[].class ) { + return DOUBLE; + } + return OBJECT; + } + + public Class getComponentType() { + return componentType; + } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java index d42cdff6c1..1fa443ddfa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java @@ -7,11 +7,13 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityDelayedFetchInitializer; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntitySelectFetchInitializerBuilder; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.internal.domain.CircularFetchImpl; @@ -24,8 +26,8 @@ public ReactiveCircularFetchImpl(CircularFetchImpl original) { } @Override - protected EntityInitializer buildEntitySelectFetchInitializer( - FetchParentAccess parentAccess, + protected EntityInitializer buildEntitySelectFetchInitializer( + InitializerParent parent, ToOneAttributeMapping fetchable, EntityPersister entityPersister, DomainResult keyResult, @@ -33,12 +35,33 @@ protected EntityInitializer buildEntitySelectFetchInitializer( boolean selectByUniqueKey, AssemblerCreationState creationState) { return ReactiveEntitySelectFetchInitializerBuilder.createInitializer( - parentAccess, + parent, fetchable, entityPersister, keyResult, navigablePath, selectByUniqueKey, + false, + creationState + ); + } + + @Override + protected EntityInitializer buildEntityDelayedFetchInitializer( + InitializerParent parent, + NavigablePath referencedPath, + ToOneAttributeMapping fetchable, + boolean selectByUniqueKey, + DomainResult keyResult, + BasicFetch discriminatorFetch, + AssemblerCreationState creationState) { + return new ReactiveEntityDelayedFetchInitializer( + parent, + referencedPath, + fetchable, + selectByUniqueKey, + keyResult, + discriminatorFetch, creationState ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java index 5a563c093b..7c23bc7398 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java @@ -19,9 +19,9 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.spi.LoadContexts; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.EntityJavaType; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; @@ -34,7 +34,6 @@ import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; /** - * * @see org.hibernate.sql.results.spi.ListResultsConsumer */ public class ReactiveListResultsConsumer implements ReactiveResultsConsumer, R> { @@ -45,7 +44,7 @@ public class ReactiveListResultsConsumer implements ReactiveResultsConsumer DE_DUP_CONSUMER = new ReactiveListResultsConsumer<>( FILTER ); private static final ReactiveListResultsConsumer ERROR_DUP_CONSUMER = new ReactiveListResultsConsumer<>( ASSERT ); - private static void validateUniqueResult(Boolean unique) { + private static boolean validateUniqueResult(Boolean unique) { if ( !unique ) { throw new HibernateException( String.format( Locale.ROOT, @@ -53,25 +52,7 @@ private static void validateUniqueResult(Boolean unique) { ASSERT ) ); } - } - - private static class RegistrationHandler { - - private final LoadContexts contexts; - private final JdbcValuesSourceProcessingStateStandardImpl state; - - private RegistrationHandler(LoadContexts contexts, JdbcValuesSourceProcessingStateStandardImpl state) { - this.contexts = contexts; - this.state = state; - } - - public void register() { - contexts.register( state ); - } - - public void deregister() { - contexts.deregister( state ); - } + return true; } @Override @@ -101,65 +82,89 @@ public CompletionStage> consume( ? new EntityResult<>( domainResultJavaType ) : new Results<>( domainResultJavaType ); - Supplier> addToResultsSupplier = addToResultsSupplier( results, rowReader, rowProcessingState, processingOptions, isEntityResultType ); - final int[] readRows = { 0 }; + Supplier> addToResultsSupplier = addToResultsSupplier( + results, + rowReader, + rowProcessingState, + processingOptions, + isEntityResultType + ); + final int[] readRows = {0}; return whileLoop( () -> rowProcessingState.next() - .thenCompose( hasNext -> { - if ( hasNext ) { - return addToResultsSupplier.get() - .thenApply( unused -> { - rowProcessingState.finishRowProcessing(); - readRows[0]++; - return true; - } ); - - } - return falseFuture(); - } ) - ) - .thenApply( v -> finishUp( results, jdbcValuesSourceProcessingState, rowReader, persistenceContext, queryOptions, readRows[0] ) ) - .handle( (list, ex) -> { - end( jdbcValues, session, jdbcValuesSourceProcessingState, rowReader, persistenceContext, ex ); - return list; - } ); + .thenCompose( hasNext -> { + if ( hasNext ) { + return addToResultsSupplier.get() + .thenApply( added -> { + rowProcessingState.finishRowProcessing( added ); + readRows[0]++; + return true; + } ); + + } + return falseFuture(); + } ) ) + .thenApply( v -> finishUp( rowReader, rowProcessingState, jdbcValuesSourceProcessingState, results, readRows, queryOptions ) ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> { + end( jdbcValues, session, jdbcValuesSourceProcessingState, persistenceContext, handler.getThrowable() ); + return handler.getResultAsCompletionStage(); + } ); + } + + private List finishUp( + ReactiveRowReader rowReader, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, + Results results, int[] readRows, QueryOptions queryOptions) { + rowReader.finishUp( rowProcessingState ); + jdbcValuesSourceProcessingState.finishUp( readRows[0] > 1 ); + + final ResultListTransformer resultListTransformer = (ResultListTransformer) queryOptions.getResultListTransformer(); + return resultListTransformer != null + ? resultListTransformer.transformList( results.getResults() ) + : results.getResults(); } - private Supplier> addToResultsSupplier( + /** + * The boolean in the CompletionStage is true if the element has been added to the results + */ + private Supplier> addToResultsSupplier( ReactiveListResultsConsumer.Results results, ReactiveRowReader rowReader, ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions processingOptions, boolean isEntityResultType) { if ( this.uniqueSemantic == FILTER - || this.uniqueSemantic == ASSERT && rowProcessingState.hasCollectionInitializers() + || this.uniqueSemantic == ASSERT && rowReader.hasCollectionInitializers() || this.uniqueSemantic == ALLOW && isEntityResultType ) { return () -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) - .thenAccept( results::addUnique ); + .thenApply( results::addUnique ); } if ( this.uniqueSemantic == ASSERT ) { return () -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) .thenApply( results::addUnique ) - .thenAccept( ReactiveListResultsConsumer::validateUniqueResult ); + .thenApply( ReactiveListResultsConsumer::validateUniqueResult ); } return () -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) - .thenAccept( results::add ); + .thenApply( results::add ); } + private void end( ReactiveValuesResultSet jdbcValues, SharedSessionContractImplementor session, JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, - ReactiveRowReader rowReader, PersistenceContext persistenceContext, Throwable ex) { try { - rowReader.finishUp( jdbcValuesSourceProcessingState ); + jdbcValues.finishUp( session ); persistenceContext.afterLoad(); + persistenceContext.getLoadContexts().deregister( jdbcValuesSourceProcessingState ); persistenceContext.initializeNonLazyCollections(); } catch (Throwable e) { @@ -174,26 +179,6 @@ private void end( } } - private List finishUp( - Results results, - JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, - ReactiveRowReader rowReader, PersistenceContext persistenceContext, - QueryOptions queryOptions, - int readRows) { - try { - rowReader.finishUp( jdbcValuesSourceProcessingState ); - jdbcValuesSourceProcessingState.finishUp( readRows > 0 ); - } - finally { - persistenceContext.getLoadContexts().deregister( jdbcValuesSourceProcessingState ); - } - - final ResultListTransformer resultListTransformer = (ResultListTransformer) queryOptions.getResultListTransformer(); - return resultListTransformer != null - ? resultListTransformer.transformList( results.getResults() ) - : results.getResults(); - } - @SuppressWarnings("unchecked") public static ReactiveListResultsConsumer instance(ReactiveListResultsConsumer.UniqueSemantic uniqueSemantic) { switch ( uniqueSemantic ) { @@ -298,8 +283,9 @@ public boolean addUnique(R result) { return true; } - public void add(R result) { + public boolean add(R result) { results.add( result ); + return true; } public List getResults() { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java index 9e0df6d041..fcf8bb760e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java @@ -5,27 +5,13 @@ */ package org.hibernate.reactive.sql.results.spi; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.reactive.sql.results.internal.ReactiveInitializersList; -import org.hibernate.sql.results.internal.InitializersList; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.RowReader; public interface ReactiveRowReader extends RowReader { - Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - CompletionStage reactiveReadRow(ReactiveRowProcessingState processingState, JdbcValuesSourceProcessingOptions options); - - @Override - default InitializersList getInitializersList() { - throw LOG.nonReactiveMethodCall( "getReactiveInitializersList" ); - } - - ReactiveInitializersList getReactiveInitializersList(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java index ad3d17853a..814db1e4be 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java @@ -25,13 +25,13 @@ public CompletionStage consume( JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, ReactiveRowProcessingState rowProcessingState, ReactiveRowReader rowReader) { - rowReader.getReactiveInitializersList().startLoading( rowProcessingState ); + rowReader.startLoading( rowProcessingState ); return rowProcessingState.next() .thenCompose( hasNext -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) .thenApply( result -> { rowProcessingState.finishRowProcessing( true ); - rowReader.finishUp( jdbcValuesSourceProcessingState ); + rowReader.finishUp( rowProcessingState ); jdbcValuesSourceProcessingState.finishUp( false ); return result; } ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java index b8b029fbdc..f0a26dccce 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java @@ -33,7 +33,7 @@ public JdbcType resolveType( if ( realDialect instanceof OracleDialect ) { String typeName = columnTypeInformation == null ? null : columnTypeInformation.getTypeName(); if ( typeName == null || typeName.isBlank() ) { - typeName = ReactiveOracleArrayJdbcType.getTypeName( elementType.getJavaTypeDescriptor(), dialect ); + typeName = ReactiveOracleArrayJdbcType.getTypeName( elementType, dialect ); } return new ReactiveOracleArrayJdbcType( elementType.getJdbcType(), typeName ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java index 8cb2d9cd52..52a9d92066 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java @@ -15,12 +15,17 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.OracleArrayJdbcType; import org.hibernate.reactive.adaptor.impl.ArrayAdaptor; +import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.StructJdbcType; import static java.sql.Types.ARRAY; @@ -29,8 +34,11 @@ */ public class ReactiveOracleArrayJdbcType extends OracleArrayJdbcType { + private final String upperTypeName; + public ReactiveOracleArrayJdbcType(JdbcType elementJdbcType, String typeName) { super( elementJdbcType, typeName ); + this.upperTypeName = typeName == null ? null : typeName.toUpperCase( Locale.ROOT ); } public static String getTypeName(WrapperOptions options, BasicPluralJavaType containerJavaType) { @@ -52,8 +60,10 @@ public ValueBinder getBinder(final JavaType javaTypeDescriptor) { final BasicPluralJavaType containerJavaType = (BasicPluralJavaType) javaTypeDescriptor; return new BasicBinder<>( javaTypeDescriptor, this ) { private String typeName(WrapperOptions options) { - String typeName = getTypeName() == null ? getTypeName( options, containerJavaType ) : getTypeName(); - return typeName.toUpperCase( Locale.ROOT ); + return ( upperTypeName == null + ? getTypeName( options, (BasicPluralJavaType) getJavaType(), (ArrayJdbcType) getJdbcType() ).toUpperCase( Locale.ROOT ) + : upperTypeName + ); } @Override @@ -93,4 +103,71 @@ private ArrayAdaptor getArray(X value, BasicPluralJavaType containerJavaType, } }; } + + /* + * FIXME: We should change the scope of these methods in ORM: see OracleArrayJdbcType#getTypeName + */ + + static String getTypeName(WrapperOptions options, BasicPluralJavaType containerJavaType, ArrayJdbcType arrayJdbcType) { + Dialect dialect = options.getSessionFactory().getJdbcServices().getDialect(); + return getTypeName( containerJavaType.getElementJavaType(), arrayJdbcType.getElementJdbcType(), dialect ); + } + + static String getTypeName(BasicType elementType, Dialect dialect) { + final BasicValueConverter converter = elementType.getValueConverter(); + if ( converter != null ) { + final String simpleName; + if ( converter instanceof JpaAttributeConverter ) { + simpleName = ( (JpaAttributeConverter) converter ).getConverterJavaType() + .getJavaTypeClass() + .getSimpleName(); + } + else { + simpleName = converter.getClass().getSimpleName(); + } + return dialect.getArrayTypeName( + simpleName, + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ); + } + return getTypeName( elementType.getJavaTypeDescriptor(), elementType.getJdbcType(), dialect ); + } + + static String getTypeName(JavaType elementJavaType, JdbcType elementJdbcType, Dialect dialect) { + final String simpleName; + if ( elementJavaType.getJavaTypeClass().isArray() ) { + simpleName = dialect.getArrayTypeName( + elementJavaType.getJavaTypeClass().getComponentType().getSimpleName(), + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ); + } + else if ( elementJdbcType instanceof StructJdbcType ) { + simpleName = ( (StructJdbcType) elementJdbcType ).getStructTypeName(); + } + else { + final Class preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( null ); + if ( preferredJavaTypeClass == elementJavaType.getJavaTypeClass() ) { + simpleName = elementJavaType.getJavaTypeClass().getSimpleName(); + } + else { + if ( preferredJavaTypeClass.isArray() ) { + simpleName = elementJavaType.getJavaTypeClass().getSimpleName() + dialect.getArrayTypeName( + preferredJavaTypeClass.getComponentType().getSimpleName(), + null, + null + ); + } + else { + simpleName = elementJavaType.getJavaTypeClass().getSimpleName() + preferredJavaTypeClass.getSimpleName(); + } + } + } + return dialect.getArrayTypeName( + simpleName, + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ); + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java index c45f8a87b1..0b7e3b41e1 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java @@ -8,8 +8,10 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletionStage; + +import org.hibernate.TransientObjectException; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -22,14 +24,32 @@ import jakarta.persistence.Table; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; @Timeout(value = 10, timeUnit = MINUTES) - public class EagerOneToOneAssociationTest extends BaseReactiveTest { @Override protected Collection> annotatedEntities() { - return List.of( Book.class, Author.class ); + return List.of( Author.class, Book.class ); + } + + @Override + public CompletionStage deleteEntities(Class... entities) { + return getSessionFactory() + .withTransaction( s -> s + .createSelectionQuery( "from Book", Book.class ) + .getResultList() + .thenCompose( books -> loop( books, book -> { + Author author = book.author; + book.author = null; + author.mostPopularBook = null; + return s.remove( book, author ); + } ) + ) + ); } @Test @@ -39,16 +59,48 @@ public void testPersist(VertxTestContext context) { mostPopularBook.setAuthor( author ); author.setMostPopularBook( mostPopularBook ); - test( - context, - openSession() - .thenCompose( s -> s.persist( mostPopularBook ) - .thenCompose( v -> s.persist( author ) ) - .thenCompose( v -> s.flush() ) - ) - .thenCompose( v -> openSession() ) - .thenCompose( s -> s.find( Book.class, 5 ) ) - .thenAccept( Assertions::assertNotNull) + test( context, openSession() + .thenCompose( s -> s + .persist( mostPopularBook ) + .thenCompose( v -> s.persist( author ) ) + .thenCompose( v -> s.flush() ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( s -> s.find( Book.class, 5 ) ) + .thenAccept( book -> { + assertThat( book ).isEqualTo( mostPopularBook ); + assertThat( book.getAuthor() ).isEqualTo( author ); + } ) + ); + } + + @Test + public void testTransientException(VertxTestContext context) { + final Book yellowface = new Book( 7, "Yellowface" ); + final Author kuang = new Author( 19, "R.F. Kuang" ); + yellowface.setAuthor( kuang ); + kuang.setMostPopularBook( yellowface ); + + test( context, assertThrown( TransientObjectException.class, openSession() + .thenCompose( s -> s + .persist( yellowface ) + .thenCompose( v -> s.persist( kuang ) ) + .thenCompose( v -> s.flush() ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( s -> s + .createSelectionQuery( "from Book", Book.class ) + .getResultList() + .thenCompose( books -> s.remove( books.toArray() ) ) + // This query should force an auto-flush. Because we have deleted the book but not the associated author + // it should cause a TransientObjectException. Note that this validation has been added in 6.6, and the same test + // wasn't throwing any exception with ORM 6.5 + .thenCompose( v -> s + .createSelectionQuery( "from Author", Author.class ) + .getResultList() + ) + ) + ) ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java index 097db58ffe..48b73829fa 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java @@ -19,6 +19,7 @@ import io.vertx.junit5.VertxTestContext; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.Table; import jakarta.persistence.metamodel.SingularAttribute; import static java.util.concurrent.TimeUnit.MINUTES; @@ -302,6 +303,7 @@ private void assertOrderByBookArray(List resultList, Book... expectedB } @Entity(name = "Book") + @Table(name = "OrderTest_Book" ) static class Book { @Id String isbn; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java index c25b25b1ee..4ba2e844ed 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java @@ -30,6 +30,7 @@ import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.Root; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; @@ -103,16 +104,10 @@ public void testDeletedStrategyWithYesNoConverter(VertxTestContext context) { @Test public void testDefaults(VertxTestContext context) { - Predicate deleted = obj -> { - switch ( dbType() ) { - case DB2: - return ( (short) obj ) == 1; - case ORACLE: - return ( (Number) obj ).intValue() == 1; - default: - return (boolean) obj; - } - }; + Predicate deleted = obj -> dbType() == DB2 + ? ( (short) obj ) == 1 + : (boolean) obj; + testSoftDelete( context, deleted, "deleted", ImplicitEntity.class, implicitEntities, () -> getMutinySessionFactory().withTransaction( s -> s .remove( s.getReference( ImplicitEntity.class, implicitEntities[0].getId() ) ) @@ -122,16 +117,10 @@ public void testDefaults(VertxTestContext context) { @Test public void testDeletionWithHQLQuery(VertxTestContext context) { - Predicate deleted = obj -> { - switch ( dbType() ) { - case DB2: - return ( (short) obj ) == 1; - case ORACLE: - return ( (Number) obj ).intValue() == 1; - default: - return (boolean) obj; - } - }; + Predicate deleted = obj -> requireNonNull( dbType() ) == DB2 + ? ( (short) obj ) == 1 + : (boolean) obj; + testSoftDelete( context, deleted, "deleted", ImplicitEntity.class, implicitEntities, () -> getMutinySessionFactory().withTransaction( s -> s .createMutationQuery( "delete from ImplicitEntity where name = :name" ) @@ -143,16 +132,10 @@ public void testDeletionWithHQLQuery(VertxTestContext context) { @Test public void testDeletionWithCriteria(VertxTestContext context) { - Predicate deleted = obj -> { - switch ( dbType() ) { - case DB2: - return ( (short) obj ) == 1; - case ORACLE: - return ( (Number) obj ).intValue() == 1; - default: - return (boolean) obj; - } - }; + Predicate deleted = obj -> dbType() == DB2 + ? ( (short) obj ) == 1 + : (boolean) obj; + testSoftDelete( context, deleted, "deleted", ImplicitEntity.class, implicitEntities, () -> getMutinySessionFactory().withTransaction( s -> { CriteriaBuilder cb = getSessionFactory().getCriteriaBuilder(); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java index 874517f344..788adcceab 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java @@ -5,20 +5,11 @@ */ package org.hibernate.reactive; -import static java.util.concurrent.TimeUnit.MINUTES; -import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; +import java.net.URI; import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.dialect.CockroachDialect; -import org.hibernate.dialect.DB2Dialect; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.MariaDBDialect; -import org.hibernate.dialect.MySQLDialect; -import org.hibernate.dialect.OracleDialect; -import org.hibernate.dialect.PostgreSQLDialect; -import org.hibernate.dialect.SQLServerDialect; import org.hibernate.reactive.containers.DatabaseConfiguration; +import org.hibernate.reactive.pool.impl.SqlClientPoolConfiguration; import org.hibernate.reactive.provider.Settings; import org.junit.jupiter.api.Assertions; @@ -26,6 +17,11 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; +import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.SqlConnectOptions; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; @Timeout(value = 10, timeUnit = MINUTES) @@ -33,20 +29,7 @@ public class UriConfigTest extends BaseReactiveTest { @Override protected Configuration constructConfiguration() { - Class dialect; - switch ( dbType() ) { - case POSTGRESQL: dialect = PostgreSQLDialect.class; break; - case COCKROACHDB: dialect = CockroachDialect.class; break; - case MYSQL: dialect = MySQLDialect.class; break; - case MARIA: dialect = MariaDBDialect.class; break; - case SQLSERVER: dialect = SQLServerDialect.class; break; - case DB2: dialect = DB2Dialect.class; break; - case ORACLE: dialect = OracleDialect.class; break; - default: throw new IllegalArgumentException( "Database not recognized: " + dbType().name() ); - } - Configuration configuration = super.constructConfiguration(); - configuration.setProperty( Environment.DIALECT, dialect.getName() ); configuration.setProperty( Settings.URL, DatabaseConfiguration.getUri() ); configuration.setProperty( Settings.SQL_CLIENT_POOL_CONFIG, UriPoolConfiguration.class.getName() ); return configuration; @@ -77,4 +60,30 @@ private String selectQuery() { throw new IllegalArgumentException( "Database not recognized: " + dbType().name() ); } } + + /** + * This class is used by {@link UriConfigTest} to test that one can use a different implementation of + * {@link SqlClientPoolConfiguration}. + *

+ * But, it also test {@link SqlConnectOptions#fromUri(String)} for each database. + *

+ */ + public static class UriPoolConfiguration implements SqlClientPoolConfiguration { + @Override + public PoolOptions poolOptions() { + return new PoolOptions(); + } + + @Override + public SqlConnectOptions connectOptions(URI uri) { + // For CockroachDB we use the PostgreSQL Vert.x client + String uriString = uri.toString().replaceAll( "^cockroach(db)?:", "postgres:" ); + if ( uriString.startsWith( "sqlserver" ) ) { + // Testscontainer adds encrypt=false to the url. The problem is that it uses the JDBC syntax that's + // different from the supported one by the Vert.x driver. + uriString = uriString.replaceAll( ";[e|E]ncrypt=false", "?encrypt=false" ); + } + return SqlConnectOptions.fromUri( uriString ); + } + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java deleted file mode 100644 index 5ac7c404e0..0000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java +++ /dev/null @@ -1,39 +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; - -import java.net.URI; - -import org.hibernate.reactive.pool.impl.SqlClientPoolConfiguration; - -import io.vertx.sqlclient.PoolOptions; -import io.vertx.sqlclient.SqlConnectOptions; - -/** - * This class is used by {@link UriConfigTest} to test that one can use a different implementation of - * {@link SqlClientPoolConfiguration}. - *

- * But, in reality, it also checks {@link SqlConnectOptions#fromUri(String)} works for each database. - *

- */ -public class UriPoolConfiguration implements SqlClientPoolConfiguration { - @Override - public PoolOptions poolOptions() { - return new PoolOptions(); - } - - @Override - public SqlConnectOptions connectOptions(URI uri) { - // For CockroachDB we use the PostgreSQL Vert.x client - String uriString = uri.toString().replaceAll( "^cockroach(db)?:", "postgres:" ); - if ( uriString.startsWith( "sqlserver" ) ) { - // Testscontainer adds encrypt=false to the url. The problem is that it uses the JDBC syntax that's - // different from the supported one by the Vert.x driver. - uriString = uriString.replaceAll( ";encrypt=false", "?encrypt=false" ); - } - return SqlConnectOptions.fromUri( uriString ); - } -} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java index babae4bd47..df28ce62a8 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java @@ -14,13 +14,13 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; +import org.hibernate.reactive.annotations.EnabledFor; import org.hibernate.reactive.containers.DatabaseConfiguration; import org.hibernate.reactive.pool.ReactiveConnectionPool; import org.hibernate.reactive.pool.impl.DefaultSqlClientPool; import org.hibernate.reactive.pool.impl.DefaultSqlClientPoolConfiguration; import org.hibernate.reactive.pool.impl.SqlClientPoolConfiguration; import org.hibernate.reactive.testing.TestingRegistryExtension; -import org.hibernate.reactive.annotations.EnabledFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java index 6f157192ea..9ad2f81365 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java @@ -44,10 +44,9 @@ class OracleDatabase implements TestableDatabase { static { { - expectedDBTypeForClass.put( boolean.class, "NUMBER" ); - expectedDBTypeForClass.put( Boolean.class, "NUMBER" ); + expectedDBTypeForClass.put( boolean.class, "BOOLEAN" ); + expectedDBTypeForClass.put( Boolean.class, "BOOLEAN" ); - // FIXME: [ORM-6] Check if we need alternatives expectedDBTypeForClass.put( NumericBooleanConverter.class, "NUMBER" ); expectedDBTypeForClass.put( YesNoConverter.class, "CHAR" ); expectedDBTypeForClass.put( TrueFalseConverter.class, "CHAR" ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java index 2e26d27515..4f46641a1a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java @@ -10,8 +10,8 @@ import org.hibernate.cfg.Configuration; import org.hibernate.reactive.BaseReactiveTest; -import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.annotations.EnabledFor; +import org.hibernate.reactive.provider.Settings; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; From 2d7f063158e32e598ae4f916244816d95e1f11a1 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 2 Aug 2024 12:11:43 +0200 Subject: [PATCH 10/12] [#1930] Update GitHub action to build and release snapshots --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b156350c55..259a2694c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,12 +1,12 @@ -# GitHub actions for branch testing the latest Hibernate ORM 6.5 snapshot +# GitHub actions for branch testing the latest Hibernate ORM 6.6 snapshot name: Hibernate Reactive CI on: push: branches: - - wip/2.3 + - wip/2.4 pull_request: - branches: wip/2.3 + branches: [ wip/2.4 ] schedule: # * is a special character in YAML, so you have to quote this string # Run every hour at minute 25 From 24a9a37a4ae33ac1a1d74ab0addeb48e7199790b Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 6 Aug 2024 14:23:24 +0200 Subject: [PATCH 11/12] fix --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 259a2694c2..0bdc9a7bd3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,9 +4,10 @@ name: Hibernate Reactive CI on: push: branches: - - wip/2.4 + - 'wip/2.4' pull_request: - branches: [ wip/2.4 ] + branches: + - 'wip/2.4' schedule: # * is a special character in YAML, so you have to quote this string # Run every hour at minute 25 From 0b1e7f36efa096d021ca4445cf47641c4f09a734 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 6 Aug 2024 14:09:10 +0200 Subject: [PATCH 12/12] [#1960] Upgrade Vert.x SQL client to 4.5.9 --- README.md | 10 +++++----- build.gradle | 2 +- gradle.properties | 6 +++--- tooling/jbang/CockroachDBReactiveTest.java.qute | 4 ++-- tooling/jbang/Db2ReactiveTest.java.qute | 4 ++-- tooling/jbang/Example.java | 6 +++--- tooling/jbang/MariaDBReactiveTest.java.qute | 4 ++-- tooling/jbang/MySQLReactiveTest.java.qute | 4 ++-- tooling/jbang/PostgreSQLReactiveTest.java.qute | 4 ++-- tooling/jbang/ReactiveTest.java | 8 ++++---- 10 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 120472ff0b..62ab172f56 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,11 @@ Hibernate Reactive has been tested with: - MS SQL Server 2022 - Oracle 23 - [Hibernate ORM][] 6.6.0.CR2 -- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.8 -- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.8 -- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.8 -- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.8 -- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.8 +- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.9 +- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.9 +- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.9 +- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.9 +- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.9 - [Quarkus][Quarkus] via the Hibernate Reactive extension [PostgreSQL]: https://www.postgresql.org diff --git a/build.gradle b/build.gradle index 48f46cd765..815345bc46 100644 --- a/build.gradle +++ b/build.gradle @@ -83,7 +83,7 @@ ext { // Example: // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT if ( !project.hasProperty( 'vertxSqlClientVersion' ) ) { - vertxSqlClientVersion = '4.5.8' + vertxSqlClientVersion = '4.5.9' } testcontainersVersion = '1.19.8' diff --git a/gradle.properties b/gradle.properties index a6ee7e1590..b54ce482af 100644 --- a/gradle.properties +++ b/gradle.properties @@ -49,9 +49,9 @@ hibernateOrmGradlePluginVersion = 6.6.0.CR1 skipOrmVersionParsing = true # Override default Vert.x Sql client version -#vertxSqlClientVersion = 4.5.8-SNAPSHOT +#vertxSqlClientVersion = 4.5.9-SNAPSHOT # Override default Vert.x Web client and server versions. For integration tests, both default to vertxSqlClientVersion -#vertxWebVersion = 4.5.8 -#vertxWebtClientVersion = 4.5.8 +#vertxWebVersion = 4.5.9 +#vertxWebtClientVersion = 4.5.9 diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 47b9322ddc..da57a01d18 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.8} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.9} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute index e675f30493..0d9c2d9049 100755 --- a/tooling/jbang/Db2ReactiveTest.java.qute +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.8} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} +//DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.9} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/Example.java b/tooling/jbang/Example.java index 9d0f05b88d..1d87dcd5d6 100644 --- a/tooling/jbang/Example.java +++ b/tooling/jbang/Example.java @@ -6,9 +6,9 @@ */ //DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.8} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.8} -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.8} +//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.9} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.9} +//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.9} //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index bdda0c8432..78e9026de8 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.8} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.9} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index e98df51ca5..0f7aa9b6be 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.8} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.9} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index 08d9fe62a3..07df205831 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.8} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.8} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.9} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.9} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 2d30f4ff94..25a6c1f1f9 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -5,11 +5,11 @@ */ ///usr/bin/env jbang "$0" "$@" ; exit $? -//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.8} +//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.9} //DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.8} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.8} -//DEPS io.vertx:vertx-unit:${vertx.version:4.5.8} +//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.9} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.9} +//DEPS io.vertx:vertx-unit:${vertx.version:4.5.9} //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.4.0-SNAPSHOT} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2