From 98f927e642f4a67cc670454127e0543bb4d33b8f Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 5 Apr 2024 10:24:53 +0200 Subject: [PATCH 1/8] [#1872] Upgrade Vert.x SQL client to 4.5.7 --- 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 c6fb4f2d7..00f2a42fa 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,11 @@ Hibernate Reactive has been tested with: - MS SQL Server 2019 - Oracle 21.3 - [Hibernate ORM][] 6.4.4.Final -- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.3 -- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.3 -- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.3 -- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.3 -- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.3 +- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.7 +- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.7 +- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.7 +- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.7 +- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.7 - [Quarkus][Quarkus] via the Hibernate Reactive extension [PostgreSQL]: https://www.postgresql.org diff --git a/build.gradle b/build.gradle index eabb5ea09..2fe0d6f7b 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.3' + vertxSqlClientVersion = '4.5.7' } testcontainersVersion = '1.19.4' diff --git a/gradle.properties b/gradle.properties index 662435ea4..ff78f1327 100644 --- a/gradle.properties +++ b/gradle.properties @@ -47,9 +47,9 @@ org.gradle.java.installations.auto-download=false #skipOrmVersionParsing = true # Override default Vert.x Sql client version -#vertxSqlClientVersion = 4.5.3-SNAPSHOT +#vertxSqlClientVersion = 4.5.7-SNAPSHOT # Override default Vert.x Web client and server versions. For integration tests, both default to vertxSqlClientVersion -#vertxWebVersion = 4.5.3 -#vertxWebtClientVersion = 4.5.3 +#vertxWebVersion = 4.5.7 +#vertxWebtClientVersion = 4.5.7 diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index bceeaea92..5c32042ed 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.3} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.3} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.7} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.7} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.2.0.Final} //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 bb72fad8f..8a9cd3179 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.3} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.3} +//DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.7} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.7} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.2.0.Final} //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 458657d68..b29c1098a 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.3} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.3} -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.3} +//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.7} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.7} +//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.7} //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.2.0.Final} //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 96d44416e..6757c0623 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.3} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.3} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.7} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.7} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.2.0.Final} //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 ef923b147..19b41a4e3 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.3} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.3} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.7} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.7} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.2.0.Final} //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 7d5eb55a5..9b71ebe4f 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.3} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.3} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.7} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.7} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:2.2.0.Final} //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 822a3da06..8a150ef5c 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.3} +//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.7} //DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.3} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.3} -//DEPS io.vertx:vertx-unit:${vertx.version:4.5.3} +//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.7} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.7} +//DEPS io.vertx:vertx-unit:${vertx.version:4.5.7} //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:2.2.0.Final} //DEPS org.assertj:assertj-core:3.24.2 //DEPS junit:junit:4.13.2 From c7e952c4bdb93719e0ccb6b66bbc14ee620b3d11 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 14 Feb 2024 11:48:11 +0100 Subject: [PATCH 2/8] [#1879] Upgrade JUnit 5 to 5.10.2 --- hibernate-reactive-core/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index 270e26ba9..0f38a856f 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -41,8 +41,8 @@ dependencies { testImplementation 'com.ongres.scram:client:2.1' // JUnit Jupiter - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.3' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2' // JDBC driver to test with ORM and PostgreSQL testRuntimeOnly "org.postgresql:postgresql:42.6.0" From 14906b7390547d316a58f5cf17635ec09b2f6bb9 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 27 Mar 2024 10:24:02 +0100 Subject: [PATCH 3/8] [#1857] Update utilities classes --- .../reactive/util/impl/CompletionStages.java | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/impl/CompletionStages.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/impl/CompletionStages.java index 215602892..ca7df6d6e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/impl/CompletionStages.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/util/impl/CompletionStages.java @@ -82,6 +82,10 @@ public static CompletionStage nullFuture() { return (CompletionStage) VOID; } + public static CompletionStage nullFuture(Void v) { + return nullFuture(); + } + public static CompletionStage completedFuture(T value) { return CompletableFuture.completedFuture( value ); } @@ -110,13 +114,6 @@ public static Ret returnOrRethrow(Throwable x, Ret re return result; } - /** - * For CompletionStage#handle when we don't care about errors - */ - public static U ignoreErrors(Void unused, Throwable throwable) { - return null; - } - public static void logSqlException(Throwable t, Supplier message, String sql) { if ( t != null ) { LOG.failedToExecuteStatement( sql, message.get(), t ); @@ -225,10 +222,6 @@ public static CompletionStage loop(Iterator iterator, IntBiPredicat return voidFuture(); } - public static U nullFuture(Void unused) { - return null; - } - public static CompletionStageHandler handle(R result, T throwable) { return new CompletionStageHandler<>( result, throwable ); } From 5fc8cb4af4244e683c860ad9b449a48fa33afa37 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 27 Mar 2024 10:21:04 +0100 Subject: [PATCH 4/8] [#1857] Upgrade Hibernate ORM to 6.5.0.CR2 --- gradle.properties | 4 +- .../adaptor/impl/ResultSetAdaptor.java | 334 ++++++++----- .../ReactiveOracleSqlAstTranslator.java | 42 -- .../ReactiveEntityIdentityInsertAction.java | 54 +- .../ReactiveEntityRegularInsertAction.java | 45 +- .../impl/ReactiveEntityUpdateAction.java | 42 +- .../internal/ReactiveMutationExecutor.java | 127 ++++- .../ReactiveMutationExecutorPostInsert.java | 141 ------ ...MutationExecutorPostInsertSingleTable.java | 63 --- ...ctiveMutationExecutorSingleNonBatched.java | 44 +- .../ReactiveMutationExecutorStandard.java | 128 ++++- .../AbstractReactiveSaveEventListener.java | 73 ++- ...eneratedValuesMutationDelegateAdaptor.java | 78 +++ ...activeGeneratedValuesMutationDelegate.java | 23 + ...tiveInsertGeneratedIdentifierDelegate.java | 116 +++++ .../ReactiveGeneratedValuesHelper.java | 229 +++++++++ .../ReactiveAbstractReturningDelegate.java | 72 +-- .../ReactiveAbstractSelectingDelegate.java | 79 --- .../ReactiveBasicSelectingDelegate.java | 57 --- ...tiveInsertGeneratedIdentifierDelegate.java | 17 +- .../ReactiveInsertReturningDelegate.java | 125 ++++- .../ReactivePluralAttributeMapping.java | 2 + .../impl/ReactiveAbstractEntityPersister.java | 17 + .../ReactiveAbstractPersisterDelegate.java | 15 +- .../impl/ReactiveCoordinatorFactory.java | 6 +- .../entity/impl/ReactiveEntityPersister.java | 18 +- .../ReactiveGeneratedValuesProcessor.java | 72 +-- ...ReactiveJoinedSubclassEntityPersister.java | 56 +-- ...eMergeCoordinatorStandardScopeFactory.java | 2 +- .../ReactiveSingleTableEntityPersister.java | 82 ++- .../ReactiveUnionSubclassEntityPersister.java | 44 +- ...UpdateCoordinatorStandardScopeFactory.java | 2 +- .../mutation/ReactiveDeleteCoordinator.java | 15 +- .../mutation/ReactiveInsertCoordinator.java | 192 +------ .../ReactiveInsertCoordinatorStandard.java | 467 ++++++++++++++++++ .../ReactiveScopedUpdateCoordinator.java | 3 +- .../ReactiveUpdateCoordinatorNoOp.java | 10 +- .../ReactiveUpdateCoordinatorStandard.java | 33 +- .../reactive/pool/BatchingConnection.java | 50 +- .../reactive/pool/ReactiveConnection.java | 3 +- .../pool/impl/SqlClientConnection.java | 38 ++ .../spi/ReactiveAbstractSelectionQuery.java | 2 +- .../sqm/internal/ReactiveQuerySqmImpl.java | 7 +- .../ReactiveAbstractCteMutationHandler.java | 37 +- .../cte/ReactiveCteDeleteHandler.java | 7 + .../cte/ReactiveCteUpdateHandler.java | 6 + .../cte/ReactiveInsertExecutionDelegate.java | 10 +- ...tiveRestrictedDeleteExecutionDelegate.java | 56 +-- .../ReactiveTableBasedInsertHandler.java | 9 +- .../ReactiveTableBasedUpdateHandler.java | 7 - .../ReactiveUpdateExecutionDelegate.java | 7 - .../session/impl/ReactiveSessionImpl.java | 1 - .../impl/ReactiveStatelessSessionImpl.java | 5 +- ...activeStandardMutationExecutorService.java | 30 +- .../StandardReactiveSelectExecutor.java | 7 +- .../sql/exec/spi/ReactiveValuesResultSet.java | 143 +++++- .../graph/ReactiveDomainResultsAssembler.java | 16 - .../ReactiveEagerCollectionFetch.java | 3 +- .../ReactiveAbstractEntityInitializer.java | 64 +-- .../internal/ReactiveEntityAssembler.java | 31 +- ...ReactiveEntityDelayedFetchInitializer.java | 138 +++--- .../ReactiveEntityFetchJoinedImpl.java | 41 +- .../ReactiveEntityFetchSelectImpl.java | 31 +- .../ReactiveEntityJoinedFetchInitializer.java | 87 ++-- .../ReactiveEntityResultInitializer.java | 17 +- ...titySelectFetchByUniqueKeyInitializer.java | 95 ++-- .../ReactiveEntitySelectFetchInitializer.java | 84 +++- ...veEntitySelectFetchInitializerBuilder.java | 4 +- .../ReactiveEntityDelayedFetchImpl.java | 21 +- ...eactiveEntityResultJoinedSubclassImpl.java | 52 -- .../internal/ReactiveResultsHelper.java | 40 +- .../internal/ReactiveStandardRowReader.java | 18 - ...ReactiveStandardValuesMappingProducer.java | 5 - .../internal/ReactiveValuesResultSetImpl.java | 9 - .../domain/ReactiveCircularFetchImpl.java | 4 +- .../reactive/BatchingConnectionTest.java | 40 +- .../GeneratedPropertySingleTableTest.java | 8 +- .../reactive/NonNullableManyToOneTest.java | 11 + .../reactive/SecondaryTableTest.java | 2 +- .../reactive/SingleTableInheritanceTest.java | 2 +- .../schema/SchemaValidationTestBase.java | 2 +- 81 files changed, 2584 insertions(+), 1595 deletions(-) delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorPostInsert.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorPostInsertSingleTable.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/GeneratedValuesMutationDelegateAdaptor.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveGeneratedValuesMutationDelegate.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveInsertGeneratedIdentifierDelegate.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractSelectingDelegate.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveBasicSelectingDelegate.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinatorStandard.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultJoinedSubclassImpl.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveValuesResultSetImpl.java diff --git a/gradle.properties b/gradle.properties index ff78f1327..e030dbc67 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,12 +35,12 @@ org.gradle.java.installations.auto-download=false #enableMavenLocalRepo = true # Override default Hibernate ORM version -#hibernateOrmVersion = 6.4.0.Final +hibernateOrmVersion = 6.5.0.CR2 # 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.4.0.Final +#hibernateOrmGradlePluginVersion = 6.5.0.CR2 # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) # this is required when using intervals or weird versions or the build will fail diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java index c7f9d6e17..b481fdf01 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/adaptor/impl/ResultSetAdaptor.java @@ -13,6 +13,7 @@ import java.sql.Blob; import java.sql.Clob; import java.sql.Date; +import java.sql.JDBCType; import java.sql.NClob; import java.sql.Ref; import java.sql.ResultSet; @@ -28,6 +29,8 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Calendar; +import java.util.Collection; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; @@ -38,10 +41,11 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import io.vertx.core.buffer.Buffer; +import io.vertx.sqlclient.PropertyKind; import io.vertx.sqlclient.Row; -import io.vertx.sqlclient.RowIterator; import io.vertx.sqlclient.RowSet; import io.vertx.sqlclient.desc.ColumnDescriptor; +import io.vertx.sqlclient.impl.RowBase; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; @@ -52,15 +56,75 @@ */ public class ResultSetAdaptor implements ResultSet { - private final RowIterator iterator; - private final RowSet rows; + private final Iterator iterator; + + private final List columnDescriptors; + private final List columnNames; private Row row; private boolean wasNull; public ResultSetAdaptor(RowSet rows) { requireNonNull( rows ); this.iterator = rows.iterator(); - this.rows = rows; + this.columnNames = rows.columnsNames() == null ? emptyList() : rows.columnsNames(); + this.columnDescriptors = rows.columnDescriptors(); + } + + public ResultSetAdaptor(RowSet rows, PropertyKind propertyKind, String idColumnName, Class idClass) { + this( rows, rows.property( propertyKind ), idColumnName, idClass ); + } + + public ResultSetAdaptor(RowSet rows, Collection ids, String idColumnName, Class idClass) { + this( rows, new RowFromId( ids, idColumnName ), idColumnName, idClass ); + } + + private ResultSetAdaptor(RowSet rows, Row row, String idColumnName, Class idClass) { + requireNonNull( rows ); + requireNonNull( idColumnName ); + this.iterator = List.of( row ).iterator(); + this.columnNames = List.of( idColumnName ); + ColumnDescriptor columnDescriptor = new ColumnDescriptor() { + @Override + public String name() { + return idColumnName; + } + + @Override + public boolean isArray() { + return idClass.isArray(); + } + + @Override + public String typeName() { + return idClass.getName(); + } + + @Override + public JDBCType jdbcType() { + return null; + } + }; + this.columnDescriptors = List.of( columnDescriptor ); + } + + private static class RowFromId extends RowBase { + + private final List columns; + + public RowFromId(Collection ids, String columnName) { + super( ids ); + this.columns = List.of( requireNonNull( columnName ) ); + } + + @Override + public String getColumnName(int pos) { + return pos > 0 ? null : columns.get( pos ); + } + + @Override + public int getColumnIndex(String column) { + return columns.indexOf( column ); + } } @Override @@ -221,7 +285,7 @@ private T caseInsensitiveGet(String columnLabel, Function produce * @return A list of column names or an empty list. */ private List getColumnsNames() { - return rows.columnsNames() == null ? emptyList() : rows.columnsNames(); + return this.columnNames; } @Override @@ -379,129 +443,6 @@ public String getCursorName() { return null; } - @Override - public ResultSetMetaData getMetaData() { - return new ResultSetMetaData() { - @Override - public int getColumnCount() { - return getColumnsNames() == null ? 0 : getColumnsNames().size(); - } - - @Override - public int getColumnType(int column) { - ColumnDescriptor descriptor = rows.columnDescriptors().get( column - 1 ); - return descriptor.isArray() ? Types.ARRAY : descriptor.jdbcType().getVendorTypeNumber(); - } - - @Override - public String getColumnLabel(int column) { - return getColumnsNames().get( column - 1 ); - } - - @Override - public String getColumnName(int column) { - return getColumnsNames().get( column - 1 ); - } - - @Override - public boolean isAutoIncrement(int column) { - return false; - } - - @Override - public boolean isCaseSensitive(int column) { - return false; - } - - @Override - public boolean isSearchable(int column) { - return false; - } - - @Override - public boolean isCurrency(int column) { - return false; - } - - @Override - public int isNullable(int column) { - return columnNullableUnknown; - } - - @Override - public boolean isSigned(int column) { - return false; - } - - @Override - public int getColumnDisplaySize(int column) { - return 0; - } - - @Override - public String getSchemaName(int column) { - return null; - } - - @Override - public int getPrecision(int column) { - return 0; - } - - @Override - public int getScale(int column) { - return 0; - } - - @Override - public String getTableName(int column) { - return null; - } - - @Override - public String getCatalogName(int column) { - return null; - } - - @Override - public String getColumnTypeName(int column) { - // This information is in rows.columnDescriptors().get( column-1 ).dataType.name - // but does not appear to be accessible. - return null; - } - - @Override - public boolean isReadOnly(int column) { - return false; - } - - @Override - public boolean isWritable(int column) { - return false; - } - - @Override - public boolean isDefinitelyWritable(int column) { - return false; - } - - @Override - public String getColumnClassName(int column) { - return null; - } - - @Override - public T unwrap(Class iface) { - return null; - } - - @Override - public boolean isWrapperFor(Class iface) { - return false; - } - }; - } - @Override public Object getObject(int columnIndex) { Object object = row.getValue( columnIndex - 1 ); @@ -680,7 +621,7 @@ public void moveToCurrentRow() { @Override public Statement getStatement() { - throw new UnsupportedOperationException(); + return new PreparedStatementAdaptor(); } @Override @@ -1309,4 +1250,139 @@ public void updateNClob(int columnIndex, Reader reader) { public void updateNClob(String columnLabel, Reader reader) { throw new UnsupportedOperationException(); } + + + @Override + public ResultSetMetaData getMetaData() { + return new MetaData( columnNames, columnDescriptors ); + } + + private static class MetaData implements ResultSetMetaData { + + private final List columns; + private final List descriptors; + + public MetaData(List columnNames, List columnDescriptors) { + columns = columnNames; + descriptors = columnDescriptors; + } + + @Override + public int getColumnCount() { + return columns.size(); + } + + @Override + public int getColumnType(int column) { + ColumnDescriptor descriptor = descriptors.get( column - 1 ); + return descriptor.isArray() ? Types.ARRAY : descriptor.jdbcType().getVendorTypeNumber(); + } + + @Override + public String getColumnLabel(int column) { + return columns.get( column - 1 ); + } + + @Override + public String getColumnName(int column) { + return columns.get( column - 1 ); + } + + @Override + public boolean isAutoIncrement(int column) { + return false; + } + + @Override + public boolean isCaseSensitive(int column) { + return false; + } + + @Override + public boolean isSearchable(int column) { + return false; + } + + @Override + public boolean isCurrency(int column) { + return false; + } + + @Override + public int isNullable(int column) { + return columnNullableUnknown; + } + + @Override + public boolean isSigned(int column) { + return false; + } + + @Override + public int getColumnDisplaySize(int column) { + return 0; + } + + @Override + public String getSchemaName(int column) { + return null; + } + + @Override + public int getPrecision(int column) { + return 0; + } + + @Override + public int getScale(int column) { + return 0; + } + + @Override + public String getTableName(int column) { + return null; + } + + @Override + public String getCatalogName(int column) { + return null; + } + + @Override + public String getColumnTypeName(int column) { + // This information is in rows.columnDescriptors().get( column-1 ).dataType.name + // but does not appear to be accessible. + return null; + } + + @Override + public boolean isReadOnly(int column) { + return false; + } + + @Override + public boolean isWritable(int column) { + return false; + } + + @Override + public boolean isDefinitelyWritable(int column) { + return false; + } + + @Override + public String getColumnClassName(int column) { + return null; + } + + @Override + public T unwrap(Class iface) { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) { + return false; + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java index 9418a07dd..0773ab033 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/dialect/ReactiveOracleSqlAstTranslator.java @@ -5,14 +5,10 @@ */ package org.hibernate.reactive.dialect; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.DialectDelegateWrapper; -import org.hibernate.dialect.DmlTargetColumnQualifierSupport; import org.hibernate.dialect.OracleSqlAstTranslator; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.reactive.sql.model.ReactiveDeleteOrUpsertOperation; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.model.MutationOperation; @@ -44,42 +40,4 @@ public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableU optionalTableUpdate ); } - - // FIXME: Copy and paste from ORM - private void renderUpsertStatement(OptionalTableUpdate optionalTableUpdate) { - // template: - // - // merge into [table] as t - // using values([bindings]) as s ([column-names]) - // on t.[key] = s.[key] - // when not matched - // then insert ... - // when matched - // then update ... - - renderMergeInto( optionalTableUpdate ); - appendSql( " " ); - renderMergeUsing( optionalTableUpdate ); - appendSql( " " ); - renderMergeOn( optionalTableUpdate ); - appendSql( " " ); - renderMergeInsert( optionalTableUpdate ); - appendSql( " " ); - renderMergeUpdate( optionalTableUpdate ); - } - - @Override - protected boolean rendersTableReferenceAlias(Clause clause) { - // todo (6.0) : For now we just skip the alias rendering in the delete and update clauses - // We need some dialect support if we want to support joins in delete and update statements - switch ( clause ) { - case DELETE: - case UPDATE: { - final Dialect realDialect = DialectDelegateWrapper.extractRealDialect( getDialect() ); - return realDialect.getDmlTargetColumnQualifierSupport() == DmlTargetColumnQualifierSupport.TABLE_ALIAS; - } - default: - return true; - } - } } 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 7ad13ddce..69168c3b1 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 @@ -14,6 +14,8 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.internal.util.NullnessUtil; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.stat.spi.StatisticsImplementor; @@ -63,29 +65,29 @@ public CompletionStage reactiveExecute() throws HibernateException { final ReactiveEntityPersister reactivePersister = (ReactiveEntityPersister) persister; return stage .thenCompose( v -> reactivePersister.insertReactive( getState(), instance, session ) ) - .thenCompose( generatedId -> { + .thenCompose( generatedValues -> { + Object generatedId = extractGeneratedId( generatedValues ); setGeneratedId( generatedId ); - return processInsertGeneratedProperties( reactivePersister, generatedId, instance, session ) - .thenApply( v -> generatedId ); - } ) - .thenAccept( generatedId -> { - //need to do that here rather than in the save event listener to let - //the post insert events to have a id-filled entity when IDENTITY is used (EJB3) - persister.setIdentifier( instance, generatedId, session ); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - persistenceContext.registerInsertedKey( getPersister(), generatedId ); - final EntityKey entityKey = session.generateEntityKey( generatedId, persister ); - setEntityKey( entityKey ); - persistenceContext.checkUniqueness( entityKey, getInstance() ); - - postInsert(); - - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() && !isVeto() ) { - statistics.insertEntity( getPersister().getEntityName() ); - } - - markExecuted(); + return processInsertGeneratedProperties( reactivePersister, generatedId, instance, generatedValues, session ) + .thenAccept( v -> { + //need to do that here rather than in the save event listener to let + //the post insert events to have an id-filled entity when IDENTITY is used (EJB3) + persister.setIdentifier( instance, generatedId, session ); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + persistenceContext.registerInsertedKey( getPersister(), generatedId ); + final EntityKey entityKey = session.generateEntityKey( generatedId, persister ); + setEntityKey( entityKey ); + persistenceContext.checkUniqueness( entityKey, getInstance() ); + + postInsert(); + + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() && !isVeto() ) { + statistics.insertEntity( getPersister().getEntityName() ); + } + + markExecuted(); + } ); } ); } else { @@ -95,13 +97,19 @@ public CompletionStage reactiveExecute() throws HibernateException { } } + private Object extractGeneratedId(GeneratedValues generatedValues) { + return NullnessUtil.castNonNull( generatedValues ) + .getGeneratedValue( getPersister().getIdentifierMapping() ); + } + private CompletionStage processInsertGeneratedProperties( ReactiveEntityPersister persister, Object generatedId, Object instance, + GeneratedValues generatedValues, SharedSessionContractImplementor session) { return persister.hasInsertGeneratedProperties() - ? persister.reactiveProcessInsertGenerated( generatedId, instance, getState(), session ) + ? persister.reactiveProcessInsertGenerated( generatedId, instance, getState(), generatedValues, session ) : voidFuture(); } 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 b6339a6b8..f3598a09f 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 @@ -16,6 +16,7 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.stat.spi.StatisticsImplementor; @@ -51,50 +52,47 @@ public void execute() throws HibernateException { @Override public CompletionStage reactiveExecute() throws HibernateException { final CompletionStage stage = reactiveNullifyTransientReferencesIfNotAlready(); - final EntityPersister persister = getPersister(); final SharedSessionContractImplementor session = getSession(); final Object instance = getInstance(); final Object id = getId(); - - // FIXME: It needs to become async final boolean veto = preInsert(); - // Don't need to lock the cache here, since if someone // else inserted the same pk first, the insert would fail if ( !veto ) { final ReactiveEntityPersister reactivePersister = (ReactiveEntityPersister) persister; final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); return stage - .thenCompose( v -> reactivePersister.insertReactive( id, getState(), instance, session ) ) - .thenApply( res -> { + .thenCompose( v -> reactivePersister.insertReactive( id, getState(), instance, session, false ) ) + .thenCompose( generatedValues -> { final EntityEntry entry = persistenceContext.getEntry( instance ); if ( entry == null ) { throw new AssertionFailure( "possible non-threadsafe access to session" ); } entry.postInsert( getState() ); - return entry; - } ) - .thenCompose( entry -> processInsertGeneratedProperties( reactivePersister, session, instance, id, entry ) ) - .thenAccept( vv -> { - persistenceContext.registerInsertedKey( persister, getId() ); - addCollectionsByKeyToPersistenceContext( persistenceContext, getState() ); - putCacheIfNecessary(); - handleNaturalIdPostSaveNotifications( id ); - postInsert(); - - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.insertEntity( getPersister().getEntityName() ); - } - - markExecuted(); + return processInsertGeneratedProperties( reactivePersister, session, instance, id, generatedValues, entry ) + .thenAccept( vv -> { + persistenceContext.registerInsertedKey( persister, getId() ); + addCollectionsByKeyToPersistenceContext( persistenceContext, getState() ); + putCacheIfNecessary(); + handleNaturalIdPostSaveNotifications( id ); + postInsert(); + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.insertEntity( getPersister().getEntityName() ); + } + markExecuted(); + } ); } ); } else { putCacheIfNecessary(); handleNaturalIdPostSaveNotifications( id ); postInsert(); + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.insertEntity( getPersister().getEntityName() ); + } markExecuted(); return stage; } @@ -105,13 +103,14 @@ private CompletionStage processInsertGeneratedProperties( SharedSessionContractImplementor session, Object instance, Object id, + GeneratedValues generatedValues, EntityEntry entry) { if ( persister.hasInsertGeneratedProperties() ) { if ( persister.isVersionPropertyGenerated() ) { throw new UnsupportedOperationException( "generated version attribute not supported in Hibernate Reactive" ); // setVersion( Versioning.getVersion( getState(), persister ) ); } - return persister.reactiveProcessInsertGenerated( id, instance, getState(), session ) + return persister.reactiveProcessInsertGenerated( id, instance, getState(), generatedValues, session ) .thenAccept( v -> entry.postUpdate( instance, getState(), getVersion() ) ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityUpdateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityUpdateAction.java index 36550848a..4a6b5e149 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityUpdateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityUpdateAction.java @@ -14,13 +14,13 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.EventSource; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.engine.ReactiveExecutable; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.type.TypeHelper; -import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -85,28 +85,28 @@ public CompletionStage reactiveExecute() throws HibernateException { getRowId(), session ) - .thenApply( res -> { + .thenCompose( generatedValues -> { final EntityEntry entry = session.getPersistenceContextInternal().getEntry( instance ); if ( entry == null ) { throw new AssertionFailure( "possible non-threadsafe access to session" ); } - return entry; - } ) - .thenCompose( this::handleGeneratedProperties ) - .thenAccept( entry -> { - handleDeleted( entry ); - updateCacheItem( persister, ck, entry ); - handleNaturalIdResolutions( persister, session, id ); - postUpdate(); + return reactiveHandleGeneratedProperties( entry, generatedValues ) + .thenAccept( v -> { + handleDeleted( entry ); + updateCacheItem( persister, ck, entry ); + handleNaturalIdResolutions( persister, session, id ); + postUpdate(); + + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.updateEntity( getPersister().getEntityName() ); + } + } ); - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.updateEntity( getPersister().getEntityName() ); - } } ); } - private CompletionStage handleGeneratedProperties(EntityEntry entry) { + private CompletionStage reactiveHandleGeneratedProperties(EntityEntry entry, GeneratedValues generatedValues) { final EntityPersister persister = getPersister(); if ( entry.getStatus() == Status.MANAGED || persister.isVersionPropertyGenerated() ) { final SharedSessionContractImplementor session = getSession(); @@ -123,21 +123,19 @@ private CompletionStage handleGeneratedProperties(EntityEntry entry session ); final ReactiveEntityPersister reactivePersister = (ReactiveEntityPersister) persister; - return processGeneratedProperties( id, reactivePersister, session, instance ) + return processGeneratedProperties( id, reactivePersister, session, generatedValues, instance ) // have the entity entry doAfterTransactionCompletion post-update processing, passing it the // update state and the new version (if one). - .thenAccept( v -> entry.postUpdate( instance, getState(), getNextVersion() ) ) - .thenApply( v -> entry ); - } - else { - return completedFuture( entry ); + .thenAccept( v -> entry.postUpdate( instance, getState(), getNextVersion() ) ); } + return voidFuture(); } private CompletionStage processGeneratedProperties( Object id, ReactiveEntityPersister persister, SharedSessionContractImplementor session, + GeneratedValues generatedValues, Object instance) { if ( persister.hasUpdateGeneratedProperties() ) { // this entity defines property generation, so process those generated values... @@ -145,7 +143,7 @@ private CompletionStage processGeneratedProperties( throw new UnsupportedOperationException( "generated version attribute not supported in Hibernate Reactive" ); // setNextVersion( Versioning.getVersion( getState(), persister ) ); } - return persister.reactiveProcessUpdateGenerated( id, instance, getState(), session ); + return persister.reactiveProcessUpdateGenerated( id, instance, getState(), generatedValues, session ); } else { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveMutationExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveMutationExecutor.java index 6b839dc97..fe9873791 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveMutationExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/env/internal/ReactiveMutationExecutor.java @@ -8,23 +8,32 @@ import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; +import org.hibernate.dialect.DB2Dialect; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.DialectDelegateWrapper; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.MutationExecutor; import org.hibernate.engine.jdbc.mutation.OperationResultChecker; +import org.hibernate.engine.jdbc.mutation.ParameterUsage; import org.hibernate.engine.jdbc.mutation.TableInclusionChecker; import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.session.ReactiveConnectionSupplier; -import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.model.TableMapping; import org.hibernate.sql.model.ValuesAnalysis; import static org.hibernate.reactive.engine.jdbc.ResultsCheckerUtil.checkResults; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; +import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; @@ -33,36 +42,49 @@ */ public interface ReactiveMutationExecutor extends MutationExecutor { - Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - @Override - default Object execute( + default GeneratedValues execute( Object modelReference, ValuesAnalysis valuesAnalysis, TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, SharedSessionContractImplementor session) { - throw LOG.nonReactiveMethodCall( "executeReactive" ); + throw make( Log.class, MethodHandles.lookup() ).nonReactiveMethodCall( "executeReactive" ); } - default CompletionStage executeReactive( + default CompletionStage executeReactive( Object modelReference, ValuesAnalysis valuesAnalysis, TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, SharedSessionContractImplementor session) { - return performReactiveNonBatchedOperations( valuesAnalysis, inclusionChecker, resultChecker, session ) - .thenCompose( ignore -> performReactiveSelfExecutingOperations( valuesAnalysis, inclusionChecker, session ) ) - .thenCompose( ignore -> performReactiveBatchedOperations( valuesAnalysis, inclusionChecker, resultChecker, session ) ) - .thenApply( CompletionStages::nullFuture ); + return executeReactive( modelReference, valuesAnalysis, inclusionChecker, resultChecker, session, true, null ); } - default CompletionStage performReactiveNonBatchedOperations( + default CompletionStage executeReactive( + Object modelReference, ValuesAnalysis valuesAnalysis, TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, - SharedSessionContractImplementor session) { - return voidFuture(); + SharedSessionContractImplementor session, + boolean isIdentityInsert, + String[] identifierColumnsNames) { + return performReactiveNonBatchedOperations( modelReference, valuesAnalysis, inclusionChecker, resultChecker, session, isIdentityInsert, identifierColumnsNames ) + .thenCompose( generatedValues -> performReactiveSelfExecutingOperations( valuesAnalysis, inclusionChecker, session ) + .thenCompose( v -> performReactiveBatchedOperations( valuesAnalysis, inclusionChecker, resultChecker, session ) ) + .thenApply( v -> generatedValues ) + ); + } + + default CompletionStage performReactiveNonBatchedOperations( + Object modelReference, + ValuesAnalysis valuesAnalysis, + TableInclusionChecker inclusionChecker, + OperationResultChecker resultChecker, + SharedSessionContractImplementor session, + boolean isIdentityInsert, + String[] identifierColumnsNames) { + return nullFuture(); } default CompletionStage performReactiveSelfExecutingOperations( @@ -79,17 +101,71 @@ default CompletionStage performReactiveBatchedOperations( return voidFuture(); } + private static String createInsert(String insertSql, String identifierColumnName, Dialect dialect) { + String sql = insertSql; + final String sqlEnd = " returning " + identifierColumnName; + Dialect realDialect = DialectDelegateWrapper.extractRealDialect( dialect ); + if ( realDialect instanceof MySQLDialect ) { + // For some reason ORM generates a query with an invalid syntax + int index = sql.lastIndexOf( sqlEnd ); + return index > -1 + ? sql.substring( 0, index ) + : sql; + } + if ( realDialect instanceof SQLServerDialect ) { + int index = sql.lastIndexOf( sqlEnd ); + // FIXME: this is a hack for HHH-16365 + if ( index > -1 ) { + sql = sql.substring( 0, index ); + } + if ( sql.endsWith( "default values" ) ) { + index = sql.indexOf( "default values" ); + sql = sql.substring( 0, index ); + sql = sql + "output inserted." + identifierColumnName + " default values"; + } + else { + sql = sql.replace( ") values (", ") output inserted." + identifierColumnName + " values (" ); + } + return sql; + } + if ( realDialect instanceof DB2Dialect ) { + // ORM query: select id from new table ( insert into IntegerTypeEntity values ( )) + // Correct : select id from new table ( insert into LongTypeEntity (id) values (default)) + return sql.replace( " values ( ))", " (" + identifierColumnName + ") values (default))" ); + } + if ( realDialect instanceof OracleDialect ) { + final String valuesStr = " values ( )"; + int index = sql.lastIndexOf( sqlEnd ); + // remove "returning id" since it's added via + if ( index > -1 ) { + sql = sql.substring( 0, index ); + } + + // Oracle is expecting values (default) + if ( sql.endsWith( valuesStr ) ) { + index = sql.lastIndexOf( valuesStr ); + sql = sql.substring( 0, index ); + sql = sql + " values (default)"; + } + + return sql; + } + return sql; + } + /** * Perform a non-batched mutation */ default CompletionStage performReactiveNonBatchedMutation( PreparedStatementDetails statementDetails, + Object id, JdbcValueBindings valueBindings, TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, - SharedSessionContractImplementor session) { + SharedSessionContractImplementor session, + String[] identifierColumnsNames) { if ( statementDetails == null ) { - return voidFuture(); + return nullFuture(); } final TableMapping tableDetails = statementDetails.getMutatingTableDetails(); @@ -100,14 +176,32 @@ default CompletionStage performReactiveNonBatchedMutation( return voidFuture(); } + if ( id != null ) { + assert !tableDetails.isIdentifierTable() : "Unsupported identifier table with generated id"; + ( (EntityTableMapping) tableDetails ).getKeyMapping().breakDownKeyJdbcValues( + id, + (jdbcValue, columnMapping) -> valueBindings.bindValue( + jdbcValue, + tableDetails.getTableName(), + columnMapping.getColumnName(), + ParameterUsage.SET + ), + session + ); + } + // If we get here the statement is needed - make sure it is resolved Object[] params = PreparedStatementAdaptor.bind( statement -> { PreparedStatementDetails details = new PrepareStatementDetailsAdaptor( statementDetails, statement, session.getJdbcServices() ); valueBindings.beforeStatement( details ); } ); + Dialect dialect = session.getJdbcServices().getDialect(); ReactiveConnection reactiveConnection = ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); String sqlString = statementDetails.getSqlString(); + if ( identifierColumnsNames != null ) { + sqlString = createInsert( statementDetails.getSqlString(), identifierColumnsNames[0], dialect ); + } return reactiveConnection .update( sqlString, params ) .thenCompose( affectedRowCount -> { @@ -125,5 +219,4 @@ default CompletionStage performReactiveNonBatchedMutation( valueBindings.afterStatement( tableDetails ); } ); } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorPostInsert.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorPostInsert.java deleted file mode 100644 index 8f0d5844a..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorPostInsert.java +++ /dev/null @@ -1,141 +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.engine.jdbc.mutation.internal; - -import java.util.concurrent.CompletionStage; -import java.util.concurrent.atomic.AtomicReference; - -import org.hibernate.engine.jdbc.mutation.OperationResultChecker; -import org.hibernate.engine.jdbc.mutation.ParameterUsage; -import org.hibernate.engine.jdbc.mutation.TableInclusionChecker; -import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; -import org.hibernate.engine.jdbc.mutation.internal.MutationExecutorPostInsert; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.persister.entity.mutation.EntityTableMapping; -import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; -import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; -import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor; -import org.hibernate.reactive.id.insert.ReactiveInsertGeneratedIdentifierDelegate; -import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.session.ReactiveConnectionSupplier; -import org.hibernate.sql.model.EntityMutationOperationGroup; -import org.hibernate.sql.model.ValuesAnalysis; - -import static org.hibernate.reactive.engine.jdbc.ResultsCheckerUtil.checkResults; -import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; -import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; - -public class ReactiveMutationExecutorPostInsert extends MutationExecutorPostInsert implements ReactiveMutationExecutor { - - public ReactiveMutationExecutorPostInsert( - EntityMutationOperationGroup mutationOperationGroup, - SharedSessionContractImplementor session) { - super( mutationOperationGroup, session ); - } - - @Override - public CompletionStage executeReactive(Object modelReference, ValuesAnalysis valuesAnalysis, - TableInclusionChecker inclusionChecker, - OperationResultChecker resultChecker, - SharedSessionContractImplementor session) { - return ( (ReactiveInsertGeneratedIdentifierDelegate) mutationTarget.getIdentityInsertDelegate() ) - .reactivePerformInsert( - identityInsertStatementDetails, - getJdbcValueBindings(), - modelReference, - session - ) - .thenApply( this::logId ) - .thenCompose( id -> { - if ( secondaryTablesStatementGroup == null ) { - return completedFuture( id ); - } - AtomicReference> res = new AtomicReference<>( completedFuture( id ) ); - secondaryTablesStatementGroup.forEachStatement( (tableName, statementDetails) -> { - res.set( res.get().thenCompose( i -> reactiveExecuteWithId( i, tableName, statementDetails, inclusionChecker, resultChecker, session ) ) ); - } ); - return res.get(); - } ); - } - - private Object logId(Object identifier) { - if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) { - MODEL_MUTATION_LOGGER - .tracef( - "Post-insert generated value : `%s` (%s)", - identifier, - mutationTarget.getNavigableRole().getFullPath() - ); - } - return identifier; - } - - private CompletionStage reactiveExecuteWithId( - Object id, - String tableName, - PreparedStatementDetails statementDetails, - TableInclusionChecker inclusionChecker, - OperationResultChecker resultChecker, - SharedSessionContractImplementor session) { - - if ( statementDetails == null ) { - return completedFuture( id ); - } - - final EntityTableMapping tableDetails = (EntityTableMapping) statementDetails.getMutatingTableDetails(); - assert !tableDetails.isIdentifierTable(); - - if ( inclusionChecker != null && !inclusionChecker.include( tableDetails ) ) { - if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) { - MODEL_MUTATION_LOGGER.tracef( - "Skipping execution of secondary insert : %s", - tableDetails.getTableName() - ); - } - return completedFuture( id ); - } - - - tableDetails.getKeyMapping().breakDownKeyJdbcValues( - id, - (jdbcValue, columnMapping) -> valueBindings - .bindValue( - jdbcValue, - tableName, - columnMapping.getColumnName(), - ParameterUsage.SET - ), - session - ); - - session.getJdbcServices().getSqlStatementLogger().logStatement( statementDetails.getSqlString() ); - - Object[] params = PreparedStatementAdaptor.bind( statement -> { - PreparedStatementDetails details = new PrepareStatementDetailsAdaptor( statementDetails, statement, session.getJdbcServices() ); - //noinspection resource - details.resolveStatement(); - valueBindings.beforeStatement( details ); - } ); - - ReactiveConnection reactiveConnection = ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); - String sqlString = statementDetails.getSqlString(); - return reactiveConnection - .update( sqlString, params ) - .thenApply( affectedRowCount -> { - if ( affectedRowCount == 0 && tableDetails.isOptional() ) { - // the optional table did not have a row - return completedFuture(id); - } - checkResults( session, statementDetails, resultChecker, affectedRowCount, -1); - return id; - } ).whenComplete( (unused, throwable) -> { - if ( statementDetails.getStatement() != null ) { - statementDetails.releaseStatement( session ); - } - valueBindings.afterStatement( tableDetails ); - } ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorPostInsertSingleTable.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorPostInsertSingleTable.java deleted file mode 100644 index c3a72200b..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorPostInsertSingleTable.java +++ /dev/null @@ -1,63 +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.engine.jdbc.mutation.internal; - -import java.util.concurrent.CompletionStage; - -import org.hibernate.engine.jdbc.mutation.OperationResultChecker; -import org.hibernate.engine.jdbc.mutation.TableInclusionChecker; -import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; -import org.hibernate.engine.jdbc.mutation.internal.MutationExecutorPostInsertSingleTable; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.persister.entity.mutation.EntityMutationTarget; -import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor; -import org.hibernate.reactive.id.insert.ReactiveInsertGeneratedIdentifierDelegate; -import org.hibernate.sql.model.EntityMutationOperationGroup; -import org.hibernate.sql.model.PreparableMutationOperation; -import org.hibernate.sql.model.ValuesAnalysis; - -import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.identityPreparation; -import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; - -public class ReactiveMutationExecutorPostInsertSingleTable extends MutationExecutorPostInsertSingleTable - implements ReactiveMutationExecutor { - - private final EntityMutationTarget mutationTarget; - private final PreparedStatementDetails identityInsertStatementDetails; - - public ReactiveMutationExecutorPostInsertSingleTable(EntityMutationOperationGroup mutationOperationGroup, SharedSessionContractImplementor session) { - super( mutationOperationGroup, session ); - this.mutationTarget = mutationOperationGroup.getMutationTarget(); - final PreparableMutationOperation operation = (PreparableMutationOperation) mutationOperationGroup - .getOperation( mutationTarget.getIdentifierTableName() ); - this.identityInsertStatementDetails = identityPreparation( operation, session ); - } - - @Override - public CompletionStage executeReactive( - Object modelReference, - ValuesAnalysis valuesAnalysis, - TableInclusionChecker inclusionChecker, - OperationResultChecker resultChecker, - SharedSessionContractImplementor session) { - return ( (ReactiveInsertGeneratedIdentifierDelegate) mutationTarget.getIdentityInsertDelegate() ) - .reactivePerformInsert( - identityInsertStatementDetails, - getJdbcValueBindings(), - modelReference, - session - ) - .thenApply( this::logId ); - } - - private Object logId(Object identifier) { - if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) { - MODEL_MUTATION_LOGGER - .tracef( "Post-insert generated value : `%s` (%s)", identifier, mutationTarget.getNavigableRole().getFullPath() ); - } - return identifier; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorSingleNonBatched.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorSingleNonBatched.java index 16ce5384f..1a37170fc 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorSingleNonBatched.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorSingleNonBatched.java @@ -7,11 +7,18 @@ import java.util.concurrent.CompletionStage; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.MariaDBDialect; import org.hibernate.engine.jdbc.mutation.OperationResultChecker; import org.hibernate.engine.jdbc.mutation.TableInclusionChecker; +import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; import org.hibernate.engine.jdbc.mutation.internal.MutationExecutorSingleNonBatched; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor; +import org.hibernate.reactive.generator.values.ReactiveGeneratedValuesMutationDelegate; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.model.PreparableMutationOperation; import org.hibernate.sql.model.ValuesAnalysis; @@ -21,24 +28,51 @@ public class ReactiveMutationExecutorSingleNonBatched extends MutationExecutorSingleNonBatched implements ReactiveMutationExecutor { + private final ReactiveGeneratedValuesMutationDelegate generatedValuesDelegate; + public ReactiveMutationExecutorSingleNonBatched( PreparableMutationOperation mutationOperation, + GeneratedValuesMutationDelegate generatedValuesDelegate, SharedSessionContractImplementor session) { - super( mutationOperation, session ); + super( mutationOperation, generatedValuesDelegate, session ); + this.generatedValuesDelegate = (ReactiveGeneratedValuesMutationDelegate) generatedValuesDelegate; } @Override - public CompletionStage performReactiveNonBatchedOperations( + public CompletionStage performReactiveNonBatchedOperations( + Object modelReference, ValuesAnalysis valuesAnalysis, TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, - SharedSessionContractImplementor session) { + SharedSessionContractImplementor session, + boolean isIdentityInsert, + String[] identifierColumnsNames) { + PreparedStatementDetails singleStatementDetails = getStatementGroup().getSingleStatementDetails(); + if ( generatedValuesDelegate != null && !isRegularInsertWithMariaDb( session, isIdentityInsert ) ) { + return generatedValuesDelegate.reactivePerformMutation( + singleStatementDetails, + getJdbcValueBindings(), + modelReference, + session + ); + } return performReactiveNonBatchedMutation( - getStatementGroup().getSingleStatementDetails(), + singleStatementDetails, + null, getJdbcValueBindings(), inclusionChecker, resultChecker, - session ); + session, + identifierColumnsNames + ).thenCompose( CompletionStages::nullFuture ); + } + + private boolean isRegularInsertWithMariaDb(SharedSessionContractImplementor session, boolean isIdentityInsert) { + if ( isIdentityInsert ) { + return false; + } + Dialect dialect = session.getJdbcServices().getDialect(); + return dialect instanceof MariaDBDialect; } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java index 3c274c214..80e24fac7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java @@ -5,28 +5,41 @@ */ package org.hibernate.reactive.engine.jdbc.mutation.internal; +import java.lang.invoke.MethodHandles; import java.sql.SQLException; import java.util.concurrent.CompletionStage; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.OperationResultChecker; +import org.hibernate.engine.jdbc.mutation.ParameterUsage; import org.hibernate.engine.jdbc.mutation.TableInclusionChecker; import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; import org.hibernate.engine.jdbc.mutation.group.PreparedStatementGroup; import org.hibernate.engine.jdbc.mutation.internal.MutationExecutorStandard; import org.hibernate.engine.jdbc.mutation.spi.BatchKeyAccess; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.persister.entity.mutation.EntityMutationTarget; +import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor; +import org.hibernate.reactive.generator.values.ReactiveGeneratedValuesMutationDelegate; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.session.ReactiveConnectionSupplier; +import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.sql.model.EntityMutationOperationGroup; import org.hibernate.sql.model.MutationOperationGroup; +import org.hibernate.sql.model.MutationType; import org.hibernate.sql.model.TableMapping; import org.hibernate.sql.model.ValuesAnalysis; import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.checkResults; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; @@ -35,12 +48,21 @@ */ public class ReactiveMutationExecutorStandard extends MutationExecutorStandard implements ReactiveMutationExecutor { + private static final Log LOG = make( Log.class, MethodHandles.lookup() ); + + private final GeneratedValuesMutationDelegate generatedValuesDelegate; + private final MutationOperationGroup mutationOperationGroup; + public ReactiveMutationExecutorStandard( MutationOperationGroup mutationOperationGroup, BatchKeyAccess batchKeySupplier, int batchSize, SharedSessionContractImplementor session) { super( mutationOperationGroup, batchKeySupplier, batchSize, session ); + this.generatedValuesDelegate = mutationOperationGroup.asEntityMutationOperationGroup() != null + ? mutationOperationGroup.asEntityMutationOperationGroup().getMutationDelegate() + : null; + this.mutationOperationGroup = mutationOperationGroup; } private ReactiveConnection connection(SharedSessionContractImplementor session) { @@ -57,7 +79,8 @@ public CompletionStage performReactiveBatchedOperations( } @Override - protected void performNonBatchedOperations( + protected GeneratedValues performNonBatchedOperations( + Object modelReference, ValuesAnalysis valuesAnalysis, TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, @@ -81,50 +104,104 @@ protected void performBatchedOperations( } @Override - public CompletionStage performReactiveNonBatchedOperations( + public CompletionStage performReactiveNonBatchedOperations( + Object modelReference, ValuesAnalysis valuesAnalysis, TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, - SharedSessionContractImplementor session) { + SharedSessionContractImplementor session, + boolean isIndentityInsert, + String[] identifiersColumnsNames) { if ( getNonBatchedStatementGroup() == null || getNonBatchedStatementGroup().getNumberOfStatements() <= 0 ) { - return voidFuture(); + return nullFuture(); } PreparedStatementGroup nonBatchedStatementGroup = getNonBatchedStatementGroup(); - final OperationsForEach forEach = new OperationsForEach( inclusionChecker, resultChecker, session, getJdbcValueBindings() ); - nonBatchedStatementGroup.forEachStatement( forEach::add ); - return forEach.buildLoop(); + if ( generatedValuesDelegate != null ) { + final EntityMutationOperationGroup entityGroup = mutationOperationGroup.asEntityMutationOperationGroup(); + final EntityMutationTarget entityTarget = entityGroup.getMutationTarget(); + final PreparedStatementDetails details = nonBatchedStatementGroup.getPreparedStatementDetails( + entityTarget.getIdentifierTableName() + ); + return ( (ReactiveGeneratedValuesMutationDelegate) generatedValuesDelegate ) + .reactivePerformMutation( details, getJdbcValueBindings(), modelReference, session ) + .thenCompose( generatedValues -> { + Object id = entityGroup.getMutationType() == MutationType.INSERT && details.getMutatingTableDetails().isIdentifierTable() + ? generatedValues.getGeneratedValue( entityTarget.getTargetPart().getIdentifierMapping() ) + : null; + OperationsForEach forEach = new OperationsForEach( + id, + inclusionChecker, + resultChecker, + session, + getJdbcValueBindings(), + true + ); + nonBatchedStatementGroup.forEachStatement( forEach::add ); + return forEach.buildLoop().thenApply( v -> generatedValues ); + } ); + + } + else { + OperationsForEach forEach = new OperationsForEach( + null, + inclusionChecker, + resultChecker, + session, + getJdbcValueBindings(), + false + ); + nonBatchedStatementGroup.forEachStatement( forEach::add ); + return forEach.buildLoop().thenCompose( CompletionStages::nullFuture ); + } } private class OperationsForEach { + private final Object id; private final TableInclusionChecker inclusionChecker; private final OperationResultChecker resultChecker; private final SharedSessionContractImplementor session; + private final boolean requiresCheck; private final JdbcValueBindings jdbcValueBindings; private CompletionStage loop = voidFuture(); public OperationsForEach( + Object id, TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, SharedSessionContractImplementor session, - JdbcValueBindings jdbcValueBindings) { + JdbcValueBindings jdbcValueBindings, + boolean requiresCheck) { + this.id = id; this.inclusionChecker = inclusionChecker; this.resultChecker = resultChecker; this.session = session; this.jdbcValueBindings = jdbcValueBindings; + this.requiresCheck = requiresCheck; } public void add(String tableName, PreparedStatementDetails statementDetails) { - loop = loop.thenCompose( v -> performReactiveNonBatchedMutation( - statementDetails, - getJdbcValueBindings(), - inclusionChecker, - resultChecker, - session - ) ); + if ( requiresCheck ) { + loop = loop.thenCompose( v -> !statementDetails + .getMutatingTableDetails().isIdentifierTable() + ? performReactiveNonBatchedMutation( statementDetails, id, jdbcValueBindings, inclusionChecker, resultChecker, session, null ) + : voidFuture() + ); + } + else { + loop = loop.thenCompose( v -> performReactiveNonBatchedMutation( + statementDetails, + null, + jdbcValueBindings, + inclusionChecker, + resultChecker, + session, + null + ) ); + } } public CompletionStage buildLoop() { @@ -134,10 +211,12 @@ public CompletionStage buildLoop() { @Override public CompletionStage performReactiveNonBatchedMutation( PreparedStatementDetails statementDetails, + Object id, JdbcValueBindings valueBindings, TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, - SharedSessionContractImplementor session) { + SharedSessionContractImplementor session, + String[] identifierColumnsNames) { if ( statementDetails == null ) { return voidFuture(); } @@ -145,12 +224,25 @@ public CompletionStage performReactiveNonBatchedMutation( final TableMapping tableDetails = statementDetails.getMutatingTableDetails(); if ( inclusionChecker != null && !inclusionChecker.include( tableDetails ) ) { if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) { - MODEL_MUTATION_LOGGER - .tracef( "Skipping execution of secondary insert : %s", tableDetails.getTableName() ); + MODEL_MUTATION_LOGGER.tracef( "Skipping execution of secondary insert : %s", tableDetails.getTableName() ); } return voidFuture(); } + if ( id != null ) { + assert !tableDetails.isIdentifierTable() : "Unsupported identifier table with generated id"; + ( (EntityTableMapping) tableDetails ).getKeyMapping().breakDownKeyJdbcValues( + id, + (jdbcValue, columnMapping) -> valueBindings.bindValue( + jdbcValue, + tableDetails.getTableName(), + columnMapping.getColumnName(), + ParameterUsage.SET + ), + session + ); + } + Object[] params = PreparedStatementAdaptor.bind( statement -> { PreparedStatementDetails details = new PrepareStatementDetailsAdaptor( statementDetails, statement, session.getJdbcServices() ); valueBindings.beforeStatement( details ); 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 44b235d5b..54fd9b23a 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 @@ -25,6 +25,7 @@ import org.hibernate.event.spi.EventSource; import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.Generator; +import org.hibernate.id.Assigned; import org.hibernate.id.IdentifierGenerationException; import org.hibernate.jpa.event.spi.CallbackRegistry; import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; @@ -122,27 +123,45 @@ protected CompletionStage reactiveSaveWithGeneratedId( C context, EventSource source, boolean requiresImmediateIdAccess) { - callbackRegistry.preCreate( entity ); - - processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); final EntityPersister persister = source.getEntityPersister( entityName, entity ); final Generator generator = persister.getGenerator(); - if ( !generator.generatedOnExecution() ) { + final boolean generatedOnExecution = generator.generatedOnExecution( entity, source ); + final Object generatedId; + if ( generatedOnExecution ) { + // the id gets generated by the database + // and is not yet available + generatedId = null; + } + else if ( generator instanceof Assigned ) { + // get it from the entity later, since we need + // the @PrePersist callback to happen first + generatedId = null; + } + else { + // go ahead and generate id, and then set it to + // the entity instance, so it will be available + // to the entity in the @PrePersist callback if ( generator instanceof ReactiveIdentifierGenerator ) { return ( (ReactiveIdentifierGenerator) generator ) .generate( ( ReactiveConnectionSupplier ) source, entity ) .thenApply( id -> castToIdentifierType( id, persister ) ) - .thenCompose( generatedId -> performSaveWithId( entity, context, source, persister, generator, generatedId ) ); + .thenCompose( gid -> performSaveWithId( + entity, + context, + source, + persister, + generator, + gid, + requiresImmediateIdAccess, + false + ) ); } - final Object generatedId = ( (BeforeExecutionGenerator) generator ).generate( source, entity, null, INSERT ); - final Object id = castToIdentifierType( generatedId, persister ); - return performSaveWithId( entity, context, source, persister, generator, id ); - } - else { - return reactivePerformSave( entity, null, persister, true, context, source, requiresImmediateIdAccess ); + generatedId = ( (BeforeExecutionGenerator) generator ).generate( source, entity, null, INSERT ); } + final Object id = castToIdentifierType( generatedId, persister ); + return reactivePerformSave( entity, id, persister, generatedOnExecution, context, source, requiresImmediateIdAccess ); } private CompletionStage performSaveWithId( @@ -151,7 +170,9 @@ private CompletionStage performSaveWithId( EventSource source, EntityPersister persister, Generator generator, - Object generatedId) { + Object generatedId, + boolean requiresImmediateIdAccess, + boolean generatedOnExecution) { if ( generatedId == null ) { throw new IdentifierGenerationException( "null id generated for: " + entity.getClass() ); } @@ -166,7 +187,11 @@ private CompletionStage performSaveWithId( generator.getClass().getName() ); } - return reactivePerformSave( entity, generatedId, persister, false, context, source, true ); + final boolean delayIdentityInserts = + !source.isTransactionInProgress() + && !requiresImmediateIdAccess + && generatedOnExecution; + return reactivePerformSave( entity, generatedId, persister, false, context, source, delayIdentityInserts ); } /** @@ -196,6 +221,22 @@ protected CompletionStage reactivePerformSave( EventSource source, boolean requiresImmediateIdAccess) { + // call this after generation of an id, + // but before we retrieve an assigned id + callbackRegistry.preCreate( entity ); + + processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); + + if ( persister.getGenerator() instanceof Assigned ) { + id = persister.getIdentifier( entity, source ); + if ( id == null ) { + throw new IdentifierGenerationException( + "Identifier of entity '" + persister.getEntityName() + + "' must be manually assigned before calling 'persist()'" + ); + } + } + if ( LOG.isTraceEnabled() ) { LOG.tracev( "Saving {0}", infoString( persister, id, source.getFactory() ) ); } @@ -268,8 +309,10 @@ protected CompletionStage reactivePerformSaveOrReplicate( final Object id = key == null ? null : key.getIdentifier(); - final boolean inTransaction = source.isTransactionInProgress(); - final boolean shouldDelayIdentityInserts = !inTransaction && !requiresImmediateIdAccess; + Generator generator = persister.getGenerator(); + final boolean shouldDelayIdentityInserts = !source.isTransactionInProgress() + && !requiresImmediateIdAccess + && generator.generatedOnExecution( entity, source ); final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); // Put a placeholder in entries, so we don't recurse back and try to save() the diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/GeneratedValuesMutationDelegateAdaptor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/GeneratedValuesMutationDelegateAdaptor.java new file mode 100644 index 000000000..271d7c5bf --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/GeneratedValuesMutationDelegateAdaptor.java @@ -0,0 +1,78 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.generator.values; + +import java.sql.PreparedStatement; +import java.util.concurrent.CompletionStage; + +import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; +import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.EventType; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.jdbc.Expectation; +import org.hibernate.sql.model.ast.builder.TableMutationBuilder; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; + +public class GeneratedValuesMutationDelegateAdaptor implements ReactiveGeneratedValuesMutationDelegate { + private final ReactiveGeneratedValuesMutationDelegate delegate; + + public GeneratedValuesMutationDelegateAdaptor(GeneratedValuesMutationDelegate delegate) { + this.delegate = (ReactiveGeneratedValuesMutationDelegate) delegate; + } + + @Override + public CompletionStage reactivePerformMutation( + PreparedStatementDetails singleStatementDetails, + JdbcValueBindings jdbcValueBindings, + Object modelReference, + SharedSessionContractImplementor session) { + return delegate.reactivePerformMutation( singleStatementDetails, jdbcValueBindings, modelReference, session ); + } + + @Override + public TableMutationBuilder createTableMutationBuilder( + Expectation expectation, + SessionFactoryImplementor sessionFactory) { + return delegate.createTableMutationBuilder( expectation, sessionFactory ); + } + + @Override + public PreparedStatement prepareStatement(String sql, SharedSessionContractImplementor session) { + return delegate.prepareStatement( sql, session ); + } + + @Override + public GeneratedValues performMutation( + PreparedStatementDetails statementDetails, + JdbcValueBindings valueBindings, + Object entity, + SharedSessionContractImplementor session) { + return delegate.performMutation( statementDetails, valueBindings, entity, session ); + } + + @Override + public EventType getTiming() { + return delegate.getTiming(); + } + + @Override + public boolean supportsArbitraryValues() { + return delegate.supportsArbitraryValues(); + } + + @Override + public boolean supportsRowId() { + return delegate.supportsRowId(); + } + + @Override + public JdbcValuesMappingProducer getGeneratedValuesMappingProducer() { + return delegate.getGeneratedValuesMappingProducer(); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveGeneratedValuesMutationDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveGeneratedValuesMutationDelegate.java new file mode 100644 index 000000000..a5c7f9e7a --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveGeneratedValuesMutationDelegate.java @@ -0,0 +1,23 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.generator.values; + +import java.util.concurrent.CompletionStage; + +import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; +import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; + +public interface ReactiveGeneratedValuesMutationDelegate extends GeneratedValuesMutationDelegate { + + CompletionStage reactivePerformMutation( + PreparedStatementDetails singleStatementDetails, + JdbcValueBindings jdbcValueBindings, + Object modelReference, + SharedSessionContractImplementor session); +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveInsertGeneratedIdentifierDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveInsertGeneratedIdentifierDelegate.java new file mode 100644 index 000000000..36d85f353 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/ReactiveInsertGeneratedIdentifierDelegate.java @@ -0,0 +1,116 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.generator.values; + +import java.sql.PreparedStatement; +import java.util.concurrent.CompletionStage; + +import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; +import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.EventType; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.id.insert.Binder; +import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; +import org.hibernate.jdbc.Expectation; +import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.sql.model.ast.builder.TableInsertBuilder; +import org.hibernate.sql.model.ast.builder.TableMutationBuilder; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; + +public class ReactiveInsertGeneratedIdentifierDelegate implements InsertGeneratedIdentifierDelegate, ReactiveGeneratedValuesMutationDelegate { + private final InsertGeneratedIdentifierDelegate delegate; + + public ReactiveInsertGeneratedIdentifierDelegate(InsertGeneratedIdentifierDelegate delegate) { + this.delegate = delegate; + } + + @Override + public TableInsertBuilder createTableInsertBuilder( + BasicEntityIdentifierMapping identifierMapping, + Expectation expectation, + SessionFactoryImplementor sessionFactory) { + return delegate.createTableInsertBuilder( identifierMapping, expectation, sessionFactory ); + } + + @Override + public PreparedStatement prepareStatement(String insertSql, SharedSessionContractImplementor session) { + return delegate.prepareStatement( insertSql, session ); + } + + @Override + public Object performInsert( + PreparedStatementDetails insertStatementDetails, + JdbcValueBindings valueBindings, + Object entity, + SharedSessionContractImplementor session) { + return delegate.performInsert( insertStatementDetails, valueBindings, entity, session ); + } + + @Override + public String prepareIdentifierGeneratingInsert(String insertSQL) { + return delegate.prepareIdentifierGeneratingInsert( insertSQL ); + } + + @Override + public Object performInsert(String insertSQL, SharedSessionContractImplementor session, Binder binder) { + return delegate.performInsert( insertSQL, session, binder ); + } + + @Override + public GeneratedValues performInsertReturning( + String insertSQL, + SharedSessionContractImplementor session, + Binder binder) { + return delegate.performInsertReturning( insertSQL, session, binder ); + } + + @Override + public TableMutationBuilder createTableMutationBuilder( + Expectation expectation, + SessionFactoryImplementor sessionFactory) { + return delegate.createTableMutationBuilder( expectation, sessionFactory ); + } + + @Override + public GeneratedValues performMutation( + PreparedStatementDetails statementDetails, + JdbcValueBindings valueBindings, + Object entity, + SharedSessionContractImplementor session) { + return delegate.performMutation( statementDetails, valueBindings, entity, session ); + } + + @Override + public EventType getTiming() { + return delegate.getTiming(); + } + + @Override + public boolean supportsArbitraryValues() { + return delegate.supportsArbitraryValues(); + } + + @Override + public boolean supportsRowId() { + return delegate.supportsRowId(); + } + + @Override + public JdbcValuesMappingProducer getGeneratedValuesMappingProducer() { + return delegate.getGeneratedValuesMappingProducer(); + } + + @Override + public CompletionStage reactivePerformMutation( + PreparedStatementDetails singleStatementDetails, + JdbcValueBindings jdbcValueBindings, + Object modelReference, + SharedSessionContractImplementor session) { + return null; + } +} 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 new file mode 100644 index 000000000..6501f68b6 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java @@ -0,0 +1,229 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.generator.values.internal; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + +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; +import org.hibernate.generator.values.GeneratedValueBasicResultBuilder; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.generator.values.internal.GeneratedValuesHelper; +import org.hibernate.generator.values.internal.GeneratedValuesImpl; +import org.hibernate.generator.values.internal.GeneratedValuesMappingProducer; +import org.hibernate.id.IdentifierGeneratorHelper; +import org.hibernate.id.insert.GetGeneratedKeysDelegate; +import org.hibernate.id.insert.UniqueKeySelectingDelegate; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.reactive.id.insert.ReactiveInsertReturningDelegate; +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; +import org.hibernate.reactive.sql.results.internal.ReactiveDirectResultSetAccess; +import org.hibernate.reactive.sql.results.internal.ReactiveResultsHelper; +import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; +import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; +import org.hibernate.sql.exec.internal.BaseExecutionContext; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.model.MutationType; +import org.hibernate.sql.results.internal.RowTransformerArrayImpl; +import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; +import org.hibernate.type.descriptor.WrapperOptions; + +import static org.hibernate.generator.internal.NaturalIdHelper.getNaturalIdPropertyNames; +import static org.hibernate.generator.values.internal.GeneratedValuesHelper.noCustomSql; +import static org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer.UniqueSemantic.NONE; + +/** + * @see org.hibernate.generator.values.internal.GeneratedValuesHelper + */ +@Internal +public class ReactiveGeneratedValuesHelper { + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( IdentifierGeneratorHelper.class ); + + /** + * + * @see GeneratedValuesHelper#getGeneratedValuesDelegate(EntityPersister, EventType) + */ + public static GeneratedValuesMutationDelegate getGeneratedValuesDelegate( + EntityPersister persister, + EventType timing) { + final boolean hasGeneratedProperties = !persister.getGeneratedProperties( timing ).isEmpty(); + final boolean hasRowId = timing == EventType.INSERT && persister.getRowIdMapping() != null; + final Dialect dialect = persister.getFactory().getJdbcServices().getDialect(); + + if ( hasRowId && dialect.supportsInsertReturning() && dialect.supportsInsertReturningRowId() + && noCustomSql( persister, timing ) ) { + // Special case for RowId on INSERT, since GetGeneratedKeysDelegate doesn't support it + // make InsertReturningDelegate the preferred method if the dialect supports it + return new ReactiveInsertReturningDelegate( persister, timing ); + } + + if ( !hasGeneratedProperties ) { + return null; + } + + if ( dialect.supportsInsertReturningGeneratedKeys() + && persister.getFactory().getSessionFactoryOptions().isGetGeneratedKeysEnabled() ) { + return new GetGeneratedKeysDelegate( persister, false, timing ); + } + else if ( supportsReturning( dialect, timing ) && noCustomSql( persister, timing ) ) { + return new ReactiveInsertReturningDelegate( persister, timing ); + } + else if ( timing == EventType.INSERT && persister.getNaturalIdentifierProperties() != null + && !persister.getEntityMetamodel().isNaturalIdentifierInsertGenerated() ) { + return new UniqueKeySelectingDelegate( + persister, + getNaturalIdPropertyNames( persister ), + timing + ); + } + return null; + } + + private static boolean supportsReturning(Dialect dialect, EventType timing) { + return timing == EventType.INSERT ? dialect.supportsInsertReturning() : dialect.supportsUpdateReturning(); + } + + /** + * Reads the {@link EntityPersister#getGeneratedProperties(EventType) generated values} + * for the specified {@link ResultSet}. + * + * @param resultSet The result set from which to extract the generated values + * @param persister The entity type which we're reading the generated values for + * @param wrapperOptions The session + * + * @return The generated values + * + * @throws HibernateException Indicates a problem reading back a generated value + */ + public static CompletionStage getGeneratedValues( + ResultSet resultSet, + EntityPersister persister, + EventType timing, + WrapperOptions wrapperOptions) { + if ( resultSet == null ) { + return null; + } + + final GeneratedValuesMutationDelegate delegate = persister.getMutationDelegate( + timing == EventType.INSERT ? MutationType.INSERT : MutationType.UPDATE + ); + final GeneratedValuesMappingProducer mappingProducer = + (GeneratedValuesMappingProducer) delegate.getGeneratedValuesMappingProducer(); + final List resultBuilders = mappingProducer.getResultBuilders(); + final List generatedProperties = new ArrayList<>( resultBuilders.size() ); + for ( GeneratedValueBasicResultBuilder resultBuilder : resultBuilders ) { + generatedProperties.add( resultBuilder.getModelPart() ); + } + + final GeneratedValuesImpl generatedValues = new GeneratedValuesImpl( generatedProperties ); + return readGeneratedValues( resultSet, persister, mappingProducer, wrapperOptions.getSession() ) + .thenApply( results -> { + if ( LOG.isDebugEnabled() ) { + LOG.debugf( "Extracted generated values %s: %s", MessageHelper.infoString( persister ), results ); + } + + for ( int i = 0; i < results.length; i++ ) { + generatedValues.addGeneratedValue( generatedProperties.get( i ), results[i] ); + } + + return generatedValues; + } ); + } + + private static CompletionStage readGeneratedValues( + ResultSet resultSet, + EntityPersister persister, + JdbcValuesMappingProducer mappingProducer, + SharedSessionContractImplementor session) { + final ExecutionContext executionContext = new BaseExecutionContext( session ); + + + final ReactiveDirectResultSetAccess directResultSetAccess; + try { + directResultSetAccess = new ReactiveDirectResultSetAccess( session, (PreparedStatement) resultSet.getStatement(), resultSet ); + } + catch (SQLException e) { + throw new HibernateException( "Could not retrieve statement from generated values result set", e ); + } + + final ReactiveValuesResultSet jdbcValues = new ReactiveValuesResultSet( + directResultSetAccess, + null, + null, + QueryOptions.NONE, + mappingProducer.resolve( + directResultSetAccess, + session.getLoadQueryInfluencers(), + session.getSessionFactory() + ), + null, + executionContext + ); + + final JdbcValuesSourceProcessingOptions processingOptions = new JdbcValuesSourceProcessingOptions() { + @Override + public Object getEffectiveOptionalObject() { + return null; + } + + @Override + public String getEffectiveOptionalEntityName() { + return null; + } + + @Override + public Object getEffectiveOptionalId() { + return null; + } + + @Override + public boolean shouldReturnProxies() { + return true; + } + }; + + final JdbcValuesSourceProcessingStateStandardImpl valuesProcessingState = new JdbcValuesSourceProcessingStateStandardImpl( + executionContext, + processingOptions + ); + + final ReactiveRowReader rowReader = ReactiveResultsHelper.createRowReader( + executionContext, + LockOptions.NONE, + RowTransformerArrayImpl.instance(), + Object[].class, + jdbcValues.getValuesMapping() + ); + + final ReactiveRowProcessingState rowProcessingState = new ReactiveRowProcessingState( valuesProcessingState, executionContext, rowReader, jdbcValues ); + return ReactiveListResultsConsumer.instance( NONE ) + .consume( jdbcValues, session, processingOptions, valuesProcessingState, rowProcessingState, rowReader ) + .thenApply( results -> { + if ( results.isEmpty() ) { + throw new HibernateException( "The database returned no natively generated values : " + persister.getNavigableRole().getFullPath() ); + } + return results.get( 0 ); + } ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractReturningDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractReturningDelegate.java index e2794b218..42dfddef0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractReturningDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractReturningDelegate.java @@ -6,6 +6,7 @@ package org.hibernate.reactive.id.insert; import java.lang.invoke.MethodHandles; +import java.sql.PreparedStatement; import java.util.concurrent.CompletionStage; import org.hibernate.dialect.CockroachDialect; @@ -19,43 +20,60 @@ import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.id.PostInsertIdentityPersister; import org.hibernate.id.insert.Binder; import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.type.Type; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + public interface ReactiveAbstractReturningDelegate extends ReactiveInsertGeneratedIdentifierDelegate { - Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + @Override + PreparedStatement prepareStatement(String insertSql, SharedSessionContractImplementor session); PostInsertIdentityPersister getPersister(); @Override - default CompletionStage reactivePerformInsert(PreparedStatementDetails insertStatementDetails, JdbcValueBindings jdbcValueBindings, Object entity, SharedSessionContractImplementor session) { - final Class idType = getPersister().getIdentifierType().getReturnedClass(); - final JdbcServices jdbcServices = session.getJdbcServices(); + default CompletionStage reactivePerformInsertReturning(String sql, SharedSessionContractImplementor session, Binder binder) { final String identifierColumnName = getPersister().getIdentifierColumnNames()[0]; - final String insertSql = createInsert( insertStatementDetails, identifierColumnName, jdbcServices.getDialect() ); + final JdbcServices jdbcServices = session.getJdbcServices(); + final String insertSql = createInsert( sql, identifierColumnName, jdbcServices.getDialect() ); + final Object[] params = PreparedStatementAdaptor.bind( binder::bindValues ); + return reactiveExecuteAndExtractReturning( insertSql, params, session ) + .thenApply( this::validateGeneratedIdentityId ); + } + CompletionStage reactiveExecuteAndExtractReturning(String sql, Object[] params, SharedSessionContractImplementor session); + + @Override + default CompletionStage reactivePerformMutation( + PreparedStatementDetails statementDetails, + JdbcValueBindings valueBindings, + Object entity, + SharedSessionContractImplementor session) { Object[] params = PreparedStatementAdaptor.bind( statement -> { - PreparedStatementDetails details = new PrepareStatementDetailsAdaptor( insertStatementDetails, statement, jdbcServices ); - jdbcValueBindings.beforeStatement( details ); + PreparedStatementDetails details = new PrepareStatementDetailsAdaptor( statementDetails, statement, session.getJdbcServices() ); + valueBindings.beforeStatement( details ); } ); - - ReactiveConnection reactiveConnection = ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); - return reactiveConnection - .insertAndSelectIdentifier( insertSql, params, idType, identifierColumnName ) - .thenApply( this::validateGeneratedIdentityId ); + final String identifierColumnName = getPersister().getIdentifierColumnNames()[0]; + final JdbcServices jdbcServices = session.getJdbcServices(); + final String insertSql = createInsert( statementDetails.getSqlString(), identifierColumnName, jdbcServices.getDialect() ); + return reactiveExecuteAndExtractReturning( insertSql, params, session ) + .whenComplete( (generatedValues, throwable) -> { + if ( statementDetails.getStatement() != null ) { + statementDetails.releaseStatement( session ); + } + valueBindings.afterStatement( statementDetails.getMutatingTableDetails() ); + } ); } - private Object validateGeneratedIdentityId(Object generatedId) { + default GeneratedValues validateGeneratedIdentityId(GeneratedValues generatedId) { if ( generatedId == null ) { - throw LOG.noNativelyGeneratedValueReturned(); + throw make( Log.class, MethodHandles.lookup() ).noNativelyGeneratedValueReturned(); } // CockroachDB might generate an identifier that fits an integer (and maybe a short) from time to time. @@ -63,24 +81,23 @@ private Object validateGeneratedIdentityId(Object generatedId) { Type identifierType = getPersister().getIdentifierType(); if ( ( identifierType.getReturnedClass().equals( Short.class ) || identifierType.getReturnedClass().equals( Integer.class ) ) && getPersister().getFactory().getJdbcServices().getDialect() instanceof CockroachDialect ) { - throw LOG.invalidIdentifierTypeForCockroachDB( identifierType.getReturnedClass(), getPersister().getEntityName() ); + throw make( Log.class, MethodHandles.lookup() ).invalidIdentifierTypeForCockroachDB( identifierType.getReturnedClass(), getPersister().getEntityName() ); } return generatedId; } - private static String createInsert(PreparedStatementDetails insertStatementDetails, String identifierColumnName, Dialect dialect) { + private static String createInsert(String insertSql, String identifierColumnName, Dialect dialect) { + String sql = insertSql; final String sqlEnd = " returning " + identifierColumnName; Dialect realDialect = DialectDelegateWrapper.extractRealDialect( dialect ); if ( realDialect instanceof MySQLDialect ) { - // For some reasons ORM generates a query with an invalid syntax - String sql = insertStatementDetails.getSqlString(); + // For some reason ORM generates a query with an invalid syntax int index = sql.lastIndexOf( sqlEnd ); return index > -1 ? sql.substring( 0, index ) : sql; } if ( realDialect instanceof SQLServerDialect ) { - String sql = insertStatementDetails.getSqlString(); int index = sql.lastIndexOf( sqlEnd ); // FIXME: this is a hack for HHH-16365 if ( index > -1 ) { @@ -99,11 +116,10 @@ private static String createInsert(PreparedStatementDetails insertStatementDetai if ( realDialect instanceof DB2Dialect ) { // ORM query: select id from new table ( insert into IntegerTypeEntity values ( )) // Correct : select id from new table ( insert into LongTypeEntity (id) values (default)) - return insertStatementDetails.getSqlString().replace( " values ( ))", " (" + identifierColumnName + ") values (default))" ); + return sql.replace( " values ( ))", " (" + identifierColumnName + ") values (default))" ); } if ( realDialect instanceof OracleDialect ) { final String valuesStr = " values ( )"; - String sql = insertStatementDetails.getSqlString(); int index = sql.lastIndexOf( sqlEnd ); // remove "returning id" since it's added via if ( index > -1 ) { @@ -119,12 +135,6 @@ private static String createInsert(PreparedStatementDetails insertStatementDetai return sql; } - return insertStatementDetails.getSqlString(); + return sql; } - - @Override - default CompletionStage reactivePerformInsert(String insertSQL, SharedSessionContractImplementor session, Binder binder) { - throw LOG.notYetImplemented(); - } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractSelectingDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractSelectingDelegate.java deleted file mode 100644 index c2c58116e..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveAbstractSelectingDelegate.java +++ /dev/null @@ -1,79 +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.id.insert; - -import java.lang.invoke.MethodHandles; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.util.concurrent.CompletionStage; - -import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; -import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; -import org.hibernate.engine.jdbc.spi.JdbcCoordinator; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.session.ReactiveConnectionSupplier; - -import static org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor.bind; -import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; - -/** - * @see org.hibernate.id.insert.AbstractSelectingDelegate - */ -public interface ReactiveAbstractSelectingDelegate extends ReactiveInsertGeneratedIdentifierDelegate { - Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - String getSelectSQL(); - - void bindParameters(Object entity, PreparedStatement ps, SharedSessionContractImplementor session); - - Object extractGeneratedValue(ResultSet resultSet, SharedSessionContractImplementor session); - - @Override - default CompletionStage reactivePerformInsert( - PreparedStatementDetails insertStatementDetails, - JdbcValueBindings jdbcValueBindings, - Object entity, - SharedSessionContractImplementor session) { - final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); - final JdbcServices jdbcServices = session.getJdbcServices(); - - Object[] updateParams = bind( statement -> { - PreparedStatementDetails details = new PrepareStatementDetailsAdaptor( insertStatementDetails, statement, session.getJdbcServices() ); - jdbcValueBindings.beforeStatement( details ); - } ); - - final String selectSQL = getSelectSQL(); - ReactiveConnection reactiveConnection = ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); - return reactiveConnection - .update( insertStatementDetails.getSqlString(), updateParams ) - .thenCompose( updated -> { - Object[] selectParams = bind( statement -> bindParameters( entity, statement, session ) ); - return reactiveConnection - .selectJdbc( selectSQL, selectParams ) - .handle( (resultSet, e) -> { - if ( e != null ) { - throw LOG.unableToExecutePostInsertIdSelectionQuery( selectSQL, e ); - } - return resultSet; - } ); - } ) - .thenCompose( resultSet -> { - try { - return completedFuture( extractGeneratedValue( resultSet, session ) ); - } - catch (Throwable e) { - return failedFuture( LOG.bindParametersForPostInsertIdSelectQueryError( selectSQL, e ) ); - } - } ); - } - -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveBasicSelectingDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveBasicSelectingDelegate.java deleted file mode 100644 index a9f6141e9..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveBasicSelectingDelegate.java +++ /dev/null @@ -1,57 +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.id.insert; - -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.concurrent.CompletionStage; - -import org.hibernate.dialect.Dialect; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.id.PostInsertIdentityPersister; -import org.hibernate.id.insert.BasicSelectingDelegate; -import org.hibernate.id.insert.Binder; - -public class ReactiveBasicSelectingDelegate extends BasicSelectingDelegate implements ReactiveAbstractSelectingDelegate { - - public ReactiveBasicSelectingDelegate(PostInsertIdentityPersister persister, Dialect dialect) { - super( persister, dialect ); - } - - @Override - public CompletionStage reactivePerformInsert( - String insertSQL, - SharedSessionContractImplementor session, - Binder binder) { - throw LOG.notYetImplemented(); - } - - @Override - public String getSelectSQL() { - return super.getSelectSQL(); - } - - @Override - public void bindParameters(Object entity, PreparedStatement ps, SharedSessionContractImplementor session) { - try { - super.bindParameters( entity, ps, session ); - } - catch (SQLException e) { - throw new RuntimeException( e ); - } - } - - @Override - public Object extractGeneratedValue(ResultSet resultSet, SharedSessionContractImplementor session) { - try { - return super.extractGeneratedValue( resultSet, session ); - } - catch (SQLException e) { - throw new RuntimeException( e ); - } - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertGeneratedIdentifierDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertGeneratedIdentifierDelegate.java index 3dc3e64bd..15d5ddb3a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertGeneratedIdentifierDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertGeneratedIdentifierDelegate.java @@ -7,21 +7,18 @@ import java.util.concurrent.CompletionStage; -import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; -import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.id.insert.Binder; +import org.hibernate.reactive.generator.values.ReactiveGeneratedValuesMutationDelegate; /** * @see org.hibernate.id.insert.InsertGeneratedIdentifierDelegate */ -public interface ReactiveInsertGeneratedIdentifierDelegate { +public interface ReactiveInsertGeneratedIdentifierDelegate extends ReactiveGeneratedValuesMutationDelegate { - CompletionStage reactivePerformInsert( - PreparedStatementDetails insertStatementDetails, - JdbcValueBindings valueBindings, - Object entity, - SharedSessionContractImplementor session); - - CompletionStage reactivePerformInsert(String insertSQL, SharedSessionContractImplementor session, Binder binder); + CompletionStage reactivePerformInsertReturning( + String insertSQL, + SharedSessionContractImplementor session, + Binder binder); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java index 6571252c3..79460c546 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java @@ -5,30 +5,137 @@ */ package org.hibernate.reactive.id.insert; +import java.lang.invoke.MethodHandles; +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CompletionStage; import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.MySQLDialect; +import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; +import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.EventType; +import org.hibernate.generator.values.GeneratedValueBasicResultBuilder; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.generator.values.internal.TableUpdateReturningBuilder; import org.hibernate.id.PostInsertIdentityPersister; -import org.hibernate.id.insert.Binder; +import org.hibernate.id.insert.AbstractReturningDelegate; import org.hibernate.id.insert.InsertReturningDelegate; +import org.hibernate.id.insert.TableInsertReturningBuilder; +import org.hibernate.jdbc.Expectation; +import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +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.ReactiveConnectionSupplier; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.model.ast.MutatingTableReference; +import org.hibernate.sql.model.ast.builder.TableMutationBuilder; -public class ReactiveInsertReturningDelegate extends InsertReturningDelegate implements ReactiveAbstractReturningDelegate { +import static java.sql.Statement.NO_GENERATED_KEYS; +import static org.hibernate.generator.values.internal.GeneratedValuesHelper.getActualGeneratedModelPart; +import static org.hibernate.reactive.generator.values.internal.ReactiveGeneratedValuesHelper.getGeneratedValues; + +/** + * @see InsertReturningDelegate + */ +public class ReactiveInsertReturningDelegate extends AbstractReturningDelegate implements ReactiveAbstractReturningDelegate { + + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + private final PostInsertIdentityPersister persister; + private final MutatingTableReference tableReference; + private final List generatedColumns; + + public ReactiveInsertReturningDelegate(EntityPersister persister, EventType timing) { + this( (PostInsertIdentityPersister) persister, timing, false ); + + } public ReactiveInsertReturningDelegate(PostInsertIdentityPersister persister, Dialect dialect) { - super( persister, dialect ); + // With JDBC it's possible to enabled GetGeneratedKeys for identity generation. + // Vert.x doesn't have this option, so we always use the same strategy for all database. + // But MySQL requires setting supportsArbitraryValues to false or it's not going to work. + this( persister, EventType.INSERT, supportsArbitraryValues( dialect ) ); + } + + private static boolean supportsArbitraryValues( Dialect dialect) { + return !( dialect instanceof MySQLDialect ); + } + + private ReactiveInsertReturningDelegate(PostInsertIdentityPersister persister, EventType timing, boolean supportsArbitraryValues) { + super( + persister, + timing, + supportsArbitraryValues, + persister.getFactory().getJdbcServices().getDialect().supportsInsertReturningRowId() + ); + this.persister = persister; + this.tableReference = new MutatingTableReference( persister.getIdentifierTableMapping() ); + final List resultBuilders = jdbcValuesMappingProducer.getResultBuilders(); + this.generatedColumns = new ArrayList<>( resultBuilders.size() ); + for ( GeneratedValueBasicResultBuilder resultBuilder : resultBuilders ) { + generatedColumns.add( new ColumnReference( + tableReference, + getActualGeneratedModelPart( resultBuilder.getModelPart() ) + ) ); + } + } + @Override + public TableMutationBuilder createTableMutationBuilder( + Expectation expectation, + SessionFactoryImplementor sessionFactory) { + if ( getTiming() == EventType.INSERT ) { + return new TableInsertReturningBuilder( persister, tableReference, generatedColumns, sessionFactory ); + } + else { + return new TableUpdateReturningBuilder<>( persister, tableReference, generatedColumns, sessionFactory ); + } + } + + @Override + public String prepareIdentifierGeneratingInsert(String insertSQL) { + return dialect().getIdentityColumnSupport().appendIdentitySelectToInsert( + ( (BasicEntityIdentifierMapping) persister.getRootEntityDescriptor().getIdentifierMapping() ).getSelectionExpression(), + insertSQL + ); + } + + @Override + public PreparedStatement prepareStatement(String sql, SharedSessionContractImplementor session) { + return session.getJdbcCoordinator().getMutationStatementPreparer().prepareStatement( sql, NO_GENERATED_KEYS ); } @Override public PostInsertIdentityPersister getPersister() { - return super.getPersister(); + return persister; + } + + @Override + public GeneratedValues performMutation( + PreparedStatementDetails statementDetails, + JdbcValueBindings valueBindings, + Object entity, + SharedSessionContractImplementor session) { + throw LOG.nonReactiveMethodCall( "reactivePerformMutation" ); + } + + @Override + public CompletionStage reactiveExecuteAndExtractReturning(String sql, Object[] params, SharedSessionContractImplementor session) { + final Class idType = getPersister().getIdentifierType().getReturnedClass(); + final String identifierColumnName = getPersister().getIdentifierColumnNames()[0]; + return ( (ReactiveConnectionSupplier) session ) + .getReactiveConnection() + .insertAndSelectIdentifierAsResultSet( sql, params, idType, identifierColumnName ) + .thenCompose( rs -> getGeneratedValues( rs, getPersister(), getTiming(), session ) ) + .thenApply( this::validateGeneratedIdentityId ); } @Override - public CompletionStage reactivePerformInsert( - String insertSQL, - SharedSessionContractImplementor session, - Binder binder) { - throw LOG.notYetImplemented(); + protected GeneratedValues executeAndExtractReturning(String sql, PreparedStatement preparedStatement, SharedSessionContractImplementor session) { + throw LOG.nonReactiveMethodCall( "reactiveExecuteAndExtractReturning" ); } } 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 062db0235..f7dca4f5c 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 @@ -46,12 +46,14 @@ protected Fetch buildEagerCollectionFetch( NavigablePath fetchedPath, PluralAttributeMapping fetchedAttribute, TableGroup collectionTableGroup, + boolean needsCollectionKeyResult, FetchParent fetchParent, DomainResultCreationState creationState) { return new ReactiveEagerCollectionFetch( fetchedPath, fetchedAttribute, collectionTableGroup, + needsCollectionKeyResult, fetchParent, creationState ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java index 417c221d2..30c383331 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java @@ -27,6 +27,8 @@ import org.hibernate.engine.spi.*; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEvent; +import org.hibernate.generator.OnExecutionGenerator; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.jdbc.Expectation; import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; @@ -37,6 +39,7 @@ import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; +import org.hibernate.reactive.generator.values.internal.ReactiveGeneratedValuesHelper; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleIdEntityLoader; import org.hibernate.reactive.logging.impl.Log; @@ -56,6 +59,8 @@ import jakarta.persistence.metamodel.Attribute; import static java.util.Collections.emptyMap; +import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.generator.EventType.UPDATE; import static org.hibernate.internal.util.collections.CollectionHelper.setOfSize; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -100,6 +105,18 @@ default AbstractEntityPersister delegate() { return (AbstractEntityPersister) this; } + default GeneratedValuesMutationDelegate createReactiveInsertDelegate() { + if ( isIdentifierAssignedByInsert() ) { + final OnExecutionGenerator generator = (OnExecutionGenerator) getGenerator(); + return generator.getGeneratedIdentifierDelegate( delegate() ); + } + return ReactiveGeneratedValuesHelper.getGeneratedValuesDelegate( this, INSERT ); + } + + default GeneratedValuesMutationDelegate createReactiveUpdateDelegate() { + return ReactiveGeneratedValuesHelper.getGeneratedValuesDelegate( this, UPDATE ); + } + default ReactiveConnection getReactiveConnection(SharedSessionContractImplementor session) { return ReactiveQueryExecutorLookup.extract( session ).getReactiveConnection(); } 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 fe58ba434..bfb19ea0d 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 @@ -21,6 +21,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.generator.Generator; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.id.IdentityGenerator; import org.hibernate.loader.ast.spi.BatchLoaderFactory; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; @@ -171,6 +172,7 @@ public CompletionStage processInsertGeneratedProperties( Object entity, Object[] state, GeneratedValuesProcessor processor, + GeneratedValues generatedValues, SharedSessionContractImplementor session, String entityName) { if ( processor == null ) { @@ -179,12 +181,12 @@ public CompletionStage processInsertGeneratedProperties( ReactiveGeneratedValuesProcessor reactiveGeneratedValuesProcessor = new ReactiveGeneratedValuesProcessor( processor.getSelectStatement(), + processor.getJdbcSelect(), processor.getGeneratedValuesToSelect(), processor.getJdbcParameters(), - processor.getEntityDescriptor(), - processor.getSessionFactory() + processor.getEntityDescriptor() ); - return reactiveGeneratedValuesProcessor.processGeneratedValues( id, entity, state, session ); + return reactiveGeneratedValuesProcessor.processGeneratedValues( id, entity, state, generatedValues, session ); } public CompletionStage processUpdateGeneratedProperties( @@ -192,6 +194,7 @@ public CompletionStage processUpdateGeneratedProperties( Object entity, Object[] state, GeneratedValuesProcessor processor, + GeneratedValues generatedValues, SharedSessionContractImplementor session, String entityName) { if ( processor == null ) { @@ -200,12 +203,12 @@ public CompletionStage processUpdateGeneratedProperties( ReactiveGeneratedValuesProcessor reactiveGeneratedValuesProcessor = new ReactiveGeneratedValuesProcessor( processor.getSelectStatement(), + processor.getJdbcSelect(), processor.getGeneratedValuesToSelect(), processor.getJdbcParameters(), - processor.getEntityDescriptor(), - processor.getSessionFactory() + processor.getEntityDescriptor() ); - return reactiveGeneratedValuesProcessor.processGeneratedValues( id, entity, state, session ); + return reactiveGeneratedValuesProcessor.processGeneratedValues( id, entity, state, generatedValues, session ); } public Map> getUniqueKeyLoadersNew() { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveCoordinatorFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveCoordinatorFactory.java index aefc03ddc..4a125e50d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveCoordinatorFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveCoordinatorFactory.java @@ -11,16 +11,16 @@ import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.reactive.persister.entity.mutation.ReactiveDeleteCoordinator; -import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinator; +import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinatorStandard; import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinator; import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinatorNoOp; public final class ReactiveCoordinatorFactory { - public static ReactiveInsertCoordinator buildInsertCoordinator( + public static ReactiveInsertCoordinatorStandard buildInsertCoordinator( AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) { - return new ReactiveInsertCoordinator( entityPersister, factory ); + return new ReactiveInsertCoordinatorStandard( entityPersister, factory ); } public static ReactiveUpdateCoordinator buildUpdateCoordinator( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java index ca42288fd..ba365a463 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java @@ -13,6 +13,7 @@ import org.hibernate.bytecode.BytecodeLogging; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.persister.entity.EntityPersister; @@ -31,14 +32,23 @@ public interface ReactiveEntityPersister extends EntityPersister { * * @see EntityPersister#insert(Object, Object[], Object, SharedSessionContractImplementor) */ - CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session); + default CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) { + return insertReactive( id, fields, object, session ); + }; + + /** + * Insert the given instance state without blocking, but it allows to specify if it's an identity insert or a regular one. + * + * @see EntityPersister#insert(Object, Object[], Object, SharedSessionContractImplementor) + */ + CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session, boolean isIdentityType); /** * Insert the given instance state without blocking. * * @see EntityPersister#insert(Object[], Object, SharedSessionContractImplementor) */ - CompletionStage insertReactive(Object[] fields, Object object, SharedSessionContractImplementor session); + CompletionStage insertReactive(Object[] fields, Object object, SharedSessionContractImplementor session); /** * Delete the given instance without blocking. @@ -52,7 +62,7 @@ public interface ReactiveEntityPersister extends EntityPersister { * * @see EntityPersister#update(Object, Object[], int[], boolean, Object[], Object, Object, Object, SharedSessionContractImplementor) */ - CompletionStage updateReactive( + CompletionStage updateReactive( final Object id, final Object[] values, int[] dirtyAttributeIndexes, @@ -138,6 +148,7 @@ CompletionStage reactiveProcessInsertGenerated( Object id, Object entity, Object[] state, + GeneratedValues generatedValues, SharedSessionContractImplementor session); /** @@ -147,6 +158,7 @@ CompletionStage reactiveProcessUpdateGenerated( Object id, Object entity, Object[] state, + GeneratedValues generatedValues, SharedSessionContractImplementor session); /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveGeneratedValuesProcessor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveGeneratedValuesProcessor.java index db44c21b0..bb4ecef88 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveGeneratedValuesProcessor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveGeneratedValuesProcessor.java @@ -7,23 +7,23 @@ import java.util.List; import java.util.concurrent.CompletionStage; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; + import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.loader.ast.internal.NoCallbackExecutionContext; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import static org.hibernate.internal.util.NullnessUtil.castNonNull; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + /** * A reactive version of {@link org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor} */ @@ -32,42 +32,58 @@ class ReactiveGeneratedValuesProcessor { private final SelectStatement selectStatement; private final List generatedValuesToSelect; private final JdbcParametersList jdbcParameters; - + private final JdbcOperationQuerySelect jdbcSelect; private final EntityMappingType entityDescriptor; - private final SessionFactoryImplementor sessionFactory; ReactiveGeneratedValuesProcessor(SelectStatement selectStatement, + JdbcOperationQuerySelect jdbcSelect, List generatedValuesToSelect, JdbcParametersList jdbcParameters, - EntityMappingType entityDescriptor, - SessionFactoryImplementor sessionFactory) { + EntityMappingType entityDescriptor) { this.selectStatement = selectStatement; + this.jdbcSelect = jdbcSelect; this.generatedValuesToSelect = generatedValuesToSelect; this.jdbcParameters = jdbcParameters; this.entityDescriptor = entityDescriptor; - this.sessionFactory = sessionFactory; } - CompletionStage processGeneratedValues(Object id, - Object entity, - Object[] state, - SharedSessionContractImplementor session) { - - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - - final JdbcParameterBindings jdbcParameterBindings = getJdbcParameterBindings(id, session); - - final JdbcOperationQuerySelect jdbcSelect = sqlAstTranslatorFactory - .buildSelectTranslator( sessionFactory, selectStatement ).translate(jdbcParameterBindings, QueryOptions.NONE); + CompletionStage processGeneratedValues(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) { + if ( hasActualGeneratedValuesToSelect( session, entity ) ) { + if ( selectStatement != null ) { + return executeSelect( id, session ) + .thenAccept( l -> { + assert l.size() == 1; + setEntityAttributes( entity, state, l.get( 0 ) ); + } ); + } + else { + castNonNull( generatedValues ); + final List results = generatedValues.getGeneratedValues( generatedValuesToSelect ); + setEntityAttributes( entity, state, results.toArray( new Object[0] ) ); + return voidFuture(); + } + } + return voidFuture(); + } + private CompletionStage> executeSelect(Object id, SharedSessionContractImplementor session) { return StandardReactiveSelectExecutor.INSTANCE - .list(jdbcSelect, jdbcParameterBindings, new NoCallbackExecutionContext( session ), r -> r, ReactiveListResultsConsumer.UniqueSemantic.FILTER) - .thenAccept(l -> { - assert l.size() == 1; - setEntityAttributes( entity, state, l.get(0) ); - }) ; + .list( + jdbcSelect, + getJdbcParameterBindings( id, session ), + new NoCallbackExecutionContext( session ), + r -> r, + ReactiveListResultsConsumer.UniqueSemantic.FILTER + ); + } + + private boolean hasActualGeneratedValuesToSelect(SharedSessionContractImplementor session, Object entity) { + for ( AttributeMapping attributeMapping : generatedValuesToSelect ) { + if ( attributeMapping.getGenerator().generatedOnExecution( entity, session ) ) { + return true; + } + } + return false; } private JdbcParameterBindings getJdbcParameterBindings(Object id, SharedSessionContractImplementor session) { 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 e0213d25e..8e4f24a1f 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 @@ -20,6 +20,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.generator.Generator; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.jdbc.Expectation; import org.hibernate.loader.ast.spi.MultiIdEntityLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; @@ -44,15 +45,13 @@ import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader; import org.hibernate.reactive.persister.entity.mutation.ReactiveDeleteCoordinator; -import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinator; +import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinatorStandard; import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinator; -import org.hibernate.reactive.sql.results.internal.ReactiveEntityResultJoinedSubclassImpl; import org.hibernate.reactive.util.impl.CompletionStages; 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.entity.internal.EntityResultJoinedSubclassImpl; import org.hibernate.type.EntityType; /** @@ -161,26 +160,16 @@ public Generator getGenerator() throws HibernateException { return reactiveDelegate.reactive( super.getGenerator() ); } + /** + * @see AbstractEntityPersister#createDomainResult(NavigablePath, TableGroup, String, DomainResultCreationState) + */ @Override public DomainResult createDomainResult( NavigablePath navigablePath, TableGroup tableGroup, String resultVariable, DomainResultCreationState creationState) { - if ( hasSubclasses() ) { - final EntityResultJoinedSubclassImpl entityResultJoinedSubclass = new ReactiveEntityResultJoinedSubclassImpl( - navigablePath, - this, - tableGroup, - resultVariable - ); - entityResultJoinedSubclass.afterInitialize( entityResultJoinedSubclass, creationState ); - //noinspection unchecked - return entityResultJoinedSubclass; - } - else { - return reactiveDelegate.createDomainResult( this, navigablePath, tableGroup, resultVariable, creationState ); - } + return reactiveDelegate.createDomainResult( this, navigablePath, tableGroup, resultVariable, creationState ); } @Override @@ -189,23 +178,27 @@ public NaturalIdMapping generateNaturalIdMapping(MappingModelCreationProcess cre } @Override - public CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) { - return ( (ReactiveInsertCoordinator) getInsertCoordinator() ).coordinateReactiveInsert( id, fields, object, session ) - .thenCompose( CompletionStages::voidFuture ); + public CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) { + return insertReactive( id, fields, object, session, true ); } @Override - public CompletionStage insertReactive(Object[] fields, Object object, SharedSessionContractImplementor session) { - return ( (ReactiveInsertCoordinator) getInsertCoordinator() ).coordinateReactiveInsert( null, fields, object, session ); + public CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session, boolean isIdentityInsert) { + return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( object, id, fields, session, isIdentityInsert ); + } + + @Override + public CompletionStage insertReactive(Object[] fields, Object object, SharedSessionContractImplementor session) { + return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( object, null, fields, session, true ); } @Override public CompletionStage deleteReactive(Object id, Object version, Object object, SharedSessionContractImplementor session) { - return ( (ReactiveDeleteCoordinator) getDeleteCoordinator() ).coordinateReactiveDelete( object, id, version, session ); + return ( (ReactiveDeleteCoordinator) getDeleteCoordinator() ).reactiveDelete( object, id, version, session ); } @Override - public CompletionStage updateReactive( + public CompletionStage updateReactive( Object id, Object[] values, int[] dirtyAttributeIndexes, @@ -219,7 +212,7 @@ public CompletionStage updateReactive( // This is different from Hibernate ORM because our reactive update coordinator cannot be share among // multiple update operations .makeScopedCoordinator() - .coordinateReactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ); + .reactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ); } /** @@ -242,7 +235,8 @@ public CompletionStage mergeReactive( // multiple update operations return ( (ReactiveUpdateCoordinator) getMergeCoordinator() ) .makeScopedCoordinator() - .coordinateReactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ); + .reactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ) + .thenCompose( CompletionStages::voidFuture ); } @Override @@ -305,12 +299,12 @@ public String[][] getLazyPropertyColumnAliases() { /** * Process properties generated with an insert * - * @see AbstractEntityPersister#processInsertGeneratedProperties(Object, Object, Object[], SharedSessionContractImplementor) + * @see AbstractEntityPersister#processInsertGeneratedProperties(Object, Object, Object[], GeneratedValues, SharedSessionContractImplementor) */ @Override - public CompletionStage reactiveProcessInsertGenerated(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) { + public CompletionStage reactiveProcessInsertGenerated(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) { return reactiveDelegate.processInsertGeneratedProperties( id, entity, state, - getInsertGeneratedValuesProcessor(), session, getEntityName() ); + getInsertGeneratedValuesProcessor(), generatedValues, session, getEntityName() ); } /** @@ -319,9 +313,9 @@ public CompletionStage reactiveProcessInsertGenerated(Object id, Object en * @see AbstractEntityPersister#processUpdateGeneratedProperties(Object, Object, Object[], SharedSessionContractImplementor) */ @Override - public CompletionStage reactiveProcessUpdateGenerated(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) { + public CompletionStage reactiveProcessUpdateGenerated(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) { return reactiveDelegate.processUpdateGeneratedProperties( id, entity, state, - getUpdateGeneratedValuesProcessor(), session, getEntityName() ); + getUpdateGeneratedValuesProcessor(), generatedValues, session, getEntityName() ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveMergeCoordinatorStandardScopeFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveMergeCoordinatorStandardScopeFactory.java index 17bcc50e2..73441f382 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveMergeCoordinatorStandardScopeFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveMergeCoordinatorStandardScopeFactory.java @@ -37,7 +37,7 @@ public ReactiveScopedUpdateCoordinator makeScopedCoordinator() { return new ReactiveMergeCoordinator( entityPersister(), factory(), - getStaticUpdateGroup(), + getStaticMutationOperationGroup(), getBatchKey(), getVersionUpdateGroup(), getVersionUpdateBatchkey() 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 b144573ea..e736d7319 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 @@ -9,6 +9,7 @@ import java.util.List; import java.util.concurrent.CompletionStage; + import org.hibernate.FetchMode; import org.hibernate.HibernateException; import org.hibernate.LockMode; @@ -20,6 +21,9 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.generator.Generator; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; import org.hibernate.jdbc.Expectation; import org.hibernate.loader.ast.spi.MultiIdEntityLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; @@ -41,10 +45,12 @@ import org.hibernate.persister.entity.mutation.InsertCoordinator; import org.hibernate.persister.entity.mutation.UpdateCoordinator; import org.hibernate.property.access.spi.PropertyAccess; +import org.hibernate.reactive.generator.values.GeneratedValuesMutationDelegateAdaptor; +import org.hibernate.reactive.generator.values.ReactiveInsertGeneratedIdentifierDelegate; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader; import org.hibernate.reactive.persister.entity.mutation.ReactiveDeleteCoordinator; -import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinator; +import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinatorStandard; import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinator; import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.spi.NavigablePath; @@ -71,6 +77,16 @@ public ReactiveSingleTableEntityPersister( reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, creationContext ); } + @Override + public GeneratedValuesMutationDelegate createInsertDelegate() { + return ReactiveAbstractEntityPersister.super.createReactiveInsertDelegate(); + } + + @Override + protected GeneratedValuesMutationDelegate createUpdateDelegate() { + return ReactiveAbstractEntityPersister.super.createReactiveUpdateDelegate(); + } + @Override protected SingleIdEntityLoader buildSingleIdEntityLoader() { return reactiveDelegate.buildSingleIdEntityLoader(); @@ -112,6 +128,33 @@ public Generator getGenerator() throws HibernateException { return reactiveDelegate.reactive( super.getGenerator() ); } + @Override + public GeneratedValuesMutationDelegate getInsertDelegate() { + GeneratedValuesMutationDelegate insertDelegate = super.getInsertDelegate(); + if ( insertDelegate == null ) { + return null; + } + return new GeneratedValuesMutationDelegateAdaptor( insertDelegate ); + } + + @Override + public GeneratedValuesMutationDelegate getUpdateDelegate() { + GeneratedValuesMutationDelegate updateDelegate = super.getUpdateDelegate(); + if ( updateDelegate == null ) { + return null; + } + return new GeneratedValuesMutationDelegateAdaptor( updateDelegate ); + } + + @Override + public InsertGeneratedIdentifierDelegate getIdentityInsertDelegate() { + final GeneratedValuesMutationDelegate insertDelegate = super.getInsertDelegate(); + if ( insertDelegate instanceof InsertGeneratedIdentifierDelegate ) { + return new ReactiveInsertGeneratedIdentifierDelegate( (InsertGeneratedIdentifierDelegate) insertDelegate ); + } + return null; + } + @Override public DomainResult createDomainResult( NavigablePath navigablePath, @@ -243,9 +286,9 @@ public void merge( * @see AbstractEntityPersister#processInsertGeneratedProperties(Object, Object, Object[], SharedSessionContractImplementor) */ @Override - public CompletionStage reactiveProcessInsertGenerated(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) { - return reactiveDelegate.processInsertGeneratedProperties( id, entity, state, - getInsertGeneratedValuesProcessor(), session, getEntityName() ); + public CompletionStage reactiveProcessInsertGenerated(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) { + return reactiveDelegate + .processInsertGeneratedProperties( id, entity, state, getInsertGeneratedValuesProcessor(), generatedValues, session, getEntityName() ); } /** @@ -254,9 +297,9 @@ public CompletionStage reactiveProcessInsertGenerated(Object id, Object en * @see AbstractEntityPersister#processUpdateGeneratedProperties(Object, Object, Object[], SharedSessionContractImplementor) */ @Override - public CompletionStage reactiveProcessUpdateGenerated(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) { - return reactiveDelegate.processUpdateGeneratedProperties( id, entity, state, - getUpdateGeneratedValuesProcessor(), session, getEntityName() ); + public CompletionStage reactiveProcessUpdateGenerated(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) { + return reactiveDelegate + .processUpdateGeneratedProperties( id, entity, state, getUpdateGeneratedValuesProcessor(), generatedValues, session, getEntityName() ); } /** @@ -298,26 +341,30 @@ private CompletionStage doReactiveLoad(Object id, Object optionalObject, } @Override - public CompletionStage insertReactive(Object[] fields, Object object, SharedSessionContractImplementor session) { - return ( (ReactiveInsertCoordinator) getInsertCoordinator() ).coordinateReactiveInsert( null, fields, object, session ); + public CompletionStage insertReactive(Object[] fields, Object entity, SharedSessionContractImplementor session) { + return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( entity, null, fields, session, true ); } @Override - public CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) { - return ( (ReactiveInsertCoordinator) getInsertCoordinator() ).coordinateReactiveInsert( id, fields, object, session ) - .thenCompose( CompletionStages::voidFuture ); + public CompletionStage insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session) { + return insertReactive( id, fields, entity, session, true ); } @Override - public CompletionStage deleteReactive(Object id, Object version, Object object, SharedSessionContractImplementor session) { - return ( (ReactiveDeleteCoordinator) getDeleteCoordinator() ).coordinateReactiveDelete( object, id, version, session ); + public CompletionStage insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session, boolean isIdentityInsert) { + return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( entity, id, fields, session, isIdentityInsert ); + } + + @Override + public CompletionStage deleteReactive(Object id, Object version, Object entity, SharedSessionContractImplementor session) { + return ( (ReactiveDeleteCoordinator) getDeleteCoordinator() ).reactiveDelete( entity, id, version, session ); } /** * Update an object */ @Override - public CompletionStage updateReactive( + public CompletionStage updateReactive( final Object id, final Object[] values, int[] dirtyAttributeIndexes, @@ -331,7 +378,7 @@ public CompletionStage updateReactive( // This is different from Hibernate ORM because our reactive update coordinator cannot be share among // multiple update operations .makeScopedCoordinator() - .coordinateReactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ); + .reactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ); } /** @@ -354,7 +401,8 @@ public CompletionStage mergeReactive( // This is different from Hibernate ORM because our reactive update coordinator cannot be share among // multiple update operations .makeScopedCoordinator() - .coordinateReactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ); + .reactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ) + .thenCompose( CompletionStages::voidFuture ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java index 87c82d6da..266aaa467 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 @@ -22,6 +22,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.generator.Generator; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.id.IdentityGenerator; import org.hibernate.jdbc.Expectation; import org.hibernate.loader.ast.spi.MultiIdEntityLoader; @@ -49,7 +50,7 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.persister.entity.mutation.ReactiveDeleteCoordinator; -import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinator; +import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinatorStandard; import org.hibernate.reactive.persister.entity.mutation.ReactiveUpdateCoordinator; import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.spi.NavigablePath; @@ -268,9 +269,9 @@ public void merge( * @see AbstractEntityPersister#processInsertGeneratedProperties(Object, Object, Object[], SharedSessionContractImplementor) */ @Override - public CompletionStage reactiveProcessInsertGenerated(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) { - return reactiveDelegate.processInsertGeneratedProperties( id, entity, state, - getInsertGeneratedValuesProcessor(), session, getEntityName() ); + public CompletionStage reactiveProcessInsertGenerated(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) { + return reactiveDelegate + .processInsertGeneratedProperties( id, entity, state, getInsertGeneratedValuesProcessor(), generatedValues, session, getEntityName() ); } /** @@ -279,9 +280,9 @@ public CompletionStage reactiveProcessInsertGenerated(Object id, Object en * @see AbstractEntityPersister#processUpdateGeneratedProperties(Object, Object, Object[], SharedSessionContractImplementor) */ @Override - public CompletionStage reactiveProcessUpdateGenerated(Object id, Object entity, Object[] state, SharedSessionContractImplementor session) { - return reactiveDelegate.processUpdateGeneratedProperties( id, entity, state, - getUpdateGeneratedValuesProcessor(), session, getEntityName() ); + public CompletionStage reactiveProcessUpdateGenerated(Object id, Object entity, Object[] state, GeneratedValues generatedValues, SharedSessionContractImplementor session) { + return reactiveDelegate + .processUpdateGeneratedProperties( id, entity, state, getUpdateGeneratedValuesProcessor(), generatedValues, session, getEntityName() ); } @@ -320,25 +321,29 @@ private CompletionStage doReactiveLoad(Object id, Object optionalObject, } @Override - public CompletionStage insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) { - return ( (ReactiveInsertCoordinator) getInsertCoordinator() ) - .coordinateReactiveInsert( id, fields, object, session ) - .thenCompose( CompletionStages::voidFuture ); + public CompletionStage insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session) { + return insertReactive( id, fields, entity, session, true ); } @Override - public CompletionStage insertReactive(Object[] fields, Object object, SharedSessionContractImplementor session) { - return ( (ReactiveInsertCoordinator) getInsertCoordinator() ) - .coordinateReactiveInsert( null, fields, object, session ); + public CompletionStage insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session, boolean isIdentityInsert) { + return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ) + .coordinateReactiveInsert( entity, id, fields, session, isIdentityInsert ); } @Override - public CompletionStage deleteReactive(Object id, Object version, Object object, SharedSessionContractImplementor session) { - return ( (ReactiveDeleteCoordinator) getDeleteCoordinator() ).coordinateReactiveDelete( object, id, version, session ); + public CompletionStage insertReactive(Object[] fields, Object entity, SharedSessionContractImplementor session) { + return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ) + .coordinateReactiveInsert( entity, null, fields, session, true ); } @Override - public CompletionStage updateReactive( + public CompletionStage deleteReactive(Object id, Object version, Object entity, SharedSessionContractImplementor session) { + return ( (ReactiveDeleteCoordinator) getDeleteCoordinator() ).reactiveDelete( entity, id, version, session ); + } + + @Override + public CompletionStage updateReactive( Object id, Object[] values, int[] dirtyAttributeIndexes, @@ -352,7 +357,7 @@ public CompletionStage updateReactive( // This is different from Hibernate ORM because our reactive update coordinator cannot be share among // multiple update operations .makeScopedCoordinator() - .coordinateReactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ); + .reactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ); } /** @@ -373,7 +378,8 @@ public CompletionStage mergeReactive( // multiple update operations return ( (ReactiveUpdateCoordinator) getMergeCoordinator() ) .makeScopedCoordinator() - .coordinateReactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ); + .reactiveUpdate( object, id, rowId, values, oldVersion, oldValues, dirtyAttributeIndexes, hasDirtyCollection, session ) + .thenCompose( CompletionStages::voidFuture ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUpdateCoordinatorStandardScopeFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUpdateCoordinatorStandardScopeFactory.java index f6b593dbc..0f4a63a75 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUpdateCoordinatorStandardScopeFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUpdateCoordinatorStandardScopeFactory.java @@ -25,7 +25,7 @@ public ReactiveScopedUpdateCoordinator makeScopedCoordinator() { return new ReactiveUpdateCoordinatorStandard( entityPersister(), factory(), - this.getStaticUpdateGroup(), + this.getStaticMutationOperationGroup(), this.getBatchKey(), this.getVersionUpdateGroup(), this.getVersionUpdateBatchkey() diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveDeleteCoordinator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveDeleteCoordinator.java index ddea801a6..bcde8e153 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveDeleteCoordinator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveDeleteCoordinator.java @@ -41,13 +41,13 @@ public ReactiveDeleteCoordinator(AbstractEntityPersister entityPersister, Sessio } @Override - public void coordinateDelete(Object entity, Object id, Object version, SharedSessionContractImplementor session) { + public void delete(Object entity, Object id, Object version, SharedSessionContractImplementor session) { throw LOG.nonReactiveMethodCall( "coordinateReactiveDelete" ); } - public CompletionStage coordinateReactiveDelete(Object entity, Object id, Object version, SharedSessionContractImplementor session) { + public CompletionStage reactiveDelete(Object entity, Object id, Object version, SharedSessionContractImplementor session) { try { - super.coordinateDelete( entity, id, version, session ); + super.delete( entity, id, version, session ); return stage != null ? stage : voidFuture(); } catch (Throwable t) { @@ -126,11 +126,11 @@ protected void doStaticDelete(Object entity, Object id, Object rowId, Object[] l final boolean applyVersion = entity != null; final MutationOperationGroup operationGroupToUse = entity == null ? resolveNoVersionDeleteGroup( session ) - : getStaticDeleteGroup(); + : getStaticMutationOperationGroup(); final ReactiveMutationExecutor mutationExecutor = mutationExecutor( session, operationGroupToUse ); - for ( int position = 0; position < getStaticDeleteGroup().getNumberOfOperations(); position++ ) { - final MutationOperation mutation = getStaticDeleteGroup().getOperation( position ); + for ( int position = 0; position < getStaticMutationOperationGroup().getNumberOfOperations(); position++ ) { + final MutationOperation mutation = getStaticMutationOperationGroup().getOperation( position ); if ( mutation != null ) { mutationExecutor.getPreparedStatementDetails( mutation.getTableDetails().getTableName() ); } @@ -141,7 +141,8 @@ protected void doStaticDelete(Object entity, Object id, Object rowId, Object[] l } final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings(); bindPartitionColumnValueBindings( loadedState, session, jdbcValueBindings ); - applyId( id, rowId, mutationExecutor, getStaticDeleteGroup(), session ); + applyId( id, rowId, mutationExecutor, getStaticMutationOperationGroup(), session ); + String[] identifierColumnNames = entityPersister().getIdentifierColumnNames(); mutationExecutor.executeReactive( entity, null, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinator.java index 346faa49b..77514aa9c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinator.java @@ -5,196 +5,18 @@ */ package org.hibernate.reactive.persister.entity.mutation; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; -import org.hibernate.Internal; -import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; -import org.hibernate.engine.jdbc.mutation.MutationExecutor; -import org.hibernate.engine.jdbc.mutation.TableInclusionChecker; -import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.generator.BeforeExecutionGenerator; -import org.hibernate.generator.Generator; -import org.hibernate.metamodel.mapping.AttributeMapping; -import org.hibernate.persister.entity.AbstractEntityPersister; -import org.hibernate.persister.entity.mutation.EntityTableMapping; -import org.hibernate.persister.entity.mutation.InsertCoordinator; -import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.sql.model.MutationOperationGroup; -import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.generator.values.GeneratedValues; -import static org.hibernate.generator.EventType.INSERT; -import static org.hibernate.reactive.persister.entity.mutation.GeneratorValueUtil.generateValue; -import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -@Internal -public class ReactiveInsertCoordinator extends InsertCoordinator { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - public ReactiveInsertCoordinator(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) { - super( entityPersister, factory ); - } - - @Override - public Object coordinateInsert(Object id, Object[] values, Object entity, SharedSessionContractImplementor session) { - throw LOG.nonReactiveMethodCall( "coordinateReactiveInsert" ); - } - - public CompletionStage coordinateReactiveInsert(Object id, Object[] currentValues, Object entity, SharedSessionContractImplementor session) { - return reactivePreInsertInMemoryValueGeneration( currentValues, entity, session ) - .thenCompose( needsDynamicInsert -> { - final boolean forceIdentifierBinding = entityPersister().getGenerator().generatedOnExecution() && id != null; - return entityPersister().getEntityMetamodel().isDynamicInsert() || needsDynamicInsert || forceIdentifierBinding - ? doDynamicInserts( id, currentValues, entity, session, forceIdentifierBinding ) - : doStaticInserts( id, currentValues, entity, session ); - } ); - } - - private CompletionStage reactivePreInsertInMemoryValueGeneration(Object[] currentValues, Object entity, SharedSessionContractImplementor session) { - final EntityMetamodel entityMetamodel = entityPersister().getEntityMetamodel(); - CompletionStage stage = falseFuture(); - if ( entityMetamodel.hasPreInsertGeneratedValues() ) { - final Generator[] generators = entityMetamodel.getGenerators(); - for ( int i = 0; i < generators.length; i++ ) { - final int index = i; - final Generator generator = generators[i]; - if ( generator != null - && !generator.generatedOnExecution() - && generator.generatesOnInsert() ) { - final Object currentValue = currentValues[i]; - final BeforeExecutionGenerator beforeGenerator = (BeforeExecutionGenerator) generator; - stage = stage - .thenCompose( foundStateDependentGenerator -> generateValue( - session, - entity, - currentValue, - beforeGenerator, - INSERT - ) - .thenApply( generatedValue -> { - currentValues[index] = generatedValue; - entityPersister().setValue( entity, index, generatedValue ); - return foundStateDependentGenerator || beforeGenerator.generatedOnExecution(); - } ) - ); - } - } - } - - return stage; - } - - @Override - protected void decomposeForInsert( - MutationExecutor mutationExecutor, - Object id, - Object[] values, - MutationOperationGroup mutationGroup, - boolean[] propertyInclusions, - TableInclusionChecker tableInclusionChecker, - SharedSessionContractImplementor session) { - throw LOG.nonReactiveMethodCall( "decomposeForReactiveInsert" ); - } - - protected CompletionStage decomposeForReactiveInsert( - MutationExecutor mutationExecutor, - Object id, - Object[] values, - MutationOperationGroup mutationGroup, - boolean[] propertyInclusions, - TableInclusionChecker tableInclusionChecker, - SharedSessionContractImplementor session) { - final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings(); - mutationGroup.forEachOperation( (position, operation) -> { - final EntityTableMapping tableDetails = (EntityTableMapping) operation.getTableDetails(); - if ( tableInclusionChecker.include( tableDetails ) ) { - final int[] attributeIndexes = tableDetails.getAttributeIndexes(); - for ( final int attributeIndex : attributeIndexes ) { - if ( propertyInclusions[attributeIndex] ) { - final AttributeMapping mapping = entityPersister().getAttributeMappings().get( attributeIndex ); - decomposeAttribute( values[attributeIndex], session, jdbcValueBindings, mapping ); - } - } - } - } ); - - mutationGroup.forEachOperation( (position, jdbcOperation) -> { - if ( id == null ) { - assert entityPersister().getIdentityInsertDelegate() != null; - } - else { - final EntityTableMapping tableDetails = (EntityTableMapping) jdbcOperation.getTableDetails(); - breakDownJdbcValue( id, session, jdbcValueBindings, tableDetails ); - } - } ); - return voidFuture(); - } - - @Override - protected CompletionStage doDynamicInserts( - Object id, - Object[] values, - Object object, - SharedSessionContractImplementor session, - boolean forceIdentifierBinding) { - final boolean[] insertability = getPropertiesToInsert( values ); - final MutationOperationGroup insertGroup = generateDynamicInsertSqlGroup( insertability, object, session, forceIdentifierBinding ); - final ReactiveMutationExecutor mutationExecutor = getReactiveMutationExecutor( session, insertGroup ); - - final InsertValuesAnalysis insertValuesAnalysis = new InsertValuesAnalysis( entityPersister(), values ); - final TableInclusionChecker tableInclusionChecker = getTableInclusionChecker( insertValuesAnalysis ); - return decomposeForReactiveInsert( mutationExecutor, id, values, insertGroup, insertability, tableInclusionChecker, session ) - .thenCompose( v -> mutationExecutor.executeReactive( - object, - insertValuesAnalysis, - tableInclusionChecker, - (statementDetails, affectedRowCount, batchPosition) -> { - statementDetails.getExpectation() - .verifyOutcome( - affectedRowCount, - statementDetails.getStatement(), - batchPosition, - statementDetails.getSqlString() - ); - return true; - }, - session - ) - .whenComplete( (o, t) -> mutationExecutor.release() ) ); - } +/** + * @see org.hibernate.persister.entity.mutation.InsertCoordinator + */ +public interface ReactiveInsertCoordinator { - @Override - protected CompletionStage doStaticInserts(Object id, Object[] values, Object object, SharedSessionContractImplementor session) { - final InsertValuesAnalysis insertValuesAnalysis = new InsertValuesAnalysis( entityPersister(), values ); - final TableInclusionChecker tableInclusionChecker = getTableInclusionChecker( insertValuesAnalysis ); - final ReactiveMutationExecutor mutationExecutor = getReactiveMutationExecutor( session, getStaticInsertGroup() ); + CompletionStage reactiveInsert(Object entity, Object[] values, SharedSessionContractImplementor session); - return decomposeForReactiveInsert( mutationExecutor, id, values, getStaticInsertGroup(), entityPersister().getPropertyInsertability(), tableInclusionChecker, session ) - .thenCompose( v -> mutationExecutor.executeReactive( - object, - insertValuesAnalysis, - tableInclusionChecker, - (statementDetails, affectedRowCount, batchPosition) -> { - statementDetails - .getExpectation() - .verifyOutcome( affectedRowCount, statementDetails.getStatement(), batchPosition, statementDetails.getSqlString() ); - return true; - }, - session - ) ); - } + CompletionStage reactiveInsert(Object entity, Object id, Object[] values, SharedSessionContractImplementor session); - private ReactiveMutationExecutor getReactiveMutationExecutor(SharedSessionContractImplementor session, MutationOperationGroup operationGroup) { - final MutationExecutorService mutationExecutorService = session - .getFactory() - .getServiceRegistry() - .getService( MutationExecutorService.class ); - return (ReactiveMutationExecutor) mutationExecutorService.createExecutor( this::getInsertBatchKey, operationGroup, session ); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinatorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinatorStandard.java new file mode 100644 index 000000000..96f5813d4 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveInsertCoordinatorStandard.java @@ -0,0 +1,467 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.persister.entity.mutation; + +import java.lang.invoke.MethodHandles; +import java.util.concurrent.CompletionStage; + +import org.hibernate.Internal; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; +import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; +import org.hibernate.engine.jdbc.mutation.MutationExecutor; +import org.hibernate.engine.jdbc.mutation.ParameterUsage; +import org.hibernate.engine.jdbc.mutation.TableInclusionChecker; +import org.hibernate.engine.jdbc.mutation.internal.NoBatchKeyAccess; +import org.hibernate.engine.jdbc.mutation.spi.BatchKeyAccess; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.Generator; +import org.hibernate.generator.OnExecutionGenerator; +import org.hibernate.generator.values.GeneratedValues; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.AttributeMappingsList; +import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.mutation.AbstractMutationCoordinator; +import org.hibernate.persister.entity.mutation.EntityTableMapping; +import org.hibernate.persister.entity.mutation.InsertCoordinator; +import org.hibernate.persister.entity.mutation.InsertCoordinatorStandard; +import org.hibernate.persister.entity.mutation.InsertCoordinatorStandard.InsertValuesAnalysis; +import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.sql.model.MutationOperationGroup; +import org.hibernate.sql.model.MutationType; +import org.hibernate.sql.model.ast.builder.MutationGroupBuilder; +import org.hibernate.sql.model.ast.builder.TableInsertBuilder; +import org.hibernate.sql.model.ast.builder.TableInsertBuilderStandard; +import org.hibernate.sql.model.ast.builder.TableMutationBuilder; +import org.hibernate.tuple.entity.EntityMetamodel; + +import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.reactive.persister.entity.mutation.GeneratorValueUtil.generateValue; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +/** + * @see InsertCoordinatorStandard + */ +@Internal +public class ReactiveInsertCoordinatorStandard extends AbstractMutationCoordinator implements ReactiveInsertCoordinator, + InsertCoordinator { + private final MutationOperationGroup staticInsertGroup; + private final BasicBatchKey batchKey; + + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + public ReactiveInsertCoordinatorStandard(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) { + super( entityPersister, factory ); + + if ( entityPersister.isIdentifierAssignedByInsert() || entityPersister.hasInsertGeneratedProperties() ) { + // disable batching in case of insert generated identifier or properties + batchKey = null; + } + else { + batchKey = new BasicBatchKey( + entityPersister.getEntityName() + "#INSERT", + null + ); + } + + if ( entityPersister.getEntityMetamodel().isDynamicInsert() ) { + // the entity specified dynamic-insert - skip generating the + // static inserts as we will create them every time + staticInsertGroup = null; + } + else { + staticInsertGroup = generateStaticOperationGroup(); + } + + } + + @Override + public GeneratedValues insert( + Object entity, + Object[] values, + SharedSessionContractImplementor session) { + return insert( entity, null, values, session ); + } + + @Override + public GeneratedValues insert( + Object entity, + Object id, + Object[] values, + SharedSessionContractImplementor session) { + throw LOG.nonReactiveMethodCall( "reactiveInsert" ); + } + + @Override + public CompletionStage reactiveInsert( + Object entity, + Object[] values, + SharedSessionContractImplementor session) { + return reactiveInsert( entity, null, values, session ); + } + + @Override + public CompletionStage reactiveInsert( + Object entity, + Object id, + Object[] values, + SharedSessionContractImplementor session) { + return coordinateReactiveInsert( entity, id, values, session, true ); + } + + public CompletionStage coordinateReactiveInsert( + Object entity, + Object id, + Object[] values, + SharedSessionContractImplementor session, + boolean isIdentityInsert) { + return reactivePreInsertInMemoryValueGeneration( values, entity, session ) + .thenCompose( needsDynamicInsert -> { + final boolean forceIdentifierBinding = entityPersister().getGenerator().generatedOnExecution() && id != null; + return entityPersister().getEntityMetamodel().isDynamicInsert() || needsDynamicInsert || forceIdentifierBinding + ? doDynamicInserts( id, values, entity, session, forceIdentifierBinding, isIdentityInsert ) + : doStaticInserts( id, values, entity, session, isIdentityInsert ); + } ); + } + + private CompletionStage reactivePreInsertInMemoryValueGeneration(Object[] currentValues, Object entity, SharedSessionContractImplementor session) { + final EntityMetamodel entityMetamodel = entityPersister().getEntityMetamodel(); + CompletionStage stage = falseFuture(); + if ( entityMetamodel.hasPreInsertGeneratedValues() ) { + final Generator[] generators = entityMetamodel.getGenerators(); + for ( int i = 0; i < generators.length; i++ ) { + final int index = i; + final Generator generator = generators[i]; + if ( generator != null + && !generator.generatedOnExecution() + && generator.generatesOnInsert() ) { + final Object currentValue = currentValues[i]; + final BeforeExecutionGenerator beforeGenerator = (BeforeExecutionGenerator) generator; + stage = stage + .thenCompose( foundStateDependentGenerator -> generateValue( + session, + entity, + currentValue, + beforeGenerator, + INSERT + ) + .thenApply( generatedValue -> { + currentValues[index] = generatedValue; + entityPersister().setValue( entity, index, generatedValue ); + return foundStateDependentGenerator || beforeGenerator.generatedOnExecution(); + } ) + ); + } + } + } + + return stage; + } + + protected CompletionStage decomposeForReactiveInsert( + MutationExecutor mutationExecutor, + Object id, + Object[] values, + MutationOperationGroup mutationGroup, + boolean[] propertyInclusions, + TableInclusionChecker tableInclusionChecker, + SharedSessionContractImplementor session) { + final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings(); + mutationGroup.forEachOperation( (position, operation) -> { + final EntityTableMapping tableDetails = (EntityTableMapping) operation.getTableDetails(); + if ( tableInclusionChecker.include( tableDetails ) ) { + final int[] attributeIndexes = tableDetails.getAttributeIndexes(); + for ( final int attributeIndex : attributeIndexes ) { + if ( propertyInclusions[attributeIndex] ) { + final AttributeMapping mapping = entityPersister().getAttributeMappings().get( attributeIndex ); + decomposeAttribute( values[attributeIndex], session, jdbcValueBindings, mapping ); + } + } + } + } ); + + mutationGroup.forEachOperation( (position, jdbcOperation) -> { + if ( id == null ) { + assert entityPersister().getIdentityInsertDelegate() != null; + } + else { + final EntityTableMapping tableDetails = (EntityTableMapping) jdbcOperation.getTableDetails(); + breakDownJdbcValue( id, session, jdbcValueBindings, tableDetails ); + } + } ); + return voidFuture(); + } + + protected CompletionStage doDynamicInserts( + Object id, + Object[] values, + Object object, + SharedSessionContractImplementor session, + boolean forceIdentifierBinding, + boolean isIdentityInsert) { + final boolean[] insertability = getPropertiesToInsert( values ); + final MutationOperationGroup insertGroup = generateDynamicInsertSqlGroup( insertability, object, session, forceIdentifierBinding ); + final ReactiveMutationExecutor mutationExecutor = getReactiveMutationExecutor( session, insertGroup, true ); + + final InsertValuesAnalysis insertValuesAnalysis = new InsertValuesAnalysis( entityPersister(), values ); + final TableInclusionChecker tableInclusionChecker = getTableInclusionChecker( insertValuesAnalysis ); + return decomposeForReactiveInsert( mutationExecutor, id, values, insertGroup, insertability, tableInclusionChecker, session ) + .thenCompose( v -> mutationExecutor.executeReactive( + object, + insertValuesAnalysis, + tableInclusionChecker, + (statementDetails, affectedRowCount, batchPosition) -> { + statementDetails.getExpectation() + .verifyOutcome( + affectedRowCount, + statementDetails.getStatement(), + batchPosition, + statementDetails.getSqlString() + ); + return true; + }, + session, + isIdentityInsert, + entityPersister().getIdentifierColumnNames() + ) + .whenComplete( (o, t) -> mutationExecutor.release() ) ); + } + + protected CompletionStage doStaticInserts(Object id, Object[] values, Object object, SharedSessionContractImplementor session, boolean isIdentityInsert) { + final InsertValuesAnalysis insertValuesAnalysis = new InsertValuesAnalysis( entityPersister(), values ); + final TableInclusionChecker tableInclusionChecker = getTableInclusionChecker( insertValuesAnalysis ); + final ReactiveMutationExecutor mutationExecutor = getReactiveMutationExecutor( session, staticInsertGroup, false ); + + return decomposeForReactiveInsert( mutationExecutor, id, values, staticInsertGroup, entityPersister().getPropertyInsertability(), tableInclusionChecker, session ) + .thenCompose( v -> mutationExecutor.executeReactive( + object, + insertValuesAnalysis, + tableInclusionChecker, + (statementDetails, affectedRowCount, batchPosition) -> { + statementDetails.getExpectation().verifyOutcome( affectedRowCount, statementDetails.getStatement(), batchPosition, statementDetails.getSqlString() ); + return true; + }, + session, + isIdentityInsert, + entityPersister().getIdentifierColumnNames() + ) ) + .whenComplete( (generatedValues, throwable) -> mutationExecutor.release() ); + } + + + protected static TableInclusionChecker getTableInclusionChecker(InsertValuesAnalysis insertValuesAnalysis) { + return tableMapping -> !tableMapping.isOptional() || insertValuesAnalysis.hasNonNullBindings( tableMapping ); + } + + private ReactiveMutationExecutor getReactiveMutationExecutor(SharedSessionContractImplementor session, MutationOperationGroup operationGroup, boolean dynamicUpdate) { + return (ReactiveMutationExecutor) mutationExecutorService + .createExecutor( resolveBatchKeyAccess( dynamicUpdate, session ), operationGroup, session ); + } + + @Override + protected BatchKeyAccess resolveBatchKeyAccess(boolean dynamicUpdate, SharedSessionContractImplementor session) { + if ( !dynamicUpdate + && !entityPersister().optimisticLockStyle().isAllOrDirty() + && session.getTransactionCoordinator() != null +// && session.getTransactionCoordinator().isTransactionActive() + ) { + return this::getBatchKey; + } + + return NoBatchKeyAccess.INSTANCE; + } + + /* + * BEGIN Copied from InsertCoordinatorStandard + */ + @Override + public BasicBatchKey getBatchKey() { + return batchKey; + } + + @Override + @Deprecated + public MutationOperationGroup getStaticMutationOperationGroup() { + return staticInsertGroup; + } + + protected void decomposeAttribute( + Object value, + SharedSessionContractImplementor session, + JdbcValueBindings jdbcValueBindings, + AttributeMapping mapping) { + if ( !(mapping instanceof PluralAttributeMapping ) ) { + mapping.decompose( + value, + 0, + jdbcValueBindings, + null, + (valueIndex, bindings, noop, jdbcValue, selectableMapping) -> { + if ( selectableMapping.isInsertable() ) { + bindings.bindValue( + jdbcValue, + entityPersister().physicalTableNameForMutation( selectableMapping ), + selectableMapping.getSelectionExpression(), + ParameterUsage.SET + ); + } + }, + session + ); + } + } + + /** + * Transform the array of property indexes to an array of booleans, + * true when the property is insertable and non-null + */ + public boolean[] getPropertiesToInsert(Object[] fields) { + boolean[] notNull = new boolean[fields.length]; + boolean[] insertable = entityPersister().getPropertyInsertability(); + for ( int i = 0; i < fields.length; i++ ) { + notNull[i] = insertable[i] && fields[i] != null; + } + return notNull; + } + + protected MutationOperationGroup generateDynamicInsertSqlGroup( + boolean[] insertable, + Object object, + SharedSessionContractImplementor session, + boolean forceIdentifierBinding) { + final MutationGroupBuilder insertGroupBuilder = new MutationGroupBuilder( MutationType.INSERT, entityPersister() ); + entityPersister().forEachMutableTable( + (tableMapping) -> insertGroupBuilder.addTableDetailsBuilder( createTableInsertBuilder( tableMapping, forceIdentifierBinding ) ) + ); + applyTableInsertDetails( insertGroupBuilder, insertable, object, session, forceIdentifierBinding ); + return createOperationGroup( null, insertGroupBuilder.buildMutationGroup() ); + } + + public MutationOperationGroup generateStaticOperationGroup() { + final MutationGroupBuilder insertGroupBuilder = new MutationGroupBuilder( MutationType.INSERT, entityPersister() ); + entityPersister().forEachMutableTable( + (tableMapping) -> insertGroupBuilder.addTableDetailsBuilder( createTableInsertBuilder( tableMapping, false ) ) + ); + applyTableInsertDetails( insertGroupBuilder, entityPersister().getPropertyInsertability(), null, null, false ); + return createOperationGroup( null, insertGroupBuilder.buildMutationGroup() ); + } + + private TableMutationBuilder createTableInsertBuilder(EntityTableMapping tableMapping, boolean forceIdentifierBinding) { + final GeneratedValuesMutationDelegate delegate = entityPersister().getInsertDelegate(); + if ( tableMapping.isIdentifierTable() && delegate != null && !forceIdentifierBinding ) { + return delegate.createTableMutationBuilder( tableMapping.getInsertExpectation(), factory() ); + } + else { + return new TableInsertBuilderStandard( entityPersister(), tableMapping, factory() ); + } + } + + private void applyTableInsertDetails( + MutationGroupBuilder insertGroupBuilder, + boolean[] attributeInclusions, + Object object, + SharedSessionContractImplementor session, + boolean forceIdentifierBinding) { + final AttributeMappingsList attributeMappings = entityPersister().getAttributeMappings(); + + insertGroupBuilder.forEachTableMutationBuilder( (builder) -> { + final EntityTableMapping tableMapping = (EntityTableMapping) builder.getMutatingTable().getTableMapping(); + assert !tableMapping.isInverse(); + + // `attributeIndexes` represents the indexes (relative to `attributeMappings`) of + // the attributes mapped to the table + final int[] attributeIndexes = tableMapping.getAttributeIndexes(); + for ( int i = 0; i < attributeIndexes.length; i++ ) { + final int attributeIndex = attributeIndexes[ i ]; + final AttributeMapping attributeMapping = attributeMappings.get( attributeIndex ); + if ( attributeInclusions[attributeIndex] ) { + attributeMapping.forEachInsertable( insertGroupBuilder ); + } + else { + final Generator generator = attributeMapping.getGenerator(); + if ( isValueGenerated( generator ) ) { + if ( session != null && !generator.generatedOnExecution( object, session ) ) { + attributeInclusions[attributeIndex] = true; + attributeMapping.forEachInsertable( insertGroupBuilder ); + } + else if ( isValueGenerationInSql( generator, factory().getJdbcServices().getDialect() ) ) { + handleValueGeneration( attributeMapping, insertGroupBuilder, (OnExecutionGenerator) generator ); + } + } + } + } + } ); + + // add the discriminator + entityPersister().addDiscriminatorToInsertGroup( insertGroupBuilder ); + entityPersister().addSoftDeleteToInsertGroup( insertGroupBuilder ); + + // add the keys + insertGroupBuilder.forEachTableMutationBuilder( (tableMutationBuilder) -> { + final TableInsertBuilder tableInsertBuilder = (TableInsertBuilder) tableMutationBuilder; + final EntityTableMapping tableMapping = (EntityTableMapping) tableInsertBuilder.getMutatingTable().getTableMapping(); + if ( tableMapping.isIdentifierTable() && entityPersister().isIdentifierAssignedByInsert() && !forceIdentifierBinding ) { + assert entityPersister().getInsertDelegate() != null; + final OnExecutionGenerator generator = (OnExecutionGenerator) entityPersister().getGenerator(); + if ( generator.referenceColumnsInSql( dialect() ) ) { + final BasicEntityIdentifierMapping identifierMapping = (BasicEntityIdentifierMapping) entityPersister().getIdentifierMapping(); + final String[] columnValues = generator.getReferencedColumnValues( dialect ); + tableMapping.getKeyMapping().forEachKeyColumn( (i, column) -> tableInsertBuilder.addKeyColumn( + column.getColumnName(), + columnValues[i], + identifierMapping.getJdbcMapping() + ) ); + } + } + else { + tableMapping.getKeyMapping().forEachKeyColumn( tableInsertBuilder::addKeyColumn ); + } + } ); + } + + protected void breakDownJdbcValue( + Object id, + SharedSessionContractImplementor session, + JdbcValueBindings jdbcValueBindings, + EntityTableMapping tableDetails) { + final String tableName = tableDetails.getTableName(); + tableDetails.getKeyMapping().breakDownKeyJdbcValues( + id, + (jdbcValue, columnMapping) -> { + jdbcValueBindings.bindValue( + jdbcValue, + tableName, + columnMapping.getColumnName(), + ParameterUsage.SET + ); + }, + session + ); + } + + private static boolean isValueGenerated(Generator generator) { + return generator != null + && generator.generatesOnInsert() + && generator.generatedOnExecution(); + } + + private static boolean isValueGenerationInSql(Generator generator, Dialect dialect) { + assert isValueGenerated( generator ); + return ( (OnExecutionGenerator) generator ).referenceColumnsInSql(dialect); + } + + /* + * END Copied from InsertCoordinatorStandard + */ +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveScopedUpdateCoordinator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveScopedUpdateCoordinator.java index 61b12b9bc..91b025ee7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveScopedUpdateCoordinator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveScopedUpdateCoordinator.java @@ -8,6 +8,7 @@ import java.util.concurrent.CompletionStage; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValues; /** * Scoped to a single operation, so that we can keep @@ -18,7 +19,7 @@ */ public interface ReactiveScopedUpdateCoordinator { - CompletionStage coordinateReactiveUpdate( + CompletionStage reactiveUpdate( Object entity, Object id, Object rowId, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorNoOp.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorNoOp.java index 2828415e4..e46dcf1ac 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorNoOp.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorNoOp.java @@ -8,10 +8,11 @@ import java.util.concurrent.CompletionStage; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.mutation.UpdateCoordinatorNoOp; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; public class ReactiveUpdateCoordinatorNoOp extends UpdateCoordinatorNoOp implements ReactiveScopedUpdateCoordinator, ReactiveUpdateCoordinator { @@ -20,7 +21,7 @@ public ReactiveUpdateCoordinatorNoOp(AbstractEntityPersister entityPersister) { } @Override - public void coordinateUpdate( + public GeneratedValues update( Object entity, Object id, Object rowId, @@ -30,10 +31,11 @@ public void coordinateUpdate( int[] dirtyAttributeIndexes, boolean hasDirtyCollection, SharedSessionContractImplementor session) { + return null; } @Override - public CompletionStage coordinateReactiveUpdate( + public CompletionStage reactiveUpdate( Object entity, Object id, Object rowId, @@ -43,7 +45,7 @@ public CompletionStage coordinateReactiveUpdate( int[] dirtyAttributeIndexes, boolean hasDirtyCollection, SharedSessionContractImplementor session) { - return voidFuture(); + return nullFuture(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java index 0f20ad292..d3237cf72 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/mutation/ReactiveUpdateCoordinatorStandard.java @@ -9,6 +9,7 @@ import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import org.hibernate.engine.jdbc.batch.spi.BatchKey; import org.hibernate.engine.jdbc.mutation.ParameterUsage; @@ -18,6 +19,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.Generator; +import org.hibernate.generator.values.GeneratedValues; import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.persister.entity.AbstractEntityPersister; @@ -34,14 +36,15 @@ import static org.hibernate.internal.util.collections.ArrayHelper.trim; import static org.hibernate.reactive.persister.entity.mutation.GeneratorValueUtil.generateValue; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** - * Reactive version of {@link UpdateCoordinatorStandard}, but it cannot be share between multiple update operations. + * Reactive version of {@link UpdateCoordinatorStandard}, but it cannot be shared between multiple update operations. */ public class ReactiveUpdateCoordinatorStandard extends UpdateCoordinatorStandard implements ReactiveScopedUpdateCoordinator { - private CompletableFuture updateResultStage; + private CompletableFuture updateResultStage; public ReactiveUpdateCoordinatorStandard( AbstractEntityPersister entityPersister, @@ -54,12 +57,12 @@ public ReactiveUpdateCoordinatorStandard( } // Utility method to use method reference - private void complete(final Object o, final Throwable throwable) { + private void complete(final GeneratedValues generatedValues, final Throwable throwable) { if ( throwable != null ) { fail( throwable ); } else { - updateResultStage.complete( null ); + updateResultStage.complete( generatedValues ); } } @@ -68,7 +71,7 @@ private void fail(Throwable throwable) { } @Override - public CompletionStage coordinateReactiveUpdate( + public CompletionStage reactiveUpdate( Object entity, Object id, Object rowId, @@ -80,7 +83,7 @@ public CompletionStage coordinateReactiveUpdate( SharedSessionContractImplementor session) { final EntityVersionMapping versionMapping = entityPersister().getVersionMapping(); if ( versionMapping != null ) { - final boolean isForcedVersionIncrement = handlePotentialImplicitForcedVersionIncrement( + final Supplier generatedValuesAccess = handlePotentialImplicitForcedVersionIncrement( entity, id, values, @@ -89,8 +92,9 @@ public CompletionStage coordinateReactiveUpdate( session, versionMapping ); - if ( isForcedVersionIncrement ) { - return voidFuture(); + if ( generatedValuesAccess != null ) { + // FIXME: I think it needs to be reactive + return completedFuture( generatedValuesAccess.get() ); } } @@ -162,7 +166,7 @@ && entityPersister().hasLazyDirtyFields( dirtyAttributeIndexes ) ) { // doDynamicUpdate, doVersionUpdate, or doStaticUpdate will initialize the stage, // if an update is necessary. // Otherwise, updateResultStage could be null. - return updateResultStage != null ? updateResultStage : voidFuture(); + return updateResultStage != null ? updateResultStage : nullFuture(); }); } @@ -212,7 +216,7 @@ private CompletionStage reactivePreUpdateInMemoryValueGeneration( } @Override - protected void doVersionUpdate( + protected GeneratedValues doVersionUpdate( Object entity, Object id, Object version, @@ -270,6 +274,7 @@ protected void doVersionUpdate( ) .whenComplete( (o, t) -> mutationExecutor.release() ) .whenComplete( this::complete ); + return null; } private ReactiveMutationExecutor mutationExecutor( @@ -282,7 +287,7 @@ private ReactiveMutationExecutor mutationExecutor( } @Override - protected void doDynamicUpdate( + protected GeneratedValues doDynamicUpdate( Object entity, Object id, Object rowId, @@ -349,10 +354,11 @@ protected void doDynamicUpdate( ) .whenComplete( (o, throwable) -> mutationExecutor.release() ) .whenComplete( this::complete ); + return null; } @Override - protected void doStaticUpdate( + protected GeneratedValues doStaticUpdate( Object entity, Object id, Object rowId, @@ -361,7 +367,7 @@ protected void doStaticUpdate( UpdateValuesAnalysisImpl valuesAnalysis, SharedSessionContractImplementor session) { this.updateResultStage = new CompletableFuture<>(); - final MutationOperationGroup staticUpdateGroup = getStaticUpdateGroup(); + final MutationOperationGroup staticUpdateGroup = getStaticMutationOperationGroup(); final ReactiveMutationExecutor mutationExecutor = mutationExecutor( session, staticUpdateGroup ); decomposeForUpdate( @@ -386,5 +392,6 @@ protected void doStaticUpdate( ) .whenComplete( (o, throwable) -> mutationExecutor.release() ) .whenComplete( this::complete ); + return null; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java index 8cca45b43..0d5234129 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java @@ -138,22 +138,22 @@ public CompletionStage executeOutsideTransaction(String sql) { } public CompletionStage update(String sql) { - return hasBatch() ? - executeBatch().thenCompose( v -> delegate.update( sql ) ) : - delegate.update( sql ); + return hasBatch() + ? executeBatch().thenCompose( v -> delegate.update( sql ) ) + : delegate.update( sql ); } @Override public CompletionStage update(String sql, Object[] paramValues) { - return hasBatch() ? - executeBatch().thenCompose( v -> delegate.update( sql, paramValues ) ) : - delegate.update( sql, paramValues ); + return hasBatch() + ? executeBatch().thenCompose( v -> delegate.update( sql, paramValues ) ) + : delegate.update( sql, paramValues ); } public CompletionStage update(String sql, List paramValues) { - return hasBatch() ? - executeBatch().thenCompose( v -> delegate.update( sql, paramValues ) ) : - delegate.update( sql, paramValues ); + return hasBatch() + ? executeBatch().thenCompose( v -> delegate.update( sql, paramValues ) ) + : delegate.update( sql, paramValues ); } public CompletionStage insertAndSelectIdentifier(String sql, Object[] paramValues, Class idClass, String idColumnName) { @@ -162,22 +162,36 @@ public CompletionStage insertAndSelectIdentifier(String sql, Object[] par : delegate.insertAndSelectIdentifier( sql, paramValues, idClass, idColumnName ); } + @Override + public CompletionStage insertAndSelectIdentifierAsResultSet( + String sql, + Object[] paramValues, + Class idClass, + String idColumnName) { + return insertAndSelectIdentifier( sql, paramValues, idClass, idColumnName ) + .thenApply( this::convertToResultSet ); + } + + private ResultSet convertToResultSet(Object o) { + return null; + } + public CompletionStage select(String sql) { - return hasBatch() ? - executeBatch().thenCompose( v -> delegate.select( sql ) ) : - delegate.select( sql ); + return hasBatch() + ? executeBatch().thenCompose( v -> delegate.select( sql ) ) + : delegate.select( sql ); } public CompletionStage select(String sql, Object[] paramValues) { - return hasBatch() ? - executeBatch().thenCompose( v -> delegate.select( sql, paramValues ) ) : - delegate.select( sql, paramValues ); + return hasBatch() + ? executeBatch().thenCompose( v -> delegate.select( sql, paramValues ) ) + : delegate.select( sql, paramValues ); } public CompletionStage selectJdbc(String sql, Object[] paramValues) { - return hasBatch() ? - executeBatch().thenCompose( v -> delegate.selectJdbc( sql, paramValues ) ) : - delegate.selectJdbc( sql, paramValues ); + return hasBatch() + ? executeBatch().thenCompose( v -> delegate.selectJdbc( sql, paramValues ) ) + : delegate.selectJdbc( sql, paramValues ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnection.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnection.java index 4b2007798..5d4886577 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnection.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnection.java @@ -5,8 +5,6 @@ */ package org.hibernate.reactive.pool; -import org.hibernate.Incubating; - import java.sql.ResultSet; import java.util.Iterator; import java.util.List; @@ -80,6 +78,7 @@ interface Expectation { CompletionStage selectJdbcOutsideTransaction(String sql, Object[] paramValues); CompletionStage insertAndSelectIdentifier(String sql, Object[] paramValues, Class idClass, String idColumnName); + CompletionStage insertAndSelectIdentifierAsResultSet(String sql, Object[] paramValues, Class idClass, String idColumnName); CompletionStage selectIdentifier(String sql, Object[] paramValues, Class idClass); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java index 390d81b83..f21cbf602 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java @@ -214,6 +214,26 @@ public CompletionStage insertAndSelectIdentifier(String sql, Object[] par return insertAndSelectIdentifier( sql, Tuple.wrap( paramValues ), idClass, idColumnName ); } + @Override + public CompletionStage insertAndSelectIdentifierAsResultSet( + String sql, + Object[] parameters, + Class idClass, + String idColumnName) { + // Oracle needs to know the name of the column id in advance, this shouldn't affect the other dbs + JsonObject options = new JsonObject() + .put( "autoGeneratedKeysIndexes", new JsonArray().add( idColumnName ) ); + + translateNulls( parameters ); + return preparedQuery( sql, Tuple.wrap( parameters ), new PrepareOptions( options ) ) + .thenApply( rows -> { + RowIterator iterator = rows.iterator(); + return iterator.hasNext() + ? new ResultSetAdaptor( rows ) + : getLastInsertedIdAsResultSet( rows, idClass, idColumnName ); + } ); + } + public CompletionStage insertAndSelectIdentifier(String sql, Tuple parameters, Class idClass, String idColumnName) { // Oracle needs to know the name of the column id in advance, this shouldn't affect the other dbs JsonObject options = new JsonObject() @@ -327,6 +347,24 @@ private static T getLastInsertedId(RowSet rows, Class idClass, Strin return null; } + private static ResultSet getLastInsertedIdAsResultSet(RowSet rows, Class idClass, String idColumnName) { + final Long mySqlId = rows.property( MYSQL_LAST_INSERTED_ID ); + if ( mySqlId != null ) { + if ( Long.class.equals( idClass ) ) { + return new ResultSetAdaptor( rows, List.of( mySqlId ), idColumnName, Long.class ); + } + if ( Integer.class.equals( idClass ) ) { + return new ResultSetAdaptor( rows, List.of( mySqlId.intValue() ), idColumnName, Integer.class ); + } + throw LOG.nativelyGeneratedValueMustBeLong(); + } + final Row oracleKeys = rows.property( ORACLE_GENERATED_KEYS ); + if ( oracleKeys != null ) { + return new ResultSetAdaptor( rows, ORACLE_GENERATED_KEYS, idColumnName, idClass ); + } + return null; + } + private void setTransaction(Transaction tx) { transaction = tx; } 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 9054d60a6..69ebe52a1 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 @@ -250,7 +250,7 @@ public ReactiveSelectQueryPlan resolveSelectReactiveQueryPlan() { private ReactiveSelectQueryPlan buildSelectQueryPlan() { final SqmSelectStatement[] concreteSqmStatements = QuerySplitter - .split( (SqmSelectStatement) getSqmStatement(), getSession().getFactory() ); + .split( (SqmSelectStatement) getSqmStatement() ); return concreteSqmStatements.length > 1 ? buildAggregatedSelectQueryPlan( concreteSqmStatements ) 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 950fd917a..3c23e6c76 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 @@ -234,9 +234,10 @@ private ReactiveSelectQueryPlan resolveSelectReactiveQueryPlan() { } } - private ReactiveSelectQueryPlan buildSelectQueryPlan() { + @Override + protected ReactiveSelectQueryPlan buildSelectQueryPlan() { final SqmSelectStatement[] concreteSqmStatements = - QuerySplitter.split( (SqmSelectStatement) getSqmStatement(), getSession().getFactory() ); + QuerySplitter.split( (SqmSelectStatement) getSqmStatement() ); return concreteSqmStatements.length > 1 ? buildAggregatedSelectQueryPlan( concreteSqmStatements ) @@ -359,7 +360,7 @@ private ReactiveNonSelectQueryPlan buildNonSelectQueryPlan() { @SuppressWarnings({ "unchecked", "rawtypes" }) private ReactiveNonSelectQueryPlan buildDeleteQueryPlan() { final SqmDeleteStatement[] concreteSqmStatements = QuerySplitter - .split( (SqmDeleteStatement) getSqmStatement(), getSessionFactory() ); + .split( (SqmDeleteStatement) getSqmStatement() ); return concreteSqmStatements.length > 1 ? buildAggregatedDeleteQueryPlan( concreteSqmStatements ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java index 3b8fe2b2c..d1ea9026f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.IdentityHashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -28,10 +27,9 @@ import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler; import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; -import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.expression.SqmStar; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; @@ -53,7 +51,6 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.internal.SqlSelectionImpl; -import org.hibernate.type.spi.TypeConfiguration; /** * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler @@ -109,15 +106,7 @@ default CompletionStage reactiveExecute(DomainQueryExecutionContext exe parameterResolutions = new IdentityHashMap<>(); } - //noinspection rawtypes - final Map paramTypeResolutions = new LinkedHashMap<>(); - - final Predicate restriction = sqmConverter.visitWhereClause( - sqmMutationStatement.getWhereClause(), - columnReference -> { - }, - (sqmParam, mappingType, jdbcParameters) -> paramTypeResolutions.put( sqmParam, mappingType ) - ); + final Predicate restriction = sqmConverter.visitWhereClause( sqmMutationStatement.getWhereClause() ); sqmConverter.pruneTableGroupJoins(); final CteStatement idSelectCte = new CteStatement( @@ -172,7 +161,12 @@ default CompletionStage reactiveExecute(DomainQueryExecutionContext exe SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), sqmConverter ), factory.getRuntimeMetamodels().getMappingMetamodel(), navigablePath -> sqmConverter.getMutatingTableGroup(), - paramTypeResolutions::get, + new SqmParameterMappingModelResolutionAccess() { + @Override @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter ); + } + }, executionContext.getSession() ); final LockOptions lockOptions = executionContext.getQueryOptions().getLockOptions(); @@ -198,18 +192,5 @@ default CompletionStage reactiveExecute(DomainQueryExecutionContext exe ); } - /** - * Copy and paste of the one in the super class - */ - default Expression createCountStar( - SessionFactoryImplementor factory, - MultiTableSqmMutationConverter sqmConverter) { - final SqmExpression arg = new SqmStar( factory.getNodeBuilder() ); - final TypeConfiguration typeConfiguration = factory.getJpaMetamodel().getTypeConfiguration(); - return factory.getQueryEngine() - .getSqmFunctionRegistry() - .findFunctionDescriptor( "count" ) - .generateSqmExpression( arg, null, factory.getQueryEngine() ) - .convertToSqlAst( sqmConverter ); - } + Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java index cba7f9cec..a1f555253 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java @@ -22,6 +22,7 @@ import org.hibernate.sql.ast.tree.cte.CteContainer; import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.JdbcParameter; /** @@ -54,4 +55,10 @@ public void addDmlCtes( public int execute(DomainQueryExecutionContext executionContext) { throw LOG.nonReactiveMethodCall( "reactiveExecute" ); } + + @Override + public Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter) { + return super.createCountStar( factory, sqmConverter ); + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java index 867a6ac3f..0730ff33e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java @@ -22,6 +22,7 @@ import org.hibernate.sql.ast.tree.cte.CteContainer; import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.JdbcParameter; /** @@ -54,4 +55,9 @@ public void addDmlCtes( public int execute(DomainQueryExecutionContext executionContext) { throw LOG.nonReactiveMethodCall( "reactiveExecute" ); } + + @Override + public Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter) { + return super.createCountStar( factory, sqmConverter ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java index 1f5ccff36..3644af942 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java @@ -12,18 +12,17 @@ import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.InsertExecutionDelegate; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTableBasedInsertHandler; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.insert.ConflictClause; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -45,9 +44,8 @@ public ReactiveInsertExecutionDelegate( Map tableReferenceByAlias, List assignments, InsertSelectStatement insertStatement, - Map, List>> parameterResolutions, + ConflictClause conflictClause, JdbcParameter sessionUidParameter, - Map, MappingModelExpressible> paramTypeResolutions, DomainQueryExecutionContext executionContext) { super( sqmInsert, @@ -60,15 +58,15 @@ public ReactiveInsertExecutionDelegate( tableReferenceByAlias, assignments, insertStatement, - parameterResolutions, + conflictClause, sessionUidParameter, - paramTypeResolutions, executionContext ); } @Override public CompletionStage reactiveExecute(ExecutionContext executionContext) { + // FIXME: Why is this null? return null; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java index bc246c682..61a866756 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java @@ -7,9 +7,6 @@ import java.lang.invoke.MethodHandles; import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -23,7 +20,6 @@ import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.MutableBoolean; import org.hibernate.internal.util.MutableInteger; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; @@ -41,6 +37,7 @@ import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; +import org.hibernate.query.sqm.mutation.internal.temptable.ColumnReferenceCheckingSqlAstWalker; import org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithoutIdTableHelper; import org.hibernate.query.sqm.mutation.internal.temptable.RestrictedDeleteExecutionDelegate; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; @@ -140,47 +137,16 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec ); assert hierarchyRootTableReference != null; - final Map, List>> parameterResolutions; - final Map, MappingModelExpressible> paramTypeResolutions; - - if ( domainParameterXref.getSqmParameterCount() == 0 ) { - parameterResolutions = Collections.emptyMap(); - paramTypeResolutions = Collections.emptyMap(); - } - else { - parameterResolutions = new IdentityHashMap<>(); - paramTypeResolutions = new LinkedHashMap<>(); - } - // Use the converter to interpret the where-clause. We do this for 2 reasons: // 1) the resolved Predicate is ultimately the base for applying restriction to the deletes // 2) we also inspect each ColumnReference that is part of the where-clause to see which // table it comes from. If all the referenced columns (if any at all) are from the root table // we can perform all the deletes without using an id-table - final MutableBoolean needsIdTableWrapper = new MutableBoolean( false ); - final Predicate specifiedRestriction = converter.visitWhereClause( - sqmDelete.getWhereClause(), - columnReference -> { - if ( !hierarchyRootTableReference.getIdentificationVariable() - .equals( columnReference.getQualifier() ) ) { - needsIdTableWrapper.setValue( true ); - } - }, - (sqmParameter, mappingType, jdbcParameters) -> { - parameterResolutions.computeIfAbsent( - sqmParameter, - k -> new ArrayList<>( 1 ) - ).add( jdbcParameters ); - paramTypeResolutions.put( sqmParameter, mappingType ); - } - ); + final Predicate specifiedRestriction = converter.visitWhereClause( sqmDelete.getWhereClause() ); final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); entityDescriptor.applyBaseRestrictions( - (filterPredicate) -> { - needsIdTableWrapper.setValue( true ); - predicateCollector.applyPredicate( filterPredicate ); - }, + predicateCollector, deletingTableGroup, true, executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), @@ -189,12 +155,18 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec ); converter.pruneTableGroupJoins(); + final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( + hierarchyRootTableReference.getIdentificationVariable() + ); + if ( predicateCollector.getPredicate() != null ) { + predicateCollector.getPredicate().accept( walker ); + } // We need an id table if we want to delete from an intermediate table to avoid FK violations // The intermediate table has a FK to the root table, so we can't delete from the root table first // Deleting from the intermediate table first also isn't possible, // because that is the source for deletion in other tables, hence we need an id table - final boolean needsIdTable = needsIdTableWrapper.getValue() + final boolean needsIdTable = !walker.isAllColumnReferencesFromIdentificationVariable() || entityDescriptor != entityDescriptor.getRootEntityDescriptor(); final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( @@ -204,8 +176,8 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec return executeWithIdTable( predicateCollector.getPredicate(), deletingTableGroup, - parameterResolutions, - paramTypeResolutions, + converter.getJdbcParamsBySqmParam(), + converter.getSqmParameterMappingModelExpressibleResolutions(), executionContextAdapter ); } @@ -213,8 +185,8 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec return executeWithoutIdTable( predicateCollector.getPredicate(), deletingTableGroup, - parameterResolutions, - paramTypeResolutions, + converter.getJdbcParamsBySqmParam(), + converter.getSqmParameterMappingModelExpressibleResolutions(), converter.getSqlExpressionResolver(), executionContextAdapter ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java index 3889b7d6e..b27340418 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java @@ -14,14 +14,12 @@ import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedInsertHandler; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; @@ -30,6 +28,7 @@ import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.insert.ConflictClause; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; import org.hibernate.sql.ast.tree.update.Assignment; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -89,9 +88,8 @@ protected ExecutionDelegate buildExecutionDelegate( Map tableReferenceByAlias, List assignments, InsertSelectStatement insertStatement, - Map, List>> parameterResolutions, + ConflictClause conflictClause, JdbcParameter sessionUidParameter, - Map, MappingModelExpressible> paramTypeResolutions, DomainQueryExecutionContext executionContext) { return new ReactiveInsertExecutionDelegate( sqmInsert, @@ -104,9 +102,8 @@ protected ExecutionDelegate buildExecutionDelegate( tableReferenceByAlias, assignments, insertStatement, - parameterResolutions, + conflictClause, sessionUidParameter, - paramTypeResolutions, executionContext ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java index db2dc3320..a247c8d7d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java @@ -14,19 +14,16 @@ import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedUpdateHandler; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.Predicate; @@ -90,8 +87,6 @@ protected ReactiveUpdateExecutionDelegate buildExecutionDelegate( Map tableReferenceByAlias, List assignments, Predicate suppliedPredicate, - Map, List>> parameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, DomainQueryExecutionContext executionContext) { return new ReactiveUpdateExecutionDelegate( sqmConverter, @@ -103,8 +98,6 @@ protected ReactiveUpdateExecutionDelegate buildExecutionDelegate( tableReferenceByAlias, assignments, suppliedPredicate, - parameterResolutions, - paramTypeResolutions, executionContext ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java index 1e3092e96..f70e5c794 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java @@ -19,14 +19,12 @@ import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.AfterUseAction; import org.hibernate.query.sqm.mutation.internal.temptable.UpdateExecutionDelegate; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; @@ -34,7 +32,6 @@ import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -71,8 +68,6 @@ public ReactiveUpdateExecutionDelegate( Map tableReferenceByAlias, List assignments, Predicate suppliedPredicate, - Map, List>> parameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, DomainQueryExecutionContext executionContext) { super( sqmConverter, @@ -84,8 +79,6 @@ public ReactiveUpdateExecutionDelegate( tableReferenceByAlias, assignments, suppliedPredicate, - parameterResolutions, - paramTypeResolutions, executionContext ); } 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 fd7a45f9b..587fd95c7 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 @@ -696,7 +696,6 @@ public CompletionStage reactiveInitializeCollection(PersistentCollection { delayedAfterCompletion(); - if ( e instanceof MappingException ) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index ea67e89e1..06faba07f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -75,6 +75,7 @@ import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; import org.hibernate.reactive.session.ReactiveSqmQueryImplementor; import org.hibernate.reactive.session.ReactiveStatelessSession; +import org.hibernate.reactive.util.impl.CompletionStages; import jakarta.persistence.EntityGraph; import jakarta.persistence.Tuple; @@ -321,7 +322,9 @@ private CompletionStage executeReactiveUpdate(Object entity) { else { oldVersion = null; } - return persister.updateReactive( id, state, null, false, null, oldVersion, entity, null, this ); + return persister + .updateReactive( id, state, null, false, null, oldVersion, entity, null, this ) + .thenCompose( CompletionStages::voidFuture ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/ReactiveStandardMutationExecutorService.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/ReactiveStandardMutationExecutorService.java index 54dc193a2..c02fc0551 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/ReactiveStandardMutationExecutorService.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/ReactiveStandardMutationExecutorService.java @@ -13,17 +13,14 @@ import org.hibernate.engine.jdbc.mutation.spi.BatchKeyAccess; import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; import org.hibernate.internal.util.config.ConfigurationHelper; -import org.hibernate.reactive.engine.jdbc.mutation.internal.ReactiveMutationExecutorPostInsert; -import org.hibernate.reactive.engine.jdbc.mutation.internal.ReactiveMutationExecutorPostInsertSingleTable; import org.hibernate.reactive.engine.jdbc.mutation.internal.ReactiveMutationExecutorSingleBatched; import org.hibernate.reactive.engine.jdbc.mutation.internal.ReactiveMutationExecutorSingleNonBatched; import org.hibernate.reactive.engine.jdbc.mutation.internal.ReactiveMutationExecutorSingleSelfExecuting; import org.hibernate.reactive.engine.jdbc.mutation.internal.ReactiveMutationExecutorStandard; -import org.hibernate.sql.model.EntityMutationOperationGroup; import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.MutationOperationGroup; -import org.hibernate.sql.model.MutationType; import org.hibernate.sql.model.PreparableMutationOperation; import org.hibernate.sql.model.SelfExecutingUpdateOperation; @@ -56,21 +53,7 @@ public MutationExecutor createExecutor( ? globalBatchSize : sessionBatchSize; - final int numberOfOperations = operationGroup.getNumberOfOperations(); - final MutationType mutationType = operationGroup.getMutationType(); - final EntityMutationOperationGroup entityMutationOperationGroup = operationGroup.asEntityMutationOperationGroup(); - - if ( mutationType == MutationType.INSERT - && entityMutationOperationGroup != null - && entityMutationOperationGroup.getMutationTarget().getIdentityInsertDelegate() != null ) { - if ( numberOfOperations > 1 ) { - return new ReactiveMutationExecutorPostInsert( entityMutationOperationGroup, session ); - } - - return new ReactiveMutationExecutorPostInsertSingleTable( entityMutationOperationGroup, session ); - } - - if ( numberOfOperations == 1 ) { + if ( operationGroup.getNumberOfOperations() == 1 ) { final MutationOperation singleOperation = operationGroup.getSingleOperation(); if ( singleOperation instanceof SelfExecutingUpdateOperation ) { return new ReactiveMutationExecutorSingleSelfExecuting( (SelfExecutingUpdateOperation) singleOperation, session ); @@ -82,9 +65,16 @@ public MutationExecutor createExecutor( return new ReactiveMutationExecutorSingleBatched( jdbcOperation, batchKey, batchSizeToUse, session ); } - return new ReactiveMutationExecutorSingleNonBatched( jdbcOperation, session ); + return new ReactiveMutationExecutorSingleNonBatched( jdbcOperation, generatedValuesDelegate( operationGroup ), session ); } return new ReactiveMutationExecutorStandard( operationGroup, batchKeySupplier, batchSizeToUse, session ); } + + private static GeneratedValuesMutationDelegate generatedValuesDelegate(MutationOperationGroup operationGroup) { + GeneratedValuesMutationDelegate generatedValuesMutationDelegate = operationGroup.asEntityMutationOperationGroup() != null + ? operationGroup.asEntityMutationOperationGroup().getMutationDelegate() + : null; + return generatedValuesMutationDelegate; + } } 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 901e25c41..51e75108e 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 @@ -6,7 +6,6 @@ package org.hibernate.reactive.sql.exec.internal; import java.io.Serializable; -import java.lang.invoke.MethodHandles; import java.sql.PreparedStatement; import java.util.Collection; import java.util.List; @@ -25,8 +24,6 @@ import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.TupleTransformer; import org.hibernate.reactive.engine.impl.ReactivePersistenceContextAdapter; -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.exec.spi.ReactiveSelectExecutor; import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; @@ -60,8 +57,6 @@ */ public class StandardReactiveSelectExecutor implements ReactiveSelectExecutor { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public static final StandardReactiveSelectExecutor INSTANCE = new StandardReactiveSelectExecutor(); private StandardReactiveSelectExecutor() { @@ -318,7 +313,7 @@ public CompletionStage resolveJdbcValuesSource(String q if ( queryResultsCacheKey == null ) { return mappingProducer .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, null, queryIdentifier, executionContext.getQueryOptions(), jdbcValuesMapping, null, executionContext ) ); + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, queryResultsCacheKey, queryIdentifier, executionContext.getQueryOptions(), jdbcValuesMapping, null, executionContext ) ); } else { // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata 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 fbb19507d..4323e04bd 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 @@ -7,23 +7,30 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Arrays; +import java.util.BitSet; import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; +import org.hibernate.JDBCException; +import org.hibernate.QueryTimeoutException; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.exception.DataException; +import org.hibernate.exception.LockTimeoutException; import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.results.internal.ReactiveResultSetAccess; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.exec.ExecutionException; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.results.caching.QueryCachePutManager; -import org.hibernate.sql.results.caching.internal.QueryCachePutManagerDisabledImpl; import org.hibernate.sql.results.caching.internal.QueryCachePutManagerEnabledImpl; +import org.hibernate.sql.results.graph.DomainResult; 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; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; @@ -40,7 +47,13 @@ public class ReactiveValuesResultSet { private final JdbcValuesMapping valuesMapping; private final ExecutionContext executionContext; private final SqlSelection[] sqlSelections; + private final BitSet initializedIndexes; private final Object[] currentRowJdbcValues; + private final int[] valueIndexesToCacheIndexes; + // Is only meaningful if valueIndexesToCacheIndexes is not null + // 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; public ReactiveValuesResultSet( ReactiveResultSetAccess resultSetAccess, @@ -54,8 +67,53 @@ public ReactiveValuesResultSet( this.resultSetAccess = resultSetAccess; this.valuesMapping = valuesMapping; this.executionContext = executionContext; - this.sqlSelections = valuesMapping.getSqlSelections().toArray( new SqlSelection[0] ); - this.currentRowJdbcValues = new Object[ valuesMapping.getRowSize() ]; + + final int rowSize = valuesMapping.getRowSize(); + this.sqlSelections = new SqlSelection[rowSize]; + for ( SqlSelection selection : valuesMapping.getSqlSelections() ) { + int valuesArrayPosition = selection.getValuesArrayPosition(); + this.sqlSelections[valuesArrayPosition] = selection; + } + this.initializedIndexes = new BitSet( rowSize ); + this.currentRowJdbcValues = new Object[rowSize]; + if ( queryCachePutManager == null ) { + this.valueIndexesToCacheIndexes = null; + this.rowToCacheSize = -1; + } + else { + final BitSet valueIndexesToCache = new BitSet( rowSize ); + for ( DomainResult domainResult : valuesMapping.getDomainResults() ) { + domainResult.collectValueIndexesToCache( valueIndexesToCache ); + } + if ( valueIndexesToCache.nextClearBit( 0 ) == -1 ) { + this.valueIndexesToCacheIndexes = null; + this.rowToCacheSize = -1; + } + else { + final int[] valueIndexesToCacheIndexes = new int[rowSize]; + int cacheIndex = 0; + for ( int i = 0; i < valueIndexesToCacheIndexes.length; i++ ) { + if ( valueIndexesToCache.get( i ) ) { + valueIndexesToCacheIndexes[i] = cacheIndex++; + } + else { + valueIndexesToCacheIndexes[i] = -1; + } + } + + this.valueIndexesToCacheIndexes = valueIndexesToCacheIndexes; + if ( cacheIndex == 1 ) { + // Special case. Set the rowToCacheSize to the inverted index of the single element to cache + for ( int i = 0; i < valueIndexesToCacheIndexes.length; i++ ) { + if ( valueIndexesToCacheIndexes[i] != -1 ) { + cacheIndex = -i; + break; + } + } + } + this.rowToCacheSize = cacheIndex; + } + } } private static QueryCachePutManager resolveQueryCachePutManager( @@ -64,24 +122,23 @@ private static QueryCachePutManager resolveQueryCachePutManager( QueryKey queryCacheKey, String queryIdentifier, JdbcValuesMetadata metadataForCache) { - if ( queryCacheKey == null ) { - return QueryCachePutManagerDisabledImpl.INSTANCE; + if ( queryCacheKey != null ) { + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final QueryResultsCache queryCache = factory.getCache() + .getQueryResultsCache( queryOptions.getResultCacheRegionName() ); + return new QueryCachePutManagerEnabledImpl( + queryCache, + factory.getStatistics(), + queryCacheKey, + queryIdentifier, + metadataForCache + ); } - - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final QueryResultsCache queryCache = factory.getCache() - .getQueryResultsCache( queryOptions.getResultCacheRegionName() ); - return new QueryCachePutManagerEnabledImpl( queryCache, factory.getStatistics(), queryCacheKey, queryIdentifier, metadataForCache ); + return null; } public final CompletionStage next() { - return processNext() - .thenApply( hadRow -> { - if ( hadRow ) { - queryCachePutManager.registerJdbcRow( getCurrentRowValuesArray() ); - } - return hadRow; - } ); + return processNext(); } protected final CompletionStage processNext() { @@ -93,18 +150,26 @@ protected final CompletionStage processNext() { private CompletionStage doNext(ResultSet resultSet) { try { - return completedFuture( resultSet.next() ); + boolean next = resultSet.next(); + return completedFuture( next ); } catch (SQLException e) { return failedFuture( makeExecutionException( "Error advancing (next) ResultSet position", e ) ); } } + // Copied from JdbcValuesResultSetImpl#makeExecutionException, not sure if we can actually have a JDBCException private ExecutionException makeExecutionException(String message, SQLException cause) { - return new ExecutionException( - message, - executionContext.getSession().getJdbcServices().getSqlExceptionHelper().convert( cause, message ) - ); + final JDBCException jdbcException = executionContext.getSession().getJdbcServices() + .getSqlExceptionHelper().convert( cause, message ); + if ( jdbcException instanceof QueryTimeoutException + || jdbcException instanceof DataException + || jdbcException instanceof LockTimeoutException ) { + // So far, the exception helper threw these exceptions more or less directly during conversion, + // so to retain the same behavior, we throw that directly now as well instead of wrapping it + throw jdbcException; + } + return new ExecutionException( message + " [" + cause.getMessage() + "]", jdbcException ); } public JdbcValuesMapping getValuesMapping() { @@ -116,7 +181,37 @@ public Object[] getCurrentRowValuesArray() { } public void finishUp(SharedSessionContractImplementor session) { - queryCachePutManager.finishUp( session ); + if ( queryCachePutManager != null ) { + queryCachePutManager.finishUp( session ); + } + resultSetAccess.release(); + } + + public void finishRowProcessing(RowProcessingState rowProcessingState, boolean wasAdded) { + if ( queryCachePutManager != null ) { + final Object objectToCache; + if ( valueIndexesToCacheIndexes == null ) { + objectToCache = Arrays.copyOf( currentRowJdbcValues, currentRowJdbcValues.length ); + } + else if ( rowToCacheSize < 1 ) { + if ( !wasAdded ) { + // skip adding duplicate objects + return; + } + objectToCache = currentRowJdbcValues[-rowToCacheSize]; + } + else { + final Object[] rowToCache = new Object[rowToCacheSize]; + for ( int i = 0; i < currentRowJdbcValues.length; i++ ) { + final int cacheIndex = valueIndexesToCacheIndexes[i]; + if ( cacheIndex != -1 ) { + rowToCache[cacheIndex] = initializedIndexes.get( i ) ? currentRowJdbcValues[i] : null; + } + } + objectToCache = rowToCache; + } + queryCachePutManager.registerJdbcRow( objectToCache ); + } } @FunctionalInterface @@ -140,7 +235,7 @@ private CompletionStage readCurrentRowValues(boolean hasResults) { final SharedSessionContractImplementor session = executionContext.getSession(); for ( final SqlSelection sqlSelection : sqlSelections ) { try { - currentRowJdbcValues[ sqlSelection.getValuesArrayPosition() ] = sqlSelection + currentRowJdbcValues[sqlSelection.getValuesArrayPosition()] = sqlSelection .getJdbcValueExtractor() .extract( resultSet, sqlSelection.getJdbcResultSetIndex(), session ); } 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 6a78a9ac8..84bef8045 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 @@ -8,30 +8,14 @@ import java.util.concurrent.CompletionStage; import org.hibernate.Incubating; -import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; -import static java.lang.invoke.MethodHandles.lookup; -import static org.hibernate.reactive.logging.impl.LoggerFactory.make; - @Incubating public interface ReactiveDomainResultsAssembler extends DomainResultAssembler { - @Override - default J assemble(RowProcessingState rowProcessingState) { - throw make( Log.class, lookup() ) - .nonReactiveMethodCall( "reactiveAssemble" ); - } - - @Override - default J assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { - throw make( Log.class, lookup() ) - .nonReactiveMethodCall( "reactiveAssemble" ); - } - CompletionStage reactiveAssemble( ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveEagerCollectionFetch.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveEagerCollectionFetch.java index e5f69bf0c..3800d5fe4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveEagerCollectionFetch.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveEagerCollectionFetch.java @@ -23,9 +23,10 @@ public ReactiveEagerCollectionFetch( NavigablePath fetchedPath, PluralAttributeMapping fetchedAttribute, TableGroup collectionTableGroup, + boolean needsCollectionKeyResult, FetchParent fetchParent, DomainResultCreationState creationState) { - super( fetchedPath, fetchedAttribute, collectionTableGroup, fetchParent, creationState ); + super( fetchedPath, fetchedAttribute, collectionTableGroup,needsCollectionKeyResult, fetchParent, creationState ); } @Override 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 index 59466966c..82aaa435d 100644 --- 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 @@ -11,12 +11,10 @@ import org.hibernate.LockMode; 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.PersistenceContext; import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; -import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; @@ -27,6 +25,7 @@ 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; @@ -37,7 +36,6 @@ 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.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -53,6 +51,27 @@ protected ReactiveAbstractEntityInitializer( 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, @@ -60,6 +79,7 @@ protected ReactiveAbstractEntityInitializer( identifierFetch, discriminatorFetch, rowIdResult, + parentAccess, creationState ); } @@ -82,44 +102,14 @@ public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState @Override public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( !isMissing() && !isEntityInitialized() ) { - final LazyInitializer lazyInitializer = extractLazyInitializer( getEntityInstance() ); - return voidFuture() - .thenCompose( v -> { - if ( lazyInitializer != null ) { - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - final EntityHolder holder = persistenceContext.getEntityHolder( getEntityKey() ); - Object instance = holder.getEntity(); - assert instance != null : "The real entity instance must be resolved in the `resolveInstance()` phase"; - if ( holder.getEntityInitializer() == this ) { - return initializeEntity( instance, rowProcessingState ) - .thenAccept( vv -> { - lazyInitializer.setImplementation( instance ); - setEntityInstanceForNotify( instance ); - } ); - } - return voidFuture().thenAccept( vv -> { - lazyInitializer.setImplementation( instance ); - setEntityInstanceForNotify( instance ); - - } ); - } - else { - // FIXME: Read from cache if possible - return initializeEntity( getEntityInstance(), rowProcessingState ) - .thenAccept( ignore -> setEntityInstanceForNotify( getEntityInstance() ) ); - } - } ) - .thenAccept( o -> { - notifyResolutionListeners( getEntityInstanceForNotify() ); - setEntityInitialized( true ); - } ); + if ( state == State.KEY_RESOLVED || state == State.RESOLVED ) { + return initializeEntity( getEntityInstanceForNotify(), rowProcessingState ) + .thenAccept( v -> state = State.INITIALIZED ); } return voidFuture(); } - private CompletionStage initializeEntity(Object toInitialize, RowProcessingState rowProcessingState) { + protected CompletionStage initializeEntity(Object toInitialize, RowProcessingState rowProcessingState) { if ( !skipInitialization( toInitialize, rowProcessingState ) ) { assert consistentInstance( toInitialize, rowProcessingState ); return initializeEntityInstance( toInitialize, rowProcessingState ); 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 7ccf45d40..be90edbb6 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 @@ -8,39 +8,48 @@ 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.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; + /** * @see org.hibernate.sql.results.graph.entity.internal.EntityAssembler */ -public class ReactiveEntityAssembler implements ReactiveDomainResultsAssembler { +public class ReactiveEntityAssembler extends EntityAssembler implements ReactiveDomainResultsAssembler { - private final JavaType javaType; - private final EntityInitializer initializer; + public ReactiveEntityAssembler(JavaType javaType, EntityInitializer initializer) { + super(javaType, initializer); + } - public ReactiveEntityAssembler(JavaType javaType, EntityInitializer initializer) { - this.javaType = javaType; - this.initializer = initializer; + @Override + public Object assemble(RowProcessingState rowProcessingState) { + throw make( Log.class, lookup() ) + .nonReactiveMethodCall( "reactiveAssemble" ); } @Override - public JavaType getAssembledJavaType() { - return javaType; + public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { + throw make( Log.class, lookup() ) + .nonReactiveMethodCall( "reactiveAssemble" ); } @Override - public CompletionStage reactiveAssemble(ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { + public CompletionStage reactiveAssemble(ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { // 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) initializer ) + return ( (ReactiveInitializer) getInitializer() ) .reactiveResolveInstance( rowProcessingState ) - .thenApply( v -> (T) initializer.getEntityInstance() ); + .thenApply( v -> getInitializer().getEntityInstance() ); } } 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 24d3f1194..e761ce183 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 @@ -24,7 +24,6 @@ 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.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; import org.hibernate.type.Type; @@ -46,16 +45,15 @@ public ReactiveEntityDelayedFetchInitializer( @Override public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( getEntityInstance() != null ) { + if ( isProcessed() ) { return voidFuture(); } - final EntityInitializer parentEntityInitializer = getParentEntityInitializer( getFetchParentAccess() ); - if ( parentEntityInitializer != null && parentEntityInitializer.isEntityInitialized() ) { - return voidFuture(); - } + setProcessed( true ); - if ( !isAttributeAssignableToConcreteDescriptor( getFetchParentAccess(), referencedModelPart ) ) { + // 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 ) ) { return voidFuture(); } @@ -68,88 +66,72 @@ public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState else { final SharedSessionContractImplementor session = rowProcessingState.getSession(); final EntityPersister concreteDescriptor = referencedModelPart.getEntityMappingType().getEntityPersister(); - if ( !isSelectByUniqueKey() ) { - final EntityKey entityKey = new EntityKey( getIdentifier(), concreteDescriptor ); - final PersistenceContext persistenceContext = session.getPersistenceContext(); - - final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); - if ( holder != null && holder.getEntity() != null ) { - setEntityInstance( persistenceContext.proxyFor( holder ) ); - } + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + if ( isSelectByUniqueKey() ) { + final String uniqueKeyPropertyName = referencedModelPart.getReferencedPropertyName(); + final Type uniqueKeyPropertyType = uniqueKeyPropertyName == null + ? concreteDescriptor.getIdentifierType() + : session.getFactory().getReferencedPropertyType( concreteDescriptor.getEntityName(), uniqueKeyPropertyName ); + final EntityUniqueKey euk = new EntityUniqueKey( + concreteDescriptor.getEntityName(), + uniqueKeyPropertyName, + getIdentifier(), + uniqueKeyPropertyType, + session.getFactory() + ); + setEntityInstance( persistenceContext.getEntity( euk ) ); if ( getEntityInstance() == null ) { - setEntityInstance( persistenceContext.getEntity( entityKey ) ); + // 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 ); + } + else { + stage = stage + .thenCompose( v -> ( (ReactiveEntityPersister) concreteDescriptor ) + .reactiveLoadByUniqueKey( uniqueKeyPropertyName, getIdentifier(), session ) ) + .thenAccept( this::setEntityInstance ) + .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() ); + } + } ); + } + } + stage = stage.thenAccept( v -> { if ( getEntityInstance() != null ) { setEntityInstance( persistenceContext.proxyFor( getEntityInstance() ) ); } - } + } ); } - - if ( getEntityInstance() == null ) { - if ( referencedModelPart.isOptional() - && getFetchParentAccess() != null - && !getFetchParentAccess().isEmbeddableInitializer() - && isEnhancedForLazyLoading( parentEntityInitializer ) ) { + else { + final EntityKey entityKey = new EntityKey( getIdentifier(), concreteDescriptor ); + final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); + if ( holder != null && holder.getEntity() != null ) { + setEntityInstance( 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 ); } else { - if ( isSelectByUniqueKey() ) { - final String uniqueKeyPropertyName = referencedModelPart.getReferencedPropertyName(); - final Type uniqueKeyPropertyType = ( referencedModelPart.getReferencedPropertyName() == null ) - ? concreteDescriptor.getIdentifierType() - : session.getFactory().getReferencedPropertyType( concreteDescriptor.getEntityName(), uniqueKeyPropertyName ); - - final EntityUniqueKey euk = new EntityUniqueKey( - concreteDescriptor.getEntityName(), - uniqueKeyPropertyName, - getIdentifier(), - uniqueKeyPropertyType, - session.getFactory() - ); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - setEntityInstance( persistenceContext.getEntity( euk ) ); - if ( getEntityInstance() == null ) { - if ( getFetchParentAccess() != null - && !getFetchParentAccess().isEmbeddableInitializer() - && isEnhancedForLazyLoading( parentEntityInitializer ) ) { - return voidFuture(); - } - stage = stage - .thenCompose( v -> ( (ReactiveEntityPersister) concreteDescriptor ) - .reactiveLoadByUniqueKey( uniqueKeyPropertyName, getIdentifier(), session ) ) - .thenAccept( this::setEntityInstance ) - .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() ); - } - } ); - } - stage = stage.thenAccept( v -> { - if ( getEntityInstance() != null ) { - setEntityInstance( persistenceContext.proxyFor( getEntityInstance() ) ); + stage = stage.thenCompose( v -> ReactiveQueryExecutorLookup + .extract( session ) + .reactiveInternalLoad( concreteDescriptor.getEntityName(), getIdentifier(), false, false ) + .thenAccept( this::setEntityInstance ) + ); + } + stage = stage + .thenAccept( v -> { + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( getEntityInstance() ); + if ( lazyInitializer != null ) { + lazyInitializer.setUnwrap( referencedModelPart.isUnwrapProxy() && concreteDescriptor.isInstrumented() ); } } ); - } - else { - stage = stage.thenCompose( v -> ReactiveQueryExecutorLookup - .extract( session ) - .reactiveInternalLoad( concreteDescriptor.getEntityName(), getIdentifier(), false, false ) - .thenAccept( this::setEntityInstance ) - ); - } - - stage = stage - .thenAccept( v -> { - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( getEntityInstance() ); - if ( lazyInitializer != null ) { - lazyInitializer.setUnwrap( referencedModelPart.isUnwrapProxy() && concreteDescriptor.isInstrumented() ); - } - } ); - } } - - stage = stage.thenAccept( unused -> notifyResolutionListeners( getEntityInstance() ) ); } return stage; } 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 a5edf834a..ffc2f047c 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,15 +5,9 @@ */ package org.hibernate.reactive.sql.results.graph.entity.internal; -import org.hibernate.LockMode; -import org.hibernate.annotations.NotFoundAction; -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.Initializer; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; -import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; +import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; public class ReactiveEntityFetchJoinedImpl extends EntityFetchJoinedImpl { @@ -22,27 +16,18 @@ public ReactiveEntityFetchJoinedImpl(EntityFetchJoinedImpl entityFetch) { } @Override - protected Initializer buildEntityJoinedFetchInitializer( - EntityResultGraphNode resultDescriptor, - EntityValuedFetchable referencedFetchable, - NavigablePath navigablePath, - LockMode lockMode, - NotFoundAction notFoundAction, - DomainResult keyResult, - DomainResult rowIdResult, - Fetch identifierFetch, - Fetch discriminatorFetch, - AssemblerCreationState creationState) { + public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { return new ReactiveEntityJoinedFetchInitializer( - resultDescriptor, - referencedFetchable, - navigablePath, - lockMode, - notFoundAction, - keyResult, - rowIdResult, - identifierFetch, - discriminatorFetch, + getEntityResult(), + getReferencedModePart(), + getNavigablePath(), + creationState.determineEffectiveLockMode( getSourceAlias() ), + getNotFoundAction(), + getKeyResult(), + getEntityResult().getRowIdResult(), + getEntityResult().getIdentifierFetch(), + getEntityResult().getDiscriminatorFetch(), + parentAccess, creationState ); } 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 3ecd817b0..7caff3460 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 @@ -5,14 +5,10 @@ */ package org.hibernate.reactive.sql.results.graph.entity.internal; -import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; -import org.hibernate.persister.entity.EntityPersister; -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.FetchParentAccess; -import org.hibernate.sql.results.graph.Initializer; +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; public class ReactiveEntityFetchSelectImpl extends EntityFetchSelectImpl { @@ -22,27 +18,20 @@ public ReactiveEntityFetchSelectImpl(EntityFetchSelectImpl original) { } @Override - protected Initializer buildEntitySelectFetchInitializer( - FetchParentAccess parentAccess, - ToOneAttributeMapping fetchedMapping, - EntityPersister entityPersister, - DomainResult keyResult, - NavigablePath navigablePath, - boolean selectByUniqueKey, - AssemblerCreationState creationState) { + public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { return ReactiveEntitySelectFetchInitializerBuilder.createInitializer( parentAccess, - fetchedMapping, - entityPersister, - keyResult, - navigablePath, - selectByUniqueKey, + getFetchedMapping(), + getReferencedMappingContainer().getEntityPersister(), + getKeyResult(), + getNavigablePath(), + isSelectByUniqueKey(), creationState ); } @Override - protected DomainResultAssembler buildEntityAssembler(Initializer initializer) { - return new ReactiveEntityAssembler<>( getResultJavaType(), initializer.asEntityInitializer() ); + 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/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java index 53048a3c9..4cb7d5b00 100644 --- 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 @@ -6,27 +6,34 @@ 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 EntityValuedFetchable referencedFetchable; private final DomainResultAssembler keyAssembler; private final NotFoundAction notFoundAction; @@ -40,6 +47,7 @@ public ReactiveEntityJoinedFetchInitializer( DomainResult rowIdResult, Fetch identifierFetch, Fetch discriminatorFetch, + FetchParentAccess parentAccess, AssemblerCreationState creationState) { super( resultDescriptor, @@ -48,59 +56,78 @@ public ReactiveEntityJoinedFetchInitializer( identifierFetch, discriminatorFetch, rowIdResult, + parentAccess, creationState ); - this.referencedFetchable = referencedFetchable; + assert getInitializedPart() == referencedFetchable; this.notFoundAction = notFoundAction; - this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState ); - } - @Override - protected void registerLoadingEntityInstanceFromExecutionContext(RowProcessingState rowProcessingState, Object instance) { - // we want the EntityResultInitializer to take care of the instance + this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState ); } @Override public void resolveKey(RowProcessingState rowProcessingState) { - if ( shouldSkipResolveInstance( rowProcessingState ) ) { - missing = true; - return; + if ( isParentShallowCached() ) { + state = State.MISSING; } + else if ( state == State.UNINITIALIZED ) { + if ( shouldSkipInitializer( rowProcessingState ) ) { + state = State.MISSING; + return; + } - super.resolveKey( rowProcessingState ); + 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 + // 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 ( isMissing() ) { - if ( notFoundAction != NotFoundAction.IGNORE ) { - throw new FetchNotFoundException( - referencedFetchable.getEntityMappingType().getEntityName(), - fkKeyValue - ); - } - else { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "Ignoring dangling foreign-key due to `@NotFound(IGNORE); association will be null - %s", - getNavigablePath() - ); + 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 - protected boolean isEntityReturn() { + 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 index 1f4b56389..fde931db7 100644 --- 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 @@ -14,7 +14,6 @@ import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; /** * @see org.hibernate.sql.results.graph.entity.internal.EntityResultInitializer @@ -37,6 +36,7 @@ public ReactiveEntityResultInitializer( identifierFetch, discriminatorFetch, rowIdResult, + null, creationState ); } @@ -47,19 +47,18 @@ protected String getSimpleConcreteImplName() { } @Override - protected boolean isEntityReturn() { - return true; + public String toString() { + return CONCRETE_NAME + "(" + getNavigablePath() + ")"; } @Override - public String toString() { - return CONCRETE_NAME + "(" + getNavigablePath() + ")"; + public boolean isPartOfKey() { + // The entity result itself can never be part of the key + return false; } @Override - protected void registerLoadingEntityInstanceFromExecutionContext( - RowProcessingState rowProcessingState, - Object instance) { - registerLoadingEntity( rowProcessingState, instance ); + 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 e975eecac..88300f7fb 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,17 +10,18 @@ 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.entity.EntityInitializer; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; 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 @@ -40,32 +41,52 @@ public ReactiveEntitySelectFetchByUniqueKeyInitializer( } @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( getEntityInstance() != null || isEntityInitialized() ) { + public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { + if ( state != State.UNINITIALIZED ) { return voidFuture(); } state = State.RESOLVED; - final EntityInitializer parentEntityInitializer = getParentEntityInitializer( parentAccess ); - if ( parentEntityInitializer != null && parentEntityInitializer.getEntityKey() != null ) { - // make sure parentEntityInitializer.resolveInstance has been called before - parentEntityInitializer.resolveInstance( rowProcessingState ); - if ( parentEntityInitializer.isEntityInitialized() ) { - initializeState(); - return voidFuture(); - } + // 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(); } - if ( !isAttributeAssignableToConcreteDescriptor() ) { - initializeState(); + entityIdentifier = keyAssembler.assemble( rowProcessingState ); + if ( entityIdentifier == null ) { + state = State.INITIALIZED; return voidFuture(); } - final Object entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - initializeState(); + 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; + final String entityName = concreteDescriptor.getEntityName(); final String uniqueKeyPropertyName = fetchedAttribute.getReferencedPropertyName(); final SharedSessionContractImplementor session = rowProcessingState.getSession(); @@ -79,43 +100,23 @@ public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingSta final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); setEntityInstance( persistenceContext.getEntity( euk ) ); if ( entityInstance == null ) { - final ReactiveEntitySelectFetchByUniqueKeyInitializer initializer = (ReactiveEntitySelectFetchByUniqueKeyInitializer) persistenceContext - .getLoadContexts() - .findInitializer( euk ); - if ( initializer == null ) { - final JdbcValuesSourceProcessingState jdbcValuesSourceProcessingState = rowProcessingState.getJdbcValuesSourceProcessingState(); - jdbcValuesSourceProcessingState.registerInitializer( euk, this ); - - return ( (ReactiveEntityPersister) concreteDescriptor ) - .reactiveLoadByUniqueKey( uniqueKeyPropertyName, entityIdentifier, session ) - .thenAccept( this::setEntityInstance ) - .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 ); - } - notifyResolutionListeners( entityInstance ); - } ); - } - else { - registerResolutionListener( this::setEntityInstance ); - } + return ( (ReactiveEntityPersister) concreteDescriptor ) + .reactiveLoadByUniqueKey( uniqueKeyPropertyName, entityIdentifier, session ) + .thenAccept( this::setEntityInstance ) + .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 ( entityInstance != null ) { setEntityInstance( persistenceContext.proxyFor( entityInstance ) ); } - initializeState(); return voidFuture(); } - private EntityInitializer getParentEntityInitializer(FetchParentAccess parentAccess) { - if ( parentAccess != null ) { - return parentAccess.findFirstEntityInitializer(); - } - return null; - } - private void setEntityInstance(Object instance) { entityInstance = instance; } 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 a20608c8f..1a9d14f4c 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 @@ -30,7 +30,6 @@ 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.EntityInitializer; import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; @@ -74,6 +73,74 @@ public void initializeInstance(RowProcessingState rowProcessingState) { @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(); @@ -96,22 +163,19 @@ public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState @Override public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( getEntityInstance() != null || isEntityInitialized() ) { - return voidFuture(); - } - - final EntityInitializer parentEntityInitializer = parentAccess.findFirstEntityInitializer(); - if ( parentEntityInitializer != null && parentEntityInitializer.isEntityInitialized() ) { - initializeState(); + if ( state != State.RESOLVED ) { return voidFuture(); } + state = State.INITIALIZED; - if ( !isAttributeAssignableToConcreteDescriptor() ) { + // 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(); } - final Object entityIdentifier = keyAssembler.assemble( rowProcessingState ); + entityIdentifier = keyAssembler.assemble( rowProcessingState ); if ( entityIdentifier == null ) { initializeState(); return voidFuture(); 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 9fc4ab20a..34d25c6bc 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 @@ -12,11 +12,11 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.AbstractFetchParentAccess; 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.embeddable.EmbeddableInitializer; +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; import org.hibernate.sql.results.graph.entity.internal.BatchInitializeEntitySelectFetchInitializer; @@ -26,7 +26,7 @@ */ public class ReactiveEntitySelectFetchInitializerBuilder { - public static AbstractFetchParentAccess createInitializer( + public static EntityInitializer createInitializer( FetchParentAccess parentAccess, ToOneAttributeMapping fetchedAttribute, EntityPersister entityPersister, 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 c025903f6..5fc1a50b7 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 @@ -8,11 +8,11 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; 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.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.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchImpl; public class ReactiveEntityDelayedFetchImpl extends EntityDelayedFetchImpl { @@ -26,12 +26,15 @@ public ReactiveEntityDelayedFetchImpl( } @Override - protected Initializer buildEntityDelayedFetchInitializer( - FetchParentAccess parentAccess, - NavigablePath navigablePath, - ToOneAttributeMapping entityValuedModelPart, - boolean selectByUniqueKey, - DomainResultAssembler resultAssembler) { - return new ReactiveEntityDelayedFetchInitializer( parentAccess, navigablePath, entityValuedModelPart, selectByUniqueKey, resultAssembler ); + public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { + return new ReactiveEntityDelayedFetchInitializer( parentAccess, + getNavigablePath(), + getEntityValuedModelPart(), + isSelectByUniqueKey(), + getKeyResult().createResultAssembler( + parentAccess, + creationState + ) + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultJoinedSubclassImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultJoinedSubclassImpl.java deleted file mode 100644 index e54065b98..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultJoinedSubclassImpl.java +++ /dev/null @@ -1,52 +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.internal; - -import org.hibernate.metamodel.mapping.EntityValuedModelPart; -import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityAssembler; -import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityResultInitializer; -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.Initializer; -import org.hibernate.sql.results.graph.entity.internal.EntityResultJoinedSubclassImpl; - -/** - * @see org.hibernate.sql.results.graph.entity.internal.EntityResultJoinedSubclassImpl - */ -public class ReactiveEntityResultJoinedSubclassImpl extends EntityResultJoinedSubclassImpl { - - public ReactiveEntityResultJoinedSubclassImpl( - NavigablePath navigablePath, - EntityValuedModelPart entityValuedModelPart, - TableGroup tableGroup, - String resultVariable) { - super( navigablePath, entityValuedModelPart, tableGroup, resultVariable ); - } - - @Override - public DomainResultAssembler createResultAssembler( - FetchParentAccess parentAccess, - AssemblerCreationState creationState) { - final Initializer initializer = creationState.resolveInitializer( - getNavigablePath(), - getReferencedModePart(), - () -> new ReactiveEntityResultInitializer( - this, - getNavigablePath(), - getLockMode( creationState ), - getIdentifierFetch(), - getDiscriminatorFetch(), - getRowIdResult(), - creationState - ) - ); - - return new ReactiveEntityAssembler( this.getResultJavaType(), initializer.asEntityInitializer() ); - } -} 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 95db00c8f..d3038621c 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 @@ -21,7 +21,10 @@ 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.spi.RowTransformer; @@ -74,6 +77,36 @@ 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( @@ -86,11 +119,8 @@ public Initializer resolveInitializer( } } - final Initializer initializer = producer.get(); - ResultsLogger.RESULTS_MESSAGE_LOGGER.tracef( - "Registering initializer : %s", - initializer - ); + final Initializer initializer = producer.createInitializer( resultGraphNode, parentAccess, this ); + ResultsLogger.RESULTS_MESSAGE_LOGGER.tracef( "Registering initializer : %s", initializer ); initializerMap.put( navigablePath, initializer ); initializersBuilder.addInitializer( initializer ); 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 d2cb94727..7fda67775 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 @@ -9,9 +9,6 @@ import java.util.List; import java.util.concurrent.CompletionStage; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.query.named.RowReaderMemento; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; @@ -130,19 +127,4 @@ private CompletionStage coordinateInitializers(ReactiveRowProcessingState public void finishUp(JdbcValuesSourceProcessingState processingState) { initializers.endLoading( processingState.getExecutionContext() ); } - - @Override - public RowReaderMemento toMemento(SessionFactoryImplementor factory) { - return new RowReaderMemento() { - @Override - public Class[] getResultClasses() { - return ArrayHelper.EMPTY_CLASS_ARRAY; - } - - @Override - public String[] getResultMappingNames() { - return ArrayHelper.EMPTY_STRING_ARRAY; - } - }; - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardValuesMappingProducer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardValuesMappingProducer.java index 0c2b42109..caa7eaf34 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardValuesMappingProducer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardValuesMappingProducer.java @@ -5,14 +5,11 @@ */ package org.hibernate.reactive.sql.results.internal; -import java.lang.invoke.MethodHandles; import java.util.List; import java.util.concurrent.CompletionStage; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.results.spi.ReactiveValuesMappingProducer; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.results.graph.DomainResult; @@ -26,8 +23,6 @@ public class ReactiveStandardValuesMappingProducer extends JdbcValuesMappingProducerStandard implements JdbcValuesMappingProducer, ReactiveValuesMappingProducer { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public ReactiveStandardValuesMappingProducer(List sqlSelections, List> domainResults) { super( sqlSelections, domainResults ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveValuesResultSetImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveValuesResultSetImpl.java deleted file mode 100644 index 0419f5b44..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveValuesResultSetImpl.java +++ /dev/null @@ -1,9 +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.internal; - -public class ReactiveValuesResultSetImpl { -} 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 514f22120..d42cdff6c 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 @@ -12,7 +12,7 @@ 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.Initializer; +import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.internal.domain.CircularFetchImpl; /** @@ -24,7 +24,7 @@ public ReactiveCircularFetchImpl(CircularFetchImpl original) { } @Override - protected Initializer buildEntitySelectFetchInitializer( + protected EntityInitializer buildEntitySelectFetchInitializer( FetchParentAccess parentAccess, ToOneAttributeMapping fetchable, EntityPersister entityPersister, diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java index cc44e8b24..1cdc423f0 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java @@ -67,28 +67,26 @@ private static boolean filter(String s) { @Test public void testBatchingWithPersistAll(VertxTestContext context) { - test( context, openSession() - .thenCompose( s -> s - .persist( - new GuineaPig( 11, "One" ), - new GuineaPig( 22, "Two" ), - new GuineaPig( 33, "Three" ) - ) - // Auto-flush - .thenCompose( v -> s - .createSelectionQuery( "select name from GuineaPig", String.class ) - .getResultList() - .thenAccept( names -> { - assertThat( names ).containsExactlyInAnyOrder( "One", "Two", "Three" ); - assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); - // Parameters are different for different dbs, so we cannot do an exact match - assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) - .startsWith( "insert into pig (name,version,id) values " ); - sqlTracker.clear(); - } ) - ) + test( context, openSession().thenCompose( s -> s + .persist( + new GuineaPig( 11, "One" ), + new GuineaPig( 22, "Two" ), + new GuineaPig( 33, "Three" ) ) - ); + // Auto-flush + .thenCompose( v -> s + .createSelectionQuery( "select name from GuineaPig", String.class ) + .getResultList() + .thenAccept( names -> { + assertThat( names ).containsExactlyInAnyOrder( "One", "Two", "Three" ); + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + // Parameters are different for different dbs, so we cannot do an exact match + assertThat( sqlTracker.getLoggedQueries().get( 0 ) ) + .startsWith( "insert into pig (name,version,id) values " ); + sqlTracker.clear(); + } ) + ) + ) ); } @Test diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertySingleTableTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertySingleTableTest.java index 0b95dc579..60b23a563 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertySingleTableTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/GeneratedPropertySingleTableTest.java @@ -52,8 +52,8 @@ protected Collection> annotatedEntities() { @Override protected Configuration constructConfiguration() { Configuration configuration = super.constructConfiguration(); - configuration.setProperty(AvailableSettings.HBM2DDL_CREATE_SOURCE, "script-then-metadata"); - configuration.setProperty(AvailableSettings.HBM2DDL_CREATE_SCRIPT_SOURCE, "/mysql-pipe.sql"); + configuration.setProperty( AvailableSettings.HBM2DDL_CREATE_SOURCE, "script-then-metadata" ); + configuration.setProperty( AvailableSettings.HBM2DDL_CREATE_SCRIPT_SOURCE, "/mysql-pipe.sql" ); return configuration; } @@ -66,7 +66,7 @@ public void testWithIdentity(VertxTestContext context) { context, getMutinySessionFactory() // Generated during insert - .withSession( session -> session.persist( davide ).call( session::flush ) + .withTransaction( session -> session.persist( davide ).call( session::flush ) .invoke( v -> { assertNotNull( davide.id ); assertEquals( "Davide", davide.firstname ); @@ -80,7 +80,7 @@ public void testWithIdentity(VertxTestContext context) { } ) ) // Generated during update .chain( () -> getMutinySessionFactory() - .withSession( session -> session.find( GeneratedWithIdentity.class, davide.id ) + .withTransaction( session -> session.find( GeneratedWithIdentity.class, davide.id ) .chain( result -> { CurrentUser.INSTANCE.logIn( "dd-update" ); result.lastname = "O'Tall"; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/NonNullableManyToOneTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/NonNullableManyToOneTest.java index 67bb65326..1504851ec 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/NonNullableManyToOneTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/NonNullableManyToOneTest.java @@ -8,6 +8,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.concurrent.CompletionStage; + + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,6 +25,7 @@ import jakarta.persistence.Table; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -49,6 +53,13 @@ public void populateDB(VertxTestContext context) { .withTransaction( s -> s.persistAll( painting, artist, dealer ) ) ); } + @Override + protected CompletionStage cleanDb() { + // the default implementation is too basic to delete this mapping and, because there's only one test, it's + // enough to drop the table at startup + return voidFuture(); + } + @Test public void testNonNullableSuccess(VertxTestContext context) { test( context, getMutinySessionFactory() diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SecondaryTableTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SecondaryTableTest.java index ee835eee4..ad9d71326 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SecondaryTableTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SecondaryTableTest.java @@ -41,7 +41,7 @@ public class SecondaryTableTest extends BaseReactiveTest { @Override protected Collection> annotatedEntities() { - return List.of( Book.class, Author.class ); + return List.of( Author.class, Book.class ); } @Test diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SingleTableInheritanceTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SingleTableInheritanceTest.java index 2e8c25c42..7361cf2fb 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SingleTableInheritanceTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SingleTableInheritanceTest.java @@ -37,7 +37,7 @@ public class SingleTableInheritanceTest extends BaseReactiveTest { @Override protected Collection> annotatedEntities() { - return List.of( Book.class, Author.class, SpellBook.class ); + return List.of( Author.class, Book.class, SpellBook.class ); } @Test diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTestBase.java index 15f9c7e37..4a5117fee 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTestBase.java @@ -38,7 +38,7 @@ * - TODO: Missing column * - TODO: Wrong column type */ -@DisabledFor(value = DB2, reason = "No InformationExtractor for Dialect [org.hibernate.dialect.DB2Dialect..]") +@DisabledFor(value = DB2, reason = "We don't have an information extractor. See https://github.com/hibernate/hibernate-reactive/issues/911") public abstract class SchemaValidationTestBase extends BaseReactiveTest { public static class IndividuallyStrategyTest extends SchemaValidationTestBase { From c3daf3cb37bdfc2c0b196f400f6de70b2922970c Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 3 Apr 2024 17:51:27 +0200 Subject: [PATCH 5/8] [#1857] Update GitHub action name --- .github/workflows/tracking-orm-6.build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tracking-orm-6.build.yml b/.github/workflows/tracking-orm-6.build.yml index da6c39b45..1e900b59c 100644 --- a/.github/workflows/tracking-orm-6.build.yml +++ b/.github/workflows/tracking-orm-6.build.yml @@ -1,6 +1,6 @@ # Run the build using the latest ORM 5.x snapshots # so that we can spot integration issues early -name: Latest ORM 6.4 +name: Latest ORM 6.5 on: # Trigger the workflow on push or pull request, From 6d34a599c83b68175a17aa44be8dd5e346cb0716 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 3 Apr 2024 13:58:18 +0200 Subject: [PATCH 6/8] [#1878] Upgrade MariaDB to 11.3.2 --- .../java/org/hibernate/reactive/containers/MariaDatabase.java | 2 +- podman.md | 2 +- tooling/jbang/MariaDBReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java index 46e2a0ad6..997e6dc7d 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java @@ -21,7 +21,7 @@ class MariaDatabase extends MySQLDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MariaDBContainer maria = new MariaDBContainer<>( imageName( "mariadb", "11.2.2" ) ) + public static final MariaDBContainer maria = new MariaDBContainer<>( imageName( "mariadb", "11.3.2" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/podman.md b/podman.md index 548f1030a..766a79d37 100644 --- a/podman.md +++ b/podman.md @@ -66,7 +66,7 @@ and schema to run the tests: ``` podman run --rm --name HibernateTestingMariaDB \ -e MYSQL_ROOT_PASSWORD=hreact -e MYSQL_DATABASE=hreact -e MYSQL_USER=hreact -e MYSQL_PASSWORD=hreact \ - -p 3306:3306 docker.io/mariadb:11.2.2 + -p 3306:3306 docker.io/mariadb:11.3.2 ``` When the database has started, you can run the tests on MariaDB with: diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index 6757c0623..802362c97 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -69,7 +69,7 @@ public class {baseName} { } @ClassRule - public final static MariaDBContainer database = new MariaDBContainer<>( imageName( "docker.io", "mariadb", "11.2.2" ) ); + public final static MariaDBContainer database = new MariaDBContainer<>( imageName( "docker.io", "mariadb", "11.3.2" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 8a150ef5c..ac3b76ef6 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -231,7 +231,7 @@ enum Database { POSTGRESQL( () -> new PostgreSQLContainer( "postgres:16.1" ) ), MYSQL( () -> new MySQLContainer( "mysql:8.3.0" ) ), DB2( () -> new Db2Container( "docker.io/icr.io/db2_community/db2:11.5.9.0" ).acceptLicense() ), - MARIADB( () -> new MariaDBContainer( "mariadb:11.2.2" ) ), + MARIADB( () -> new MariaDBContainer( "mariadb:11.3.2" ) ), COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v23.1.14" ) ); private final Supplier> containerSupplier; From 2b3dd7812175deb94059aec92ce8eb478d50461f Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 3 Apr 2024 14:36:37 +0200 Subject: [PATCH 7/8] [#1878] Update CockroachDB to v23.1.17 --- .../org/hibernate/reactive/containers/CockroachDBDatabase.java | 2 +- podman.md | 2 +- tooling/jbang/CockroachDBReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java index ef8ef06ee..b6ae58294 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java @@ -25,7 +25,7 @@ class CockroachDBDatabase extends PostgreSQLDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final CockroachContainer cockroachDb = new CockroachContainer( imageName( "cockroachdb/cockroach", "v23.1.14" ) ) + public static final CockroachContainer cockroachDb = new CockroachContainer( imageName( "cockroachdb/cockroach", "v23.1.17" ) ) // Username, password and database are not supported by test container at the moment // Testcontainers will use a database named 'postgres' and the 'root' user .withReuse( true ); diff --git a/podman.md b/podman.md index 766a79d37..475842257 100644 --- a/podman.md +++ b/podman.md @@ -120,7 +120,7 @@ configured to run the tests: ``` podman run --rm --name=HibernateTestingCockroachDB \ --hostname=roachrr1 -p 26257:26257 -p 8080:8080 \ - docker.io/cockroachdb/cockroach:v23.1.14 start-single-node --insecure + docker.io/cockroachdb/cockroach:v23.1.17 start-single-node --insecure ``` Some of tests needs temporary tables and because this is an experimental feature in diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 5c32042ed..a71664417 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -70,7 +70,7 @@ public class {baseName} { } @ClassRule - public final static CockroachContainer database = new CockroachContainer( imageName( "cockroachdb", "cockroach", "v23.1.14" ) ); + public final static CockroachContainer database = new CockroachContainer( imageName( "cockroachdb", "cockroach", "v23.1.17" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index ac3b76ef6..c1aa94de9 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -232,7 +232,7 @@ enum Database { MYSQL( () -> new MySQLContainer( "mysql:8.3.0" ) ), DB2( () -> new Db2Container( "docker.io/icr.io/db2_community/db2:11.5.9.0" ).acceptLicense() ), MARIADB( () -> new MariaDBContainer( "mariadb:11.3.2" ) ), - COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v23.1.14" ) ); + COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v23.1.17" ) ); private final Supplier> containerSupplier; From 8b7a2ea35c3053a2f6fc69cf1efe0d9f205155dd Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 3 Apr 2024 17:51:54 +0200 Subject: [PATCH 8/8] [#1878] Upgrade PostgreSQL to 16.2 --- .../org/hibernate/reactive/containers/PostgreSQLDatabase.java | 2 +- .../src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java | 2 +- .../java/org/hibernate/reactive/it/verticle/VertxServer.java | 2 +- podman.md | 2 +- tooling/jbang/Example.java | 2 +- tooling/jbang/PostgreSQLReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java index 2a646971b..f7e6509e8 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java @@ -87,7 +87,7 @@ class PostgreSQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( imageName( "postgres", "16.1" ) ) + public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( imageName( "postgres", "16.2" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java index d1bbc66f0..64e0c7078 100644 --- a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java @@ -53,7 +53,7 @@ public abstract class BaseReactiveIT { public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); public static final DockerImageName IMAGE_NAME = DockerImageName - .parse( "docker.io/postgres:16.1" ) + .parse( "docker.io/postgres:16.2" ) .asCompatibleSubstituteFor( "postgres" ); public static final String USERNAME = "hreact"; diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java index 28b65aa73..149ca09d6 100644 --- a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java +++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java @@ -36,7 +36,7 @@ public class VertxServer { // These properties are in DatabaseConfiguration in core public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); - public static final String IMAGE_NAME = "postgres:16.1"; + public static final String IMAGE_NAME = "postgres:16.2"; public static final String USERNAME = "hreact"; public static final String PASSWORD = "hreact"; public static final String DB_NAME = "hreact"; diff --git a/podman.md b/podman.md index 475842257..54bfa9a35 100644 --- a/podman.md +++ b/podman.md @@ -38,7 +38,7 @@ required credentials and schema to run the tests: ``` podman run --rm --name HibernateTestingPGSQL \ -e POSTGRES_USER=hreact -e POSTGRES_PASSWORD=hreact -e POSTGRES_DB=hreact \ - -p 5432:5432 docker.io/postgres:16.1 + -p 5432:5432 docker.io/postgres:16.2 ``` When the database has started, you can run the tests on PostgreSQL with: diff --git a/tooling/jbang/Example.java b/tooling/jbang/Example.java index b29c1098a..48de89956 100644 --- a/tooling/jbang/Example.java +++ b/tooling/jbang/Example.java @@ -59,7 +59,7 @@ *

  *                 podman run --rm --name HibernateTestingPGSQL \
  *                      -e POSTGRES_USER=hreact -e POSTGRES_PASSWORD=hreact -e POSTGRES_DB=hreact \
- *                      -p 5432:5432 postgres:16.1
+ *                      -p 5432:5432 postgres:16.2
  *              
* *
3. Run the example with JBang
diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index 9b71ebe4f..c00e4ebb5 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -67,7 +67,7 @@ public class {baseName} { } @ClassRule - public final static PostgreSQLContainer database = new PostgreSQLContainer( imageName( "docker.io", "postgres", "16.1" ) ); + public final static PostgreSQLContainer database = new PostgreSQLContainer( imageName( "docker.io", "postgres", "16.2" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index c1aa94de9..df2c48ed5 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -228,7 +228,7 @@ public String toString() { * It's a wrapper around the testcontainers classes. */ enum Database { - POSTGRESQL( () -> new PostgreSQLContainer( "postgres:16.1" ) ), + POSTGRESQL( () -> new PostgreSQLContainer( "postgres:16.2" ) ), MYSQL( () -> new MySQLContainer( "mysql:8.3.0" ) ), DB2( () -> new Db2Container( "docker.io/icr.io/db2_community/db2:11.5.9.0" ).acceptLicense() ), MARIADB( () -> new MariaDBContainer( "mariadb:11.3.2" ) ),