From 9aa2e39722e2382c4d00ec8ff6268e81ceb73e24 Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Thu, 19 Dec 2024 11:46:17 +0100
Subject: [PATCH 001/162] [#2044] Enable GitHub workflows on different branches
Include 2.* and 3.* branches and tags (before it was only the WIP ones)
---
.github/workflows/build.yml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6c90f0b9c..adbb93c23 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -5,12 +5,17 @@ on:
branches:
- 'main'
- 'wip/**'
+ - '2.*'
+ - '3.*'
tags:
- '2.*'
+ - '3.*'
pull_request:
branches:
- 'main'
- 'wip/**'
+ - '2.*'
+ - '3.*'
# For building snapshots
workflow_call:
inputs:
From b14ffd21c4209a75df969b9dfee60b8f29d0a75f Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Thu, 19 Dec 2024 09:47:14 +0100
Subject: [PATCH 002/162] [#2043] Upgrade Hibernate ORM to 6.6.4.Final
---
README.md | 2 +-
gradle.properties | 4 ++--
.../query/sql/spi/ReactiveNamedNativeQueryMemento.java | 5 +++++
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index a44faa31b..d3a3431af 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ Hibernate Reactive has been tested with:
- CockroachDB v24
- MS SQL Server 2022
- Oracle 23
-- [Hibernate ORM][] 6.6.3.Final
+- [Hibernate ORM][] 6.6.4.Final
- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.11
- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.11
- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.11
diff --git a/gradle.properties b/gradle.properties
index 55ec51fc8..e8b659047 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -35,12 +35,12 @@ org.gradle.java.installations.auto-download=false
#enableMavenLocalRepo = true
# The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`)
-hibernateOrmVersion = 6.6.3.Final
+hibernateOrmVersion = 6.6.4.Final
# 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.6.3.Final
+#hibernateOrmGradlePluginVersion = 6.6.4.Final
# If set to true, skip Hibernate ORM version parsing (default is true, if set to null)
# this is required when using intervals or weird versions or the build will fail
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedNativeQueryMemento.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedNativeQueryMemento.java
index fc1493d02..9d639d099 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedNativeQueryMemento.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedNativeQueryMemento.java
@@ -52,6 +52,11 @@ public Class> getResultMappingClass() {
return delegate.getResultMappingClass();
}
+ @Override
+ public Class> getResultType() {
+ return delegate.getResultType();
+ }
+
@Override
public Integer getFirstResult() {
return delegate.getFirstResult();
From c06e818f3ccf5aa79b1ad1314fb2e782e3c81a45 Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Fri, 20 Dec 2024 10:51:49 +0100
Subject: [PATCH 003/162] [#2049] Update GitHub workflow cache action to v4
v2 has been deprecated for a while:
https://github.blog/changelog/2024-09-16-notice-of-upcoming-deprecations-and-changes-in-github-actions-services/
---
.github/workflows/build.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index adbb93c23..c0e403d92 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -90,7 +90,7 @@ jobs:
echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")"
shell: bash
- name: Cache Gradle downloads
- uses: actions/cache@v2
+ uses: actions/cache@v4
id: cache-gradle
with:
path: |
@@ -132,7 +132,7 @@ jobs:
echo "::set-output name=yearmonth::$(/bin/date -u "+%Y-%m")"
shell: bash
- name: Cache Gradle downloads
- uses: actions/cache@v2
+ uses: actions/cache@v4
id: cache-gradle
with:
path: |
From bad390ee1c19de7b883da3adfb79faf2691a1c01 Mon Sep 17 00:00:00 2001
From: Steve Ebersole
Date: Thu, 12 Dec 2024 09:43:53 -0600
Subject: [PATCH 004/162] [#1095] Sign the artifacts for Sonatype
---
ci/release/Jenkinsfile | 6 +-
ci/snapshot-publish.Jenkinsfile | 4 +-
publish.gradle | 97 ++++++++++++++++++++++++++++-----
3 files changed, 89 insertions(+), 18 deletions(-)
diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile
index bb4315fb8..5fe408354 100644
--- a/ci/release/Jenkinsfile
+++ b/ci/release/Jenkinsfile
@@ -168,8 +168,8 @@ pipeline {
withCredentials([
usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USER'),
usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'PLUGIN_PORTAL_PASSWORD', usernameVariable: 'PLUGIN_PORTAL_USERNAME'),
- file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'),
- string(credentialsId: 'release.gpg.passphrase', variable: 'RELEASE_GPG_PASSPHRASE')
+ file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'),
+ string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE')
]) {
sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) {
// set release version
@@ -202,7 +202,7 @@ pipeline {
usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USER'),
usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'PLUGIN_PORTAL_PASSWORD', usernameVariable: 'PLUGIN_PORTAL_USERNAME'),
file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'),
- string(credentialsId: 'release.gpg.passphrase', variable: 'RELEASE_GPG_PASSPHRASE'),
+ string(credentialsId: 'release.gpg.passphrase', variable: 'RELEASE_GPG_PASSPHRASE')
gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default')
]) {
sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) {
diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile
index c95f6663f..83fd95a4a 100644
--- a/ci/snapshot-publish.Jenkinsfile
+++ b/ci/snapshot-publish.Jenkinsfile
@@ -32,8 +32,8 @@ pipeline {
steps {
withCredentials([
usernamePassword(credentialsId: 'ossrh.sonatype.org', usernameVariable: 'hibernatePublishUsername', passwordVariable: 'hibernatePublishPassword'),
- string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_PASS'),
- file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_KEYRING')
+ file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'),
+ string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE')
]) {
sh '''./gradlew clean publish \
-PhibernatePublishUsername=$hibernatePublishUsername \
diff --git a/publish.gradle b/publish.gradle
index 3c48e1298..39e0e16b2 100644
--- a/publish.gradle
+++ b/publish.gradle
@@ -1,13 +1,14 @@
+apply plugin: 'java'
apply plugin: 'maven-publish'
+apply plugin: 'signing'
-tasks.register( 'sourcesJar', Jar ) {
- from sourceSets.main.allJava
- archiveClassifier = 'sources'
-}
+// Java / publishing
-tasks.register( 'javadocJar', Jar ) {
- from javadoc
- archiveClassifier = 'javadoc'
+java {
+ // include javadoc and sources jar in the Java component
+ // - classes jar included by default
+ withJavadocJar()
+ withSourcesJar()
}
jar {
@@ -35,14 +36,9 @@ javadoc {
publishing {
publications {
- logger.lifecycle "Publishing groupId: '" + project.group + "', version: '" + project.version + "'"
-
publishedArtifacts(MavenPublication) {
- groupId = project.group
- version = project.version
from components.java
- artifact sourcesJar
- artifact javadocJar
+
pom {
name = project.mavenPomName
description = project.description
@@ -80,3 +76,78 @@ publishing {
}
}
}
+
+
+// signing
+
+var signingExtension = project.getExtensions().getByType(SigningExtension) as SigningExtension
+
+// create a `signPublications` "grouping" task which will execute all Sign tasks
+def signPublicationsTask = tasks.register('signPublications')
+tasks.named( "publishPublishedArtifactsPublicationToSonatypeRepository" ) {
+ dependsOn signPublicationsTask
+}
+
+gradle.taskGraph.whenReady { TaskExecutionGraph graph ->
+ boolean wasSigningRequested = false
+ boolean wasPublishingRequested = false
+ List signingTasks = []
+
+ graph.allTasks.each {task ->
+ logger.lifecycle( "Checking task : $task" )
+ if ( task instanceof Sign ) {
+ logger.lifecycle( " - Task is Sign" )
+ signingTasks.add( task )
+ wasSigningRequested = true
+ }
+ else if ( task instanceof PublishToMavenRepository ) {
+ logger.lifecycle( " - Task is PublishToMavenRepository" )
+ wasPublishingRequested = true
+ }
+ }
+
+ if ( wasPublishingRequested ) {
+ logger.lifecycle "Publishing groupId: '" + project.group + "', version: '" + project.version + "'"
+ }
+
+ if ( wasSigningRequested || wasPublishingRequested ) {
+ // signing was explicitly requested and/or we are publishing to Sonatype OSSRH
+ // - we need the signing to happen
+ signingExtension.required = true
+
+ var signingKey = resolveSigningKey()
+ var signingPassword = resolveSigningPassphrase()
+ signingExtension.useInMemoryPgpKeys( signingKey, signingPassword )
+ signingExtension.sign publishing.publications.publishedArtifacts
+
+ signPublicationsTask.get().dependsOn( signingTasks )
+ }
+ else {
+ // signing was not explicitly requested and we are not publishing to OSSRH,
+ // - disable all Sign tasks
+ signingTasks.each { enabled = false }
+ }
+}
+
+
+static String resolveSigningKey() {
+ var key = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY" )
+ if ( key != null ) {
+ return key
+ }
+
+ var keyFile = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY_PATH" )
+ if ( keyFile != null ) {
+ return new File( keyFile ).text
+ }
+
+ throw new RuntimeException( "Cannot perform signing without GPG details." )
+}
+
+static String resolveSigningPassphrase() {
+ var passphrase = System.getenv().get( "SIGNING_GPG_PASSPHRASE" )
+ if ( passphrase == null ) {
+ throw new RuntimeException( "Cannot perform signing without GPG details." )
+ }
+ return passphrase
+}
From 8e70e3a2e5b272f389c787dbea150b3afd70e896 Mon Sep 17 00:00:00 2001
From: Steve Ebersole
Date: Thu, 12 Dec 2024 11:38:34 -0600
Subject: [PATCH 005/162] [#2027] Use env-vars for passing secrets used during
release
---
build.gradle | 18 +--------
ci/release/Jenkinsfile | 38 ++++++++-----------
ci/snapshot-publish.Jenkinsfile | 9 ++---
publish.gradle | 65 ++++++++++++++++++++++++++-------
4 files changed, 72 insertions(+), 58 deletions(-)
diff --git a/build.gradle b/build.gradle
index 5aee62970..ed2007312 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,15 +12,6 @@ group = "org.hibernate.reactive"
// leverage the ProjectVersion which comes from the `local.versions` plugin
version = project.projectVersion.fullName
-ext {
- if ( !project.hasProperty( 'hibernatePublishUsername' ) ) {
- hibernatePublishUsername = null
- }
- if ( !project.hasProperty( 'hibernatePublishPassword' ) ) {
- hibernatePublishPassword = null
- }
-}
-
// Versions which need to be aligned across modules; this also
// allows overriding the build using a parameter, which can be
// useful to monitor compatibility for upcoming versions on CI:
@@ -39,15 +30,10 @@ ext {
logger.lifecycle "Vert.x SQL Client Version: " + project.vertxSqlClientVersion
}
-// To release, see task ciRelease in release/build.gradle
-// To publish on Sonatype (Maven Central):
-// ./gradlew publishToSonatype closeAndReleaseStagingRepository -PhibernatePublishUsername="" -PhibernatePublishPassword=""
+// Publishing to Sonatype (Maven Central):
nexusPublishing {
repositories {
- sonatype {
- username = project.hibernatePublishUsername
- password = project.hibernatePublishPassword
- }
+ sonatype()
}
}
diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile
index 5fe408354..87e009d49 100644
--- a/ci/release/Jenkinsfile
+++ b/ci/release/Jenkinsfile
@@ -165,24 +165,18 @@ pipeline {
configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"),
configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts")
]) {
- withCredentials([
- usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USER'),
- usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'PLUGIN_PORTAL_PASSWORD', usernameVariable: 'PLUGIN_PORTAL_USERNAME'),
- file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'),
- string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE')
- ]) {
- sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) {
- // set release version
- // update changelog from JIRA
- // tags the version
- // changes the version to the provided development version
- withEnv([
- "BRANCH=${env.GIT_BRANCH}",
- // Increase the amount of memory for this part since asciidoctor doc rendering consumes a lot of metaspace
- "GRADLE_OPTS=-Dorg.gradle.jvmargs='-Dlog4j2.disableJmx -Xmx4g -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8'"
- ]) {
- sh ".release/scripts/prepare-release.sh ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION}"
- }
+
+ sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) {
+ // set release version
+ // update changelog from JIRA
+ // tags the version
+ // changes the version to the provided development version
+ withEnv([
+ "BRANCH=${env.GIT_BRANCH}",
+ // Increase the amount of memory for this part since asciidoctor doc rendering consumes a lot of metaspace
+ "GRADLE_OPTS=-Dorg.gradle.jvmargs='-Dlog4j2.disableJmx -Xmx4g -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8'"
+ ]) {
+ sh ".release/scripts/prepare-release.sh ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION}"
}
}
}
@@ -199,10 +193,10 @@ pipeline {
configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts")
]) {
withCredentials([
- usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USER'),
- usernamePassword(credentialsId: 'gradle-plugin-portal-api-key', passwordVariable: 'PLUGIN_PORTAL_PASSWORD', usernameVariable: 'PLUGIN_PORTAL_USERNAME'),
- file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'),
- string(credentialsId: 'release.gpg.passphrase', variable: 'RELEASE_GPG_PASSPHRASE')
+ // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh
+ usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_sonatypePassword', usernameVariable: 'ORG_GRADLE_PROJECT_sonatypeUsername'),
+ file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'),
+ string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE')
gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default')
]) {
sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) {
diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile
index 83fd95a4a..c2fba1d6b 100644
--- a/ci/snapshot-publish.Jenkinsfile
+++ b/ci/snapshot-publish.Jenkinsfile
@@ -31,15 +31,12 @@ pipeline {
stage('Publish') {
steps {
withCredentials([
- usernamePassword(credentialsId: 'ossrh.sonatype.org', usernameVariable: 'hibernatePublishUsername', passwordVariable: 'hibernatePublishPassword'),
+ // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh
+ usernamePassword(credentialsId: 'ossrh.sonatype.org', usernameVariable: 'ORG_GRADLE_PROJECT_sonatypeUsername', passwordVariable: 'ORG_GRADLE_PROJECT_sonatypePassword'),
file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'),
string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE')
]) {
- sh '''./gradlew clean publish \
- -PhibernatePublishUsername=$hibernatePublishUsername \
- -PhibernatePublishPassword=$hibernatePublishPassword \
- --no-scan \
- '''
+ sh "./gradlew clean publish --no-scan"
}
}
}
diff --git a/publish.gradle b/publish.gradle
index 39e0e16b2..0271197d4 100644
--- a/publish.gradle
+++ b/publish.gradle
@@ -5,8 +5,8 @@ apply plugin: 'signing'
// Java / publishing
java {
- // include javadoc and sources jar in the Java component
- // - classes jar included by default
+ // Configure the Java "software component" to include javadoc and sources jars in addition to the classes jar.
+ // Ultimately, this component is what makes up the publication for this project.
withJavadocJar()
withSourcesJar()
}
@@ -36,7 +36,7 @@ javadoc {
publishing {
publications {
- publishedArtifacts(MavenPublication) {
+ register( "publishedArtifacts", MavenPublication) {
from components.java
pom {
@@ -78,35 +78,39 @@ publishing {
}
-// signing
+// Signing
var signingExtension = project.getExtensions().getByType(SigningExtension) as SigningExtension
-// create a `signPublications` "grouping" task which will execute all Sign tasks
-def signPublicationsTask = tasks.register('signPublications')
+def signPublicationsTask = tasks.register('signPublications') {
+ description "Grouping task which executes all Sign tasks"
+ dependsOn tasks.withType( Sign )
+}
+
tasks.named( "publishPublishedArtifactsPublicationToSonatypeRepository" ) {
+ // publishing depends on signing
dependsOn signPublicationsTask
}
gradle.taskGraph.whenReady { TaskExecutionGraph graph ->
boolean wasSigningRequested = false
boolean wasPublishingRequested = false
- List signingTasks = []
graph.allTasks.each {task ->
- logger.lifecycle( "Checking task : $task" )
if ( task instanceof Sign ) {
- logger.lifecycle( " - Task is Sign" )
- signingTasks.add( task )
wasSigningRequested = true
}
else if ( task instanceof PublishToMavenRepository ) {
- logger.lifecycle( " - Task is PublishToMavenRepository" )
wasPublishingRequested = true
}
}
if ( wasPublishingRequested ) {
+ def publishUser = resolvePublishUser()
+ def publishPass = resolvePublishPass()
+ if ( publishUser == null || publishPass == null ) {
+ throw new RuntimeException( "Cannot perform publishing to OSSRH without credentials." )
+ }
logger.lifecycle "Publishing groupId: '" + project.group + "', version: '" + project.version + "'"
}
@@ -119,14 +123,47 @@ gradle.taskGraph.whenReady { TaskExecutionGraph graph ->
var signingPassword = resolveSigningPassphrase()
signingExtension.useInMemoryPgpKeys( signingKey, signingPassword )
signingExtension.sign publishing.publications.publishedArtifacts
-
- signPublicationsTask.get().dependsOn( signingTasks )
}
else {
// signing was not explicitly requested and we are not publishing to OSSRH,
// - disable all Sign tasks
- signingTasks.each { enabled = false }
+ tasks.withType( Sign ).each { enabled = false }
+ }
+}
+
+String resolvePublishUser() {
+ var envVar = System.getenv().get( "ORG_GRADLE_PROJECT_sonatypeUsername" )
+ if ( envVar != null ) {
+ return envVar
+ }
+
+ def projectProp = projectPropOrNull( "sonatypeUsername" )
+ if ( projectProp != null ) {
+ return projectProp
+ }
+
+ return null
+}
+
+String resolvePublishPass() {
+ var envVar = System.getenv().get( "ORG_GRADLE_PROJECT_sonatypePassword" )
+ if ( envVar != null ) {
+ return envVar
+ }
+
+ def projectProp = projectPropOrNull( "sonatypePassword" )
+ if ( projectProp != null ) {
+ return projectProp
+ }
+
+ return null
+}
+
+String projectPropOrNull(String name) {
+ if ( project.hasProperty( name ) ) {
+ return project.findProperty( name )
}
+ return null;
}
From b27d9d8eede55c37e3bae47aa3b9ade792af4144 Mon Sep 17 00:00:00 2001
From: Steve Ebersole
Date: Wed, 18 Dec 2024 12:01:46 -0600
Subject: [PATCH 006/162] [#1095] Sign the artifacts for Sonatype
---
publish.gradle | 81 +++++++++++++++++++-------------------------------
1 file changed, 31 insertions(+), 50 deletions(-)
diff --git a/publish.gradle b/publish.gradle
index 0271197d4..977092d9a 100644
--- a/publish.gradle
+++ b/publish.gradle
@@ -82,25 +82,19 @@ publishing {
var signingExtension = project.getExtensions().getByType(SigningExtension) as SigningExtension
-def signPublicationsTask = tasks.register('signPublications') {
- description "Grouping task which executes all Sign tasks"
- dependsOn tasks.withType( Sign )
-}
+var publishingExtension = project.getExtensions().getByType(PublishingExtension) as PublishingExtension
+signingExtension.sign publishingExtension.publications.publishedArtifacts
-tasks.named( "publishPublishedArtifactsPublicationToSonatypeRepository" ) {
- // publishing depends on signing
- dependsOn signPublicationsTask
-}
+var signingKey = resolveSigningKey()
+var signingPassphrase = resolveSigningPassphrase()
+signingExtension.useInMemoryPgpKeys(signingKey, signingPassphrase)
gradle.taskGraph.whenReady { TaskExecutionGraph graph ->
- boolean wasSigningRequested = false
boolean wasPublishingRequested = false
graph.allTasks.each {task ->
- if ( task instanceof Sign ) {
- wasSigningRequested = true
- }
- else if ( task instanceof PublishToMavenRepository ) {
+ if ( task instanceof PublishToMavenRepository ) {
+ logger.lifecycle( "Found PublishToMavenRepository task : {}", task.path )
wasPublishingRequested = true
}
}
@@ -111,24 +105,34 @@ gradle.taskGraph.whenReady { TaskExecutionGraph graph ->
if ( publishUser == null || publishPass == null ) {
throw new RuntimeException( "Cannot perform publishing to OSSRH without credentials." )
}
- logger.lifecycle "Publishing groupId: '" + project.group + "', version: '" + project.version + "'"
- }
- if ( wasSigningRequested || wasPublishingRequested ) {
- // signing was explicitly requested and/or we are publishing to Sonatype OSSRH
- // - we need the signing to happen
+ logger.lifecycle "Publishing {} : {} : {}", project.group, project.name, project.version
+
+ // require signing if publishing to OSSRH
signingExtension.required = true
+ }
+ else if ( signingKey == null || signingPassphrase == null ) {
+ tasks.withType( Sign ).each { t-> t.enabled = false }
+ }
+}
+
- var signingKey = resolveSigningKey()
- var signingPassword = resolveSigningPassphrase()
- signingExtension.useInMemoryPgpKeys( signingKey, signingPassword )
- signingExtension.sign publishing.publications.publishedArtifacts
+static String resolveSigningKey() {
+ var key = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY" )
+ if ( key != null ) {
+ return key
}
- else {
- // signing was not explicitly requested and we are not publishing to OSSRH,
- // - disable all Sign tasks
- tasks.withType( Sign ).each { enabled = false }
+
+ var keyFile = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY_PATH" )
+ if ( keyFile != null ) {
+ return new File( keyFile ).text
}
+
+ return null
+}
+
+static String resolveSigningPassphrase() {
+ return System.getenv().get( "SIGNING_GPG_PASSPHRASE" )
}
String resolvePublishUser() {
@@ -164,27 +168,4 @@ String projectPropOrNull(String name) {
return project.findProperty( name )
}
return null;
-}
-
-
-static String resolveSigningKey() {
- var key = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY" )
- if ( key != null ) {
- return key
- }
-
- var keyFile = System.getenv().get( "SIGNING_GPG_PRIVATE_KEY_PATH" )
- if ( keyFile != null ) {
- return new File( keyFile ).text
- }
-
- throw new RuntimeException( "Cannot perform signing without GPG details." )
-}
-
-static String resolveSigningPassphrase() {
- var passphrase = System.getenv().get( "SIGNING_GPG_PASSPHRASE" )
- if ( passphrase == null ) {
- throw new RuntimeException( "Cannot perform signing without GPG details." )
- }
- return passphrase
-}
+}
\ No newline at end of file
From c16976cd4cec1f24994995ff90371e51e65580cd Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Fri, 20 Dec 2024 17:46:36 +0100
Subject: [PATCH 007/162] [#1095] Add missing comma to JenkinsFile
---
ci/release/Jenkinsfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile
index 87e009d49..87d2676c1 100644
--- a/ci/release/Jenkinsfile
+++ b/ci/release/Jenkinsfile
@@ -196,7 +196,7 @@ pipeline {
// https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh
usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_sonatypePassword', usernameVariable: 'ORG_GRADLE_PROJECT_sonatypeUsername'),
file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_GPG_PRIVATE_KEY_PATH'),
- string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE')
+ string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_GPG_PASSPHRASE'),
gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default')
]) {
sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) {
From 8db37dd4d79c1c8e40e5d63e11b5bea9d3181592 Mon Sep 17 00:00:00 2001
From: Hibernate-CI
Date: Fri, 20 Dec 2024 16:49:09 +0000
Subject: [PATCH 008/162] Update project version to : `2.4.3.Final`
---
gradle/version.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle/version.properties b/gradle/version.properties
index dd20a5fa4..7eac1a461 100644
--- a/gradle/version.properties
+++ b/gradle/version.properties
@@ -1 +1 @@
-projectVersion=2.4.3-SNAPSHOT
\ No newline at end of file
+projectVersion=2.4.3.Final
\ No newline at end of file
From 3c4607a9b0e61cf96a5ee5496cecd6420cb2e8e4 Mon Sep 17 00:00:00 2001
From: Hibernate-CI
Date: Fri, 20 Dec 2024 16:50:08 +0000
Subject: [PATCH 009/162] Update project version to : `2.4.4-SNAPSHOT`
---
gradle/version.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle/version.properties b/gradle/version.properties
index 7eac1a461..47693ff21 100644
--- a/gradle/version.properties
+++ b/gradle/version.properties
@@ -1 +1 @@
-projectVersion=2.4.3.Final
\ No newline at end of file
+projectVersion=2.4.4-SNAPSHOT
\ No newline at end of file
From 91fc13658b7d75675cdd6ece6766ea0d95c5e82c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?=
Date: Tue, 14 Jan 2025 09:20:07 +0100
Subject: [PATCH 010/162] [#2063] Use specific Jenkins nodes for releases
This should be safer as these nodes are only used once.
---
ci/release/Jenkinsfile | 2 +-
ci/snapshot-publish.Jenkinsfile | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile
index 87d2676c1..80b89bf9f 100644
--- a/ci/release/Jenkinsfile
+++ b/ci/release/Jenkinsfile
@@ -58,7 +58,7 @@ def checkoutReleaseScripts() {
pipeline {
agent {
- label 'Worker&&Containers'
+ label 'Release'
}
tools {
jdk 'OpenJDK 11 Latest'
diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile
index c2fba1d6b..c9e19ca4c 100644
--- a/ci/snapshot-publish.Jenkinsfile
+++ b/ci/snapshot-publish.Jenkinsfile
@@ -12,7 +12,7 @@ if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) {
pipeline {
agent {
- label 'Fedora'
+ label 'Release'
}
tools {
jdk 'OpenJDK 11 Latest'
From e160db5f879f4907f190c651f2483b0e191214e0 Mon Sep 17 00:00:00 2001
From: Andrea Boriero
Date: Fri, 17 Jan 2025 10:31:55 +0100
Subject: [PATCH 011/162] [#2026] UnexpectedAccessToTheDatabase error when
merging a detached entity with a ToMany association
---
.../reactive/engine/impl/CollectionTypes.java | 516 ++++++++++++++++++
.../reactive/engine/impl/EntityTypes.java | 215 +++++---
.../hibernate/reactive/logging/impl/Log.java | 4 +
3 files changed, 661 insertions(+), 74 deletions(-)
create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
new file mode 100644
index 000000000..ab9d02d08
--- /dev/null
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
@@ -0,0 +1,516 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.engine.impl;
+
+import java.io.Serializable;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.concurrent.CompletionStage;
+
+import org.hibernate.Hibernate;
+import org.hibernate.HibernateException;
+import org.hibernate.collection.spi.AbstractPersistentCollection;
+import org.hibernate.collection.spi.PersistentArrayHolder;
+import org.hibernate.collection.spi.PersistentCollection;
+import org.hibernate.engine.spi.CollectionEntry;
+import org.hibernate.engine.spi.PersistenceContext;
+import org.hibernate.engine.spi.SessionImplementor;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.metamodel.mapping.PluralAttributeMapping;
+import org.hibernate.persister.collection.CollectionPersister;
+import org.hibernate.reactive.logging.impl.Log;
+import org.hibernate.reactive.logging.impl.LoggerFactory;
+import org.hibernate.type.ArrayType;
+import org.hibernate.type.CollectionType;
+import org.hibernate.type.CustomCollectionType;
+import org.hibernate.type.EntityType;
+import org.hibernate.type.ForeignKeyDirection;
+import org.hibernate.type.MapType;
+import org.hibernate.type.Type;
+
+import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY;
+import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize;
+import static org.hibernate.pretty.MessageHelper.collectionInfoString;
+import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
+import static org.hibernate.reactive.util.impl.CompletionStages.loop;
+import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture;
+import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;
+
+/**
+ * Reactive operations that really belong to {@link CollectionType}
+ *
+ */
+public class CollectionTypes {
+ private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() );
+
+ /**
+ * @see org.hibernate.type.AbstractType#replace(Object, Object, SharedSessionContractImplementor, Object, Map, ForeignKeyDirection)
+ */
+ public static CompletionStage
+ // One thing to be careful of here is a "bare" original collection
+ // in which case we should never ever ever reset the dirty flag
+ // on the target because we simply do not know...
+ if ( original instanceof PersistentCollection>
+ && result instanceof PersistentCollection> ) {
+ PersistentCollection> resultPersistentCollection = (PersistentCollection>) result;
+ PersistentCollection> originalPersistentCollection = (PersistentCollection>) original;
+ return preserveSnapshot(
+ originalPersistentCollection, resultPersistentCollection,
+ elemType, owner, copyCache, session
+ ).thenApply( v -> {
+ if ( !originalPersistentCollection.isDirty() ) {
+ resultPersistentCollection.clearDirty();
+ }
+ return result;
+ } );
+ }
+ else {
+ return completedFuture( result );
+ }
+ } );
+ }
+
+ private static CompletionStage replaceMapTypeElements(
+ CollectionType type,
+ Map original,
+ Map target,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final CollectionPersister persister =
+ session.getFactory().getRuntimeMetamodels().getMappingMetamodel()
+ .getCollectionDescriptor( type.getRole() );
+
+ final Map result = target;
+ result.clear();
+
+ return loop(
+ original.entrySet(), entry -> {
+ final Map.Entry me = entry;
+ return getReplace( persister.getIndexType(), me.getKey(), owner, session, copyCache )
+ .thenCompose( key ->
+ getReplace(
+ persister.getElementType(),
+ me.getValue(),
+ owner,
+ session,
+ copyCache
+ ).thenAccept( value ->
+ result.put( key, value ) )
+ );
+ }
+ ).thenApply( unused -> result );
+ }
+
+ private static CompletionStage replaceArrayTypeElements(
+ CollectionType type,
+ Object original,
+ Object target,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final Object result;
+ final int length = Array.getLength( original );
+ if ( length != Array.getLength( target ) ) {
+ //note: this affects the return value!
+ result = ( (ArrayType) type ).instantiateResult( original );
+ }
+ else {
+ result = target;
+ }
+
+ final Type elemType = type.getElementType( session.getFactory() );
+ return loop(
+ 0, length, i -> {
+ return getReplace( elemType, Array.get( original, i ), owner, session, copyCache )
+ .thenApply( o -> {
+ Array.set( result, i, o );
+ return result;
+ }
+ );
+ }
+ ).thenApply( unused -> result );
+ }
+
+ private static CompletionStage getReplace(
+ Type elemType,
+ Object o,
+ Object owner,
+ SessionImplementor session,
+ Map copyCache) {
+ return getReplace( elemType, o, null, owner, session, copyCache );
+ }
+
+ private static CompletionStage getReplace(
+ Type elemType,
+ Object o,
+ Object target,
+ Object owner,
+ SessionImplementor session,
+ Map copyCache) {
+ if ( elemType instanceof EntityType ) {
+ return EntityTypes.replace( (EntityType) elemType, o, target, session, owner, copyCache );
+ }
+ else {
+ final Object replace = elemType.replace( o, target, session, owner, copyCache );
+ return completedFuture( replace );
+ }
+ }
+
+ /**
+ * @see CollectionType#preserveSnapshot(PersistentCollection, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage preserveSnapshot(
+ PersistentCollection> original,
+ PersistentCollection> result,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( result );
+ if ( ce != null ) {
+ return createSnapshot( original, result, elemType, owner, copyCache, session )
+ .thenAccept( serializable ->
+ ce.resetStoredSnapshot( result, serializable ) );
+ }
+ return voidFuture();
+ }
+
+ /**
+ * @see CollectionType#createSnapshot(PersistentCollection, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createSnapshot(
+ PersistentCollection> original,
+ PersistentCollection> result,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final Serializable originalSnapshot = original.getStoredSnapshot();
+ if ( originalSnapshot instanceof List> ) {
+ List> list = (List>) originalSnapshot;
+ return createListSnapshot( list, elemType, owner, copyCache, session );
+ }
+ else if ( originalSnapshot instanceof Map, ?> ) {
+ Map, ?> map = (Map, ?>) originalSnapshot;
+ return createMapSnapshot( map, result, elemType, owner, copyCache, session );
+ }
+ else if ( originalSnapshot instanceof Object[] ) {
+ Object[] array = (Object[]) originalSnapshot;
+ return createArraySnapshot( array, elemType, owner, copyCache, session );
+ }
+ else {
+ // retain the same snapshot
+ return completedFuture( result.getStoredSnapshot() );
+ }
+ }
+
+ /**
+ * @see CollectionType#createArraySnapshot(Object[], Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createArraySnapshot(
+ Object[] array,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ return loop(
+ 0, array.length,
+ i ->
+ getReplace( elemType, array[i], owner, session, copyCache )
+ .thenCompose( o -> {
+ array[i] = o;
+ return voidFuture();
+ }
+ )
+ ).thenApply( unused -> array );
+ }
+
+ /**
+ * @see CollectionType#createMapSnapshot(Map, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createMapSnapshot(
+ Map, ?> map,
+ PersistentCollection> result,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final Map, ?> resultSnapshot = (Map, ?>) result.getStoredSnapshot();
+ final Map targetMap;
+ if ( map instanceof SortedMap, ?> ) {
+ SortedMap, ?> sortedMap = (SortedMap, ?>) map;
+ //noinspection unchecked, rawtypes
+ targetMap = new TreeMap( sortedMap.comparator() );
+ }
+ else {
+ targetMap = mapOfSize( map.size() );
+ }
+ return loop(
+ map.entrySet(), entry ->
+ getReplace( elemType, entry.getValue(), resultSnapshot, owner, session, copyCache )
+ .thenCompose( newValue -> {
+ final Object key = entry.getKey();
+ targetMap.put( key == entry.getValue() ? newValue : key, newValue );
+ return voidFuture();
+ } )
+ ).thenApply( v -> (Serializable) targetMap );
+ }
+
+ /**
+ * @see CollectionType#createListSnapshot(List, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createListSnapshot(
+ List> list,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final ArrayList targetList = new ArrayList<>( list.size() );
+ return loop(
+ list, obj ->
+ getReplace( elemType, obj, owner, session, copyCache )
+ .thenCompose( o -> {
+ targetList.add( o );
+ return voidFuture();
+ } )
+ ).thenApply( unused -> targetList );
+ }
+
+ /**
+ * @see CollectionType#instantiateResultIfNecessary(Object, Object)
+ */
+ private static Object instantiateResultIfNecessary(CollectionType type, Object original, Object target) {
+ // for a null target, or a target which is the same as the original,
+ // we need to put the merged elements in a new collection
+ // by default just use an unanticipated capacity since we don't
+ // know how to extract the capacity to use from original here...
+ return target == null
+ || target == original
+ || target == UNFETCHED_PROPERTY
+ || target instanceof PersistentCollection> && ( (PersistentCollection>) target).isWrapper( original ) ?
+ type.instantiate( -1 ) :
+ target;
+ }
+
+
+}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
index 879f7ddc8..722ba4a78 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
@@ -25,6 +25,7 @@
import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup;
import org.hibernate.reactive.session.impl.ReactiveSessionImpl;
+import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.OneToOneType;
@@ -158,42 +159,11 @@ public static CompletionStage replace(
final Object owner,
final Map copyCache) {
Object[] copied = new Object[original.length];
- for ( int i = 0; i < types.length; i++ ) {
- if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
- copied[i] = target[i];
- }
- else {
- if ( !( types[i] instanceof EntityType ) ) {
- copied[i] = types[i].replace(
- original[i],
- target[i] == UNFETCHED_PROPERTY ? null : target[i],
- session,
- owner,
- copyCache
- );
- }
- }
- }
- return loop( 0, types.length,
- i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN
- && types[i] instanceof EntityType,
- i -> replace(
- (EntityType) types[i],
- original[i],
- target[i] == UNFETCHED_PROPERTY ? null : target[i],
- session,
- owner,
- copyCache
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy )
- .thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } )
+ return loop(
+ 0, types.length,
+ i ->
+ replace( original, target, types, session, owner, copyCache, i, copied )
+
).thenApply( v -> copied );
}
@@ -209,43 +179,11 @@ public static CompletionStage replace(
final Map copyCache,
final ForeignKeyDirection foreignKeyDirection) {
Object[] copied = new Object[original.length];
- for ( int i = 0; i < types.length; i++ ) {
- if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
- copied[i] = target[i];
- }
- else {
- if ( !( types[i] instanceof EntityType ) ) {
- copied[i] = types[i].replace(
- original[i],
- target[i] == UNFETCHED_PROPERTY ? null : target[i],
- session,
- owner,
- copyCache,
- foreignKeyDirection
- );
- }
- }
- }
- return loop( 0, types.length,
- i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN
- && types[i] instanceof EntityType,
- i -> replace(
- (EntityType) types[i],
- original[i],
- target[i] == UNFETCHED_PROPERTY ? null : target[i],
- session,
- owner,
- copyCache,
- foreignKeyDirection
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } )
+ return loop(
+ 0, types.length,
+ i ->
+ replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied )
+
).thenApply( v -> copied );
}
@@ -272,7 +210,7 @@ private static CompletionStage replace(
/**
* @see EntityType#replace(Object, Object, SharedSessionContractImplementor, Object, Map)
*/
- private static CompletionStage replace(
+ protected static CompletionStage replace(
EntityType entityType,
Object original,
Object target,
@@ -450,4 +388,133 @@ private static CompletionStage loadHibernateProxyEntity(
}
}
+ private static CompletionStage replace(
+ Object[] original,
+ Object[] target,
+ Type[] types,
+ SessionImplementor session,
+ Object owner,
+ Map copyCache,
+ int i,
+ Object[] copied) {
+ if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
+ copied[i] = target[i];
+ return voidFuture();
+ }
+ else if ( types[i] instanceof CollectionType ) {
+ return CollectionTypes.replace(
+ (CollectionType) types[i],
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache
+ ).thenCompose( copy -> {
+ if ( copy instanceof CompletionStage ) {
+ return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
+ }
+ else {
+ copied[i] = copy;
+ return voidFuture();
+ }
+ } );
+ }
+ else if ( types[i] instanceof EntityType ) {
+ return replace(
+ (EntityType) types[i],
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache
+ ).thenCompose( copy -> {
+ if ( copy instanceof CompletionStage ) {
+ return ( (CompletionStage>) copy )
+ .thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
+ }
+ else {
+ copied[i] = copy;
+ return voidFuture();
+ }
+ } );
+ }
+ else {
+ final Type type = types[i];
+ copied[i] = type.replace(
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache
+ );
+ return voidFuture();
+ }
+ }
+
+ private static CompletionStage replace(
+ Object[] original,
+ Object[] target,
+ Type[] types,
+ SessionImplementor session,
+ Object owner,
+ Map copyCache,
+ ForeignKeyDirection foreignKeyDirection,
+ int i,
+ Object[] copied) {
+ if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
+ copied[i] = target[i];
+ return voidFuture();
+ }
+ else if ( types[i] instanceof CollectionType ) {
+ return CollectionTypes.replace(
+ (CollectionType) types[i],
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache,
+ foreignKeyDirection
+ ).thenCompose( copy -> {
+ if ( copy instanceof CompletionStage ) {
+ return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
+ }
+ else {
+ copied[i] = copy;
+ return voidFuture();
+ }
+ } );
+ }
+ else if ( types[i] instanceof EntityType ) {
+ return replace(
+ (EntityType) types[i],
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache,
+ foreignKeyDirection
+ ).thenCompose( copy -> {
+ if ( copy instanceof CompletionStage ) {
+ return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
+ }
+ else {
+ copied[i] = copy;
+ return voidFuture();
+ }
+ } );
+ }
+ else {
+ copied[i] = types[i].replace(
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache,
+ foreignKeyDirection
+ );
+ return voidFuture();
+ }
+ }
+
+
}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java
index 72760a26b..fef169431 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java
@@ -300,4 +300,8 @@ public interface Log extends BasicLogger {
@LogMessage(level = WARN)
@Message(id = 448, value = "Warnings creating temp table : %s")
void warningsCreatingTempTable(SQLWarning warning);
+
+ @LogMessage(level = WARN)
+ @Message( id= 494, value = "Attempt to merge an uninitialized collection with queued operations; queued operations will be ignored: %s")
+ void ignoreQueuedOperationsOnMerge(String collectionInfoString);
}
From 665a3405e78bbe05713b6afcd91f484d7ba2b039 Mon Sep 17 00:00:00 2001
From: Andrea Boriero
Date: Mon, 20 Jan 2025 10:48:36 +0100
Subject: [PATCH 012/162] [#2026] Add test for UnexpectedAccessToTheDatabase
error when merging a detached entity with a ToMany association
---
.../reactive/OneToManyArrayMergeTest.java | 190 +++++++++++++++
.../reactive/OneToManyMapMergeTest.java | 199 +++++++++++++++
.../reactive/OneToManyMergeTest.java | 227 ++++++++++++++++++
3 files changed, 616 insertions(+)
create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java
create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java
create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java
new file mode 100644
index 000000000..e2eb826d9
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java
@@ -0,0 +1,190 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.vertx.junit5.Timeout;
+import io.vertx.junit5.VertxTestContext;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Timeout(value = 2, timeUnit = MINUTES)
+public class OneToManyArrayMergeTest extends BaseReactiveTest {
+
+ private final static Long USER_ID = 1L;
+ private final static Long ADMIN_ROLE_ID = 2L;
+ private final static Long USER_ROLE_ID = 3L;
+ private final static String UPDATED_FIRSTNAME = "UPDATED FIRSTNAME";
+ private final static String UPDATED_LASTNAME = "UPDATED LASTNAME";
+
+ @Override
+ protected Collection> annotatedEntities() {
+ return List.of( User.class, Role.class );
+ }
+
+ @BeforeEach
+ public void populateDb(VertxTestContext context) {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ User user = new User( USER_ID, "first", "last", adminRole );
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.persistAll( user, adminRole, userRole ) )
+ );
+ }
+
+ @Test
+ public void testMerge(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ .chain( user -> getMutinySessionFactory()
+ .withTransaction( s -> s
+ .createQuery( "FROM Role", Role.class )
+ .getResultList() )
+ .map( roles -> {
+ user.addAll( roles );
+ user.setFirstname( UPDATED_FIRSTNAME );
+ user.setLastname( UPDATED_LASTNAME );
+ return user;
+ } )
+ )
+ .chain( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).hasSize( 2 );
+ return getMutinySessionFactory()
+ .withTransaction( s -> s.merge( user ) );
+ }
+ )
+ .chain( v -> getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ )
+ .invoke( user -> {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).containsExactlyInAnyOrder(
+ adminRole,
+ userRole
+ );
+ }
+ )
+ );
+ }
+
+ @Entity(name = "User")
+ @Table(name = "USER_TABLE")
+ public static class User {
+
+ @Id
+ private Long id;
+
+ private String firstname;
+
+ private String lastname;
+
+ @OneToMany(fetch = FetchType.EAGER)
+ private Role[] roles;
+
+ public User() {
+ }
+
+ public User(Long id, String firstname, String lastname, Role... roles) {
+ this.id = id;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ this.roles = new Role[roles.length];
+ for ( int i = 0; i < roles.length; i++ ) {
+ this.roles[i] = roles[i];
+ }
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public Role[] getRoles() {
+ return roles;
+ }
+
+ public void addAll(List roles) {
+ this.roles = new Role[roles.size()];
+ for ( int i = 0; i < roles.size(); i++ ) {
+ this.roles[i] = roles.get( i );
+ }
+ }
+ }
+
+ @Entity(name = "Role")
+ @Table(name = "ROLE_TABLE")
+ public static class Role {
+
+ @Id
+ private Long id;
+ private String code;
+
+ public Role() {
+ }
+
+ public Role(Long id, String code) {
+ this.id = id;
+ this.code = code;
+ }
+
+ public Object getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ Role role = (Role) o;
+ return Objects.equals( id, role.id ) && Objects.equals( code, role.code );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash( id, code );
+ }
+
+ @Override
+ public String toString() {
+ return "Role{" + code + '}';
+ }
+ }
+}
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java
new file mode 100644
index 000000000..7a1096d3d
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java
@@ -0,0 +1,199 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.vertx.junit5.Timeout;
+import io.vertx.junit5.VertxTestContext;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Timeout(value = 2, timeUnit = MINUTES)
+public class OneToManyMapMergeTest extends BaseReactiveTest {
+
+ private final static Long USER_ID = 1L;
+ private final static Long ADMIN_ROLE_ID = 2L;
+ private final static Long USER_ROLE_ID = 3L;
+ private final static String UPDATED_FIRSTNAME = "UPDATED FIRSTNAME";
+ private final static String UPDATED_LASTNAME = "UPDATED LASTNAME";
+
+ @Override
+ protected Collection> annotatedEntities() {
+ return List.of( User.class, Role.class );
+ }
+
+ @BeforeEach
+ public void populateDb(VertxTestContext context) {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ User user = new User( USER_ID, "first", "last", adminRole );
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.persistAll( user, adminRole, userRole ) )
+ );
+ }
+
+ @Test
+ public void testMerge(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ .chain( user -> getMutinySessionFactory()
+ .withTransaction( s -> s
+ .createQuery( "FROM Role", Role.class )
+ .getResultList() )
+ .map( roles -> {
+ user.addAll( roles );
+ user.setFirstname( UPDATED_FIRSTNAME );
+ user.setLastname( UPDATED_LASTNAME );
+ return user;
+ } )
+ )
+ .chain( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).hasSize( 2 );
+ return getMutinySessionFactory()
+ .withTransaction( s -> s.merge( user ) );
+ }
+ )
+ .chain( v -> getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ )
+ .invoke( user -> {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).containsEntry(
+ adminRole.getCode(),
+ adminRole
+ );
+ assertThat( user.getRoles() ).containsEntry(
+ userRole.getCode(),
+ userRole
+ );
+ }
+ )
+ );
+ }
+
+ @Entity(name = "User")
+ @Table(name = "USER_TABLE")
+ public static class User {
+
+ @Id
+ private Long id;
+
+ private String firstname;
+
+ private String lastname;
+
+ @OneToMany(fetch = FetchType.EAGER)
+ private Map roles = new HashMap();
+
+ public User() {
+ }
+
+ public User(Long id, String firstname, String lastname, Role... roles) {
+ this.id = id;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ for ( Role role : roles ) {
+ this.roles.put( role.getCode(), role );
+ }
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public Map getRoles() {
+ return roles;
+ }
+
+ public void addAll(List roles) {
+ this.roles.clear();
+ for ( Role role : roles ) {
+ this.roles.put( role.getCode(), role );
+ }
+ }
+ }
+
+ @Entity(name = "Role")
+ @Table(name = "ROLE_TABLE")
+ public static class Role {
+
+ @Id
+ private Long id;
+ private String code;
+
+ public Role() {
+ }
+
+ public Role(Long id, String code) {
+ this.id = id;
+ this.code = code;
+ }
+
+ public Object getId() {
+ return id;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ Role role = (Role) o;
+ return Objects.equals( id, role.id ) && Objects.equals( code, role.code );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash( id, code );
+ }
+
+ @Override
+ public String toString() {
+ return "Role{" + code + '}';
+ }
+ }
+}
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java
new file mode 100644
index 000000000..76d7f7732
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java
@@ -0,0 +1,227 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.vertx.junit5.Timeout;
+import io.vertx.junit5.VertxTestContext;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Timeout(value = 2, timeUnit = MINUTES)
+public class OneToManyMergeTest extends BaseReactiveTest {
+
+ private final static Long USER_ID = 1L;
+ private final static Long ADMIN_ROLE_ID = 2L;
+ private final static Long USER_ROLE_ID = 3L;
+ private final static String UPDATED_FIRSTNAME = "UPDATED FIRSTNAME";
+ private final static String UPDATED_LASTNAME = "UPDATED LASTNAME";
+
+ @Override
+ protected Collection> annotatedEntities() {
+ return List.of( User.class, Role.class );
+ }
+
+ @BeforeEach
+ public void populateDb(VertxTestContext context) {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ User user = new User( USER_ID, "first", "last", adminRole );
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.persistAll( user, adminRole, userRole ) )
+ );
+ }
+
+ @Test
+ public void testMerge(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ .chain( user -> getMutinySessionFactory()
+ .withTransaction( s -> s
+ .createQuery( "FROM Role", Role.class )
+ .getResultList() )
+ .map( roles -> {
+ user.getRoles().clear();
+ user.getRoles().addAll( roles );
+ user.setFirstname( UPDATED_FIRSTNAME );
+ user.setLastname( UPDATED_LASTNAME );
+ return user;
+ } )
+ )
+ .chain( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).hasSize( 2 );
+ return getMutinySessionFactory()
+ .withTransaction( s -> s.merge( user ) );
+ }
+ )
+ .chain( v -> getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ )
+ .invoke( user -> {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).containsExactlyInAnyOrder(
+ adminRole,
+ userRole
+ );
+ }
+ )
+ );
+ }
+
+ @Test
+ public void testMergeRemovingCollectionElements(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ .chain( user -> getMutinySessionFactory()
+ .withTransaction( s -> s
+ .createQuery( "FROM Role", Role.class )
+ .getResultList() )
+ .map( roles -> {
+ user.clearRoles();
+ user.setFirstname( UPDATED_FIRSTNAME );
+ user.setLastname( UPDATED_LASTNAME );
+ return user;
+ } )
+ )
+ .chain( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).isNull();
+ return getMutinySessionFactory()
+ .withTransaction( s -> s.merge( user ) );
+ }
+ )
+ .chain( v -> getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ )
+ .invoke( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).isNullOrEmpty();
+ }
+ )
+ );
+ }
+
+ @Entity(name = "User")
+ @Table(name = "USER_TABLE")
+ public static class User {
+
+ @Id
+ private Long id;
+
+ private String firstname;
+
+ private String lastname;
+
+ @OneToMany(fetch = FetchType.EAGER)
+ private List roles;
+
+ public User() {
+ }
+
+ public User(Long id, String firstname, String lastname, Role... roles) {
+ this.id = id;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ this.roles = List.of( roles );
+ }
+
+ public User(Long id, String firstname, String lastname) {
+ this.id = id;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void clearRoles() {
+ this.roles = null;
+ }
+ }
+
+ @Entity(name = "Role")
+ @Table(name = "ROLE_TABLE")
+ public static class Role {
+
+ @Id
+ private Long id;
+ private String code;
+
+ public Role() {
+ }
+
+ public Role(Long id, String code) {
+ this.id = id;
+ this.code = code;
+ }
+
+ public Object getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ Role role = (Role) o;
+ return Objects.equals( id, role.id ) && Objects.equals( code, role.code );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash( id, code );
+ }
+
+ @Override
+ public String toString() {
+ return "Role{" + code + '}';
+ }
+ }
+}
From d2e8e8f3a0e0f2d62833421371384ce52254a6fc Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Mon, 20 Jan 2025 11:57:06 +0100
Subject: [PATCH 013/162] [#2026] Fix EntityTypes#loadByUniqueKey
It's using a `.thenApply` instead of a `.thenCompose`.
---
.../reactive/engine/impl/EntityTypes.java | 47 +++----------------
1 file changed, 7 insertions(+), 40 deletions(-)
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
index 722ba4a78..d5f37dba3 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
@@ -138,7 +138,7 @@ static CompletionStage loadByUniqueKey(
else {
return persister
.reactiveLoadByUniqueKey( uniqueKeyPropertyName, key, session )
- .thenApply( ukResult -> loadHibernateProxyEntity( ukResult, session )
+ .thenCompose( ukResult -> loadHibernateProxyEntity( ukResult, session )
.thenApply( targetUK -> {
persistenceContext.addEntity( euk, targetUK );
return targetUK;
@@ -364,9 +364,9 @@ private static CompletionStage getIdentifierFromHibernateProxy(
if ( type.isEntityIdentifierMapping() ) {
propertyValue = getIdentifier( (EntityType) type, propertyValue, (SessionImplementor) session );
}
- return completedFuture( propertyValue );
+ return propertyValue;
}
- return nullFuture();
+ return null;
} );
}
@@ -409,15 +409,7 @@ else if ( types[i] instanceof CollectionType ) {
session,
owner,
copyCache
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } );
+ ).thenAccept( copy -> copied[i] = copy );
}
else if ( types[i] instanceof EntityType ) {
return replace(
@@ -427,16 +419,7 @@ else if ( types[i] instanceof EntityType ) {
session,
owner,
copyCache
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy )
- .thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } );
+ ).thenAccept( copy -> copied[i] = copy );
}
else {
final Type type = types[i];
@@ -474,15 +457,7 @@ else if ( types[i] instanceof CollectionType ) {
owner,
copyCache,
foreignKeyDirection
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } );
+ ).thenAccept( copy -> copied[i] = copy );
}
else if ( types[i] instanceof EntityType ) {
return replace(
@@ -493,15 +468,7 @@ else if ( types[i] instanceof EntityType ) {
owner,
copyCache,
foreignKeyDirection
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } );
+ ).thenAccept( copy -> copied[i] = copy );
}
else {
copied[i] = types[i].replace(
From af3d4f99828f068c4fe41d02fe6bdb51dbdff4ae Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Mon, 20 Jan 2025 12:03:22 +0100
Subject: [PATCH 014/162] [#2026] Small refactoring and clean ups
---
.../reactive/engine/impl/CollectionTypes.java | 96 +++++++------------
.../reactive/engine/impl/EntityTypes.java | 16 ++--
2 files changed, 41 insertions(+), 71 deletions(-)
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
index ab9d02d08..41dd0162b 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
@@ -42,7 +42,6 @@
import static org.hibernate.pretty.MessageHelper.collectionInfoString;
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
import static org.hibernate.reactive.util.impl.CompletionStages.loop;
-import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture;
import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;
/**
@@ -81,10 +80,10 @@ public static CompletionStage replace(
Object owner,
Map copyCache) throws HibernateException {
if ( original == null ) {
- return replaceNullOriginal( target, session );
+ return completedFuture( replaceNullOriginal( target, session ) );
}
else if ( !Hibernate.isInitialized( original ) ) {
- return replaceUninitializedOriginal( type, original, target, session, copyCache );
+ return completedFuture( replaceUninitializedOriginal( type, original, target, session, copyCache ) );
}
else {
return replaceOriginal( type, original, target, session, owner, copyCache );
@@ -92,25 +91,24 @@ else if ( !Hibernate.isInitialized( original ) ) {
}
// todo: make org.hibernate.type.CollectionType#replaceNullOriginal public ?
-
/**
* @see CollectionType#replaceNullOriginal(Object, SharedSessionContractImplementor)
*/
- private static CompletionStage replaceNullOriginal(
+ private static Object replaceNullOriginal(
Object target,
SessionImplementor session) {
if ( target == null ) {
- return nullFuture();
+ return null;
}
else if ( target instanceof Collection> ) {
Collection> collection = (Collection>) target;
collection.clear();
- return completedFuture( collection );
+ return collection;
}
else if ( target instanceof Map, ?> ) {
Map, ?> map = (Map, ?>) target;
map.clear();
- return completedFuture( map );
+ return map;
}
else {
final PersistenceContext persistenceContext = session.getPersistenceContext();
@@ -127,18 +125,15 @@ else if ( target instanceof Map, ?> ) {
arrayHolder.endRead();
arrayHolder.dirty();
persistenceContext.addCollectionHolder( collectionHolder );
- return completedFuture( arrayHolder.getArray() );
+ return arrayHolder.getArray();
}
}
}
- return nullFuture();
+ return null;
}
// todo: make org.hibernate.type.CollectionType#replaceUninitializedOriginal public
- /**
- * @see CollectionType#replaceNullOriginal(Object, SharedSessionContractImplementor)
- */
- private static CompletionStage replaceUninitializedOriginal(
+ private static Object replaceUninitializedOriginal(
CollectionType type,
Object original,
Object target,
@@ -164,7 +159,7 @@ private static CompletionStage replaceUninitializedOriginal(
collectionInfoString( type.getRole(), persistentCollection.getKey() ) );
}
}
- return completedFuture( target );
+ return target;
}
/**
@@ -196,11 +191,11 @@ private static CompletionStage replaceOriginal(
//TODO: this is a little inefficient, don't need to do a whole
// deep replaceElements() call
return replaceElements( type, result, target, owner, copyCache, session )
- .thenCompose( unused -> {
+ .thenApply( unused -> {
if ( wasClean ) {
( (PersistentCollection>) target ).clearDirty();
}
- return completedFuture( target );
+ return target;
} );
}
else {
@@ -296,10 +291,8 @@ private static CompletionStage replaceMapTypeElements(
Object owner,
Map copyCache,
SessionImplementor session) {
- final CollectionPersister persister =
- session.getFactory().getRuntimeMetamodels().getMappingMetamodel()
- .getCollectionDescriptor( type.getRole() );
-
+ final CollectionPersister persister = session.getFactory().getRuntimeMetamodels()
+ .getMappingMetamodel().getCollectionDescriptor( type.getRole() );
final Map result = target;
result.clear();
@@ -307,15 +300,13 @@ private static CompletionStage replaceMapTypeElements(
original.entrySet(), entry -> {
final Map.Entry me = entry;
return getReplace( persister.getIndexType(), me.getKey(), owner, session, copyCache )
- .thenCompose( key ->
- getReplace(
- persister.getElementType(),
- me.getValue(),
- owner,
- session,
- copyCache
- ).thenAccept( value ->
- result.put( key, value ) )
+ .thenCompose( key -> getReplace(
+ persister.getElementType(),
+ me.getValue(),
+ owner,
+ session,
+ copyCache
+ ).thenAccept( value -> result.put( key, value ) )
);
}
).thenApply( unused -> result );
@@ -340,14 +331,11 @@ private static CompletionStage replaceArrayTypeElements(
final Type elemType = type.getElementType( session.getFactory() );
return loop(
- 0, length, i -> {
- return getReplace( elemType, Array.get( original, i ), owner, session, copyCache )
- .thenApply( o -> {
- Array.set( result, i, o );
- return result;
- }
- );
- }
+ 0, length, i -> getReplace( elemType, Array.get( original, i ), owner, session, copyCache )
+ .thenApply( o -> {
+ Array.set( result, i, o );
+ return result;
+ } )
).thenApply( unused -> result );
}
@@ -389,8 +377,7 @@ private static CompletionStage preserveSnapshot(
final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( result );
if ( ce != null ) {
return createSnapshot( original, result, elemType, owner, copyCache, session )
- .thenAccept( serializable ->
- ce.resetStoredSnapshot( result, serializable ) );
+ .thenAccept( serializable -> ce.resetStoredSnapshot( result, serializable ) );
}
return voidFuture();
}
@@ -434,14 +421,8 @@ private static CompletionStage createArraySnapshot(
Map copyCache,
SessionImplementor session) {
return loop(
- 0, array.length,
- i ->
- getReplace( elemType, array[i], owner, session, copyCache )
- .thenCompose( o -> {
- array[i] = o;
- return voidFuture();
- }
- )
+ 0, array.length, i -> getReplace( elemType, array[i], owner, session, copyCache )
+ .thenAccept( o -> array[i] = o )
).thenApply( unused -> array );
}
@@ -468,10 +449,9 @@ private static CompletionStage createMapSnapshot(
return loop(
map.entrySet(), entry ->
getReplace( elemType, entry.getValue(), resultSnapshot, owner, session, copyCache )
- .thenCompose( newValue -> {
+ .thenAccept( newValue -> {
final Object key = entry.getKey();
targetMap.put( key == entry.getValue() ? newValue : key, newValue );
- return voidFuture();
} )
).thenApply( v -> (Serializable) targetMap );
}
@@ -487,12 +467,8 @@ private static CompletionStage createListSnapshot(
SessionImplementor session) {
final ArrayList targetList = new ArrayList<>( list.size() );
return loop(
- list, obj ->
- getReplace( elemType, obj, owner, session, copyCache )
- .thenCompose( o -> {
- targetList.add( o );
- return voidFuture();
- } )
+ list, obj -> getReplace( elemType, obj, owner, session, copyCache )
+ .thenAccept( targetList::add )
).thenApply( unused -> targetList );
}
@@ -507,10 +483,8 @@ private static Object instantiateResultIfNecessary(CollectionType type, Object o
return target == null
|| target == original
|| target == UNFETCHED_PROPERTY
- || target instanceof PersistentCollection> && ( (PersistentCollection>) target).isWrapper( original ) ?
- type.instantiate( -1 ) :
- target;
+ || target instanceof PersistentCollection> && ( (PersistentCollection>) target).isWrapper( original )
+ ? type.instantiate( -1 )
+ : target;
}
-
-
}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
index d5f37dba3..fb1c29fe4 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
@@ -160,10 +160,7 @@ public static CompletionStage replace(
final Map copyCache) {
Object[] copied = new Object[original.length];
return loop(
- 0, types.length,
- i ->
- replace( original, target, types, session, owner, copyCache, i, copied )
-
+ 0, types.length, i -> replace( original, target, types, session, owner, copyCache, i, copied )
).thenApply( v -> copied );
}
@@ -181,9 +178,7 @@ public static CompletionStage replace(
Object[] copied = new Object[original.length];
return loop(
0, types.length,
- i ->
- replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied )
-
+ i -> replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied )
).thenApply( v -> copied );
}
@@ -274,15 +269,16 @@ private static CompletionStage resolveIdOrUniqueKey(
// as a ComponentType. In the case that the entity is unfetched, we need to
// explicitly fetch it here before calling replace(). (Note that in Hibernate
// ORM this is unnecessary due to transparent lazy fetching.)
- return ( (ReactiveSessionImpl) session ).reactiveFetch( id, true )
+ return ( (ReactiveSessionImpl) session )
+ .reactiveFetch( id, true )
.thenCompose( fetched -> {
- Object idOrUniqueKey = entityType.getIdentifierOrUniqueKeyType( session.getFactory() )
+ Object idOrUniqueKey = entityType
+ .getIdentifierOrUniqueKeyType( session.getFactory() )
.replace( fetched, null, session, owner, copyCache );
if ( idOrUniqueKey instanceof CompletionStage ) {
return ( (CompletionStage>) idOrUniqueKey )
.thenCompose( key -> resolve( entityType, key, owner, session ) );
}
-
return resolve( entityType, idOrUniqueKey, owner, session );
} );
} );
From e82f6bcd86526c079158b7e1194f1eed14f7055d Mon Sep 17 00:00:00 2001
From: Andrea Boriero
Date: Mon, 20 Jan 2025 14:08:20 +0100
Subject: [PATCH 015/162] [#2060] ClassCastException when using embeddable ids
---
.../internal/ReactiveEmbeddableForeignKeyResultImpl.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java
index a708481cf..1e3433320 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java
@@ -10,7 +10,6 @@
import org.hibernate.sql.results.graph.InitializerParent;
import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl;
-import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl;
public class ReactiveEmbeddableForeignKeyResultImpl extends EmbeddableForeignKeyResultImpl {
@@ -22,6 +21,6 @@ public ReactiveEmbeddableForeignKeyResultImpl(EmbeddableForeignKeyResultImpl
public EmbeddableInitializer> createInitializer(InitializerParent parent, AssemblerCreationState creationState) {
return getReferencedModePart() instanceof NonAggregatedIdentifierMapping
? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true )
- : new EmbeddableInitializerImpl( this, null, null, creationState, true );
+ : new ReactiveEmbeddableInitializerImpl( this, null, null, creationState, true );
}
}
From e9be7858133acc873ee5e349ff8adcfc0b93938c Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Wed, 8 Jan 2025 11:54:41 +0100
Subject: [PATCH 016/162] [#2060] Add test for ClassCastException when using
embeddable ids
---
.../reactive/EmbeddedIdWithManyTest.java | 180 ++++++++++++++++++
1 file changed, 180 insertions(+)
create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java
new file mode 100644
index 000000000..d77de6a4c
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java
@@ -0,0 +1,180 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.vertx.junit5.VertxTestContext;
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import jakarta.persistence.EmbeddedId;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.MappedSuperclass;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+public class EmbeddedIdWithManyTest extends BaseReactiveTest {
+
+ @Override
+ protected Collection> annotatedEntities() {
+ return List.of( Flower.class, Fruit.class );
+ }
+
+ @BeforeEach
+ public void populateDb(VertxTestContext context) {
+ Seed seed1 = new Seed( 1 );
+ Flower rose = new Flower( seed1, "Rose" );
+
+ Fruit cherry = new Fruit( seed1, "Cherry" );
+ cherry.addFriend( rose );
+
+ Seed seed2 = new Seed( 2 );
+ Flower sunflower = new Flower( seed2, "Sunflower" );
+
+ Fruit apple = new Fruit( seed2, "Apple" );
+ apple.addFriend( sunflower );
+
+ Seed seed3 = new Seed( 3 );
+ Flower chrysanthemum = new Flower( seed3, "Chrysanthemum" );
+
+ Fruit banana = new Fruit( seed3, "Banana" );
+ banana.addFriend( chrysanthemum );
+
+ test(
+ context,
+ getMutinySessionFactory().withTransaction( s -> s
+ .persistAll( cherry, rose, sunflower, apple, chrysanthemum, banana )
+ )
+ );
+ }
+
+ @Test
+ public void test(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory().withTransaction( s -> s
+ .createSelectionQuery( "from Flower", Flower.class )
+ .getResultList()
+ )
+ );
+ }
+
+ @Embeddable
+ public static class Seed {
+
+ @Column(nullable = false, updatable = false)
+ private Integer id;
+
+ public Seed() {
+ }
+
+ public Seed(Integer id) {
+ this.id = id;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+ }
+
+ @MappedSuperclass
+ public static abstract class Plant {
+
+ @EmbeddedId
+ private Seed seed;
+
+ @Column(length = 40, unique = true)
+ private String name;
+
+ protected Plant() {
+ }
+
+ protected Plant(Seed seed, String name) {
+ this.seed = seed;
+ this.name = name;
+ }
+
+ public Seed getSeed() {
+ return seed;
+ }
+
+ public void setSeed(Seed seed) {
+ this.seed = seed;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+ @Entity(name = "Fruit")
+ @Table(name = "known_fruits")
+ public static class Fruit extends Plant {
+
+ @OneToMany(mappedBy = "friend", fetch = FetchType.LAZY)
+ private List friends = new ArrayList<>();
+
+ public Fruit() {
+ }
+
+ public Fruit(Seed seed, String name) {
+ super( seed, name );
+ }
+
+ public void addFriend(Flower flower) {
+ this.friends.add( flower );
+ flower.friend = this;
+ }
+
+ public List getFriends() {
+ return friends;
+ }
+
+ @Override
+ public String toString() {
+ return "Fruit{" + getSeed().getId() + "," + getName() + '}';
+ }
+
+ }
+
+ @Entity(name = "Flower")
+ @Table(name = "known_flowers")
+ public static class Flower extends Plant {
+
+ @ManyToOne(fetch = FetchType.LAZY, optional = false)
+ @JoinColumn(name = "friend", referencedColumnName = "id", nullable = false)
+ private Fruit friend;
+
+ public Flower() {
+ }
+
+ public Flower(Seed seed, String name) {
+ super( seed, name );
+ }
+
+ @Override
+ public String toString() {
+ return "Flower{" + getSeed().getId() + "," + getName() + '}';
+ }
+
+ }
+
+}
From 2162b8bbc5db5d69bc3217888a29d93caa8404fa Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Tue, 21 Jan 2025 07:51:13 +0100
Subject: [PATCH 017/162] [#2060] Add assertions to the test
---
.../reactive/EmbeddedIdWithManyTest.java | 41 +++++++++++++++----
1 file changed, 34 insertions(+), 7 deletions(-)
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java
index d77de6a4c..eed1e5846 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithManyTest.java
@@ -24,8 +24,18 @@
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
+import static org.assertj.core.api.Assertions.assertThat;
+
public class EmbeddedIdWithManyTest extends BaseReactiveTest {
+ Fruit cherry;
+ Fruit apple;
+ Fruit banana;
+
+ Flower sunflower;
+ Flower chrysanthemum;
+ Flower rose;
+
@Override
protected Collection> annotatedEntities() {
return List.of( Flower.class, Fruit.class );
@@ -34,21 +44,21 @@ protected Collection> annotatedEntities() {
@BeforeEach
public void populateDb(VertxTestContext context) {
Seed seed1 = new Seed( 1 );
- Flower rose = new Flower( seed1, "Rose" );
+ rose = new Flower( seed1, "Rose" );
- Fruit cherry = new Fruit( seed1, "Cherry" );
+ cherry = new Fruit( seed1, "Cherry" );
cherry.addFriend( rose );
Seed seed2 = new Seed( 2 );
- Flower sunflower = new Flower( seed2, "Sunflower" );
+ sunflower = new Flower( seed2, "Sunflower" );
- Fruit apple = new Fruit( seed2, "Apple" );
+ apple = new Fruit( seed2, "Apple" );
apple.addFriend( sunflower );
Seed seed3 = new Seed( 3 );
- Flower chrysanthemum = new Flower( seed3, "Chrysanthemum" );
+ chrysanthemum = new Flower( seed3, "Chrysanthemum" );
- Fruit banana = new Fruit( seed3, "Banana" );
+ banana = new Fruit( seed3, "Banana" );
banana.addFriend( chrysanthemum );
test(
@@ -60,11 +70,28 @@ public void populateDb(VertxTestContext context) {
}
@Test
- public void test(VertxTestContext context) {
+ public void testFindWithEmbeddedId(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory().withTransaction( s -> s
+ .find( Flower.class, chrysanthemum.getSeed() )
+ .invoke( flower -> assertThat( flower.getName() ).isEqualTo( chrysanthemum.getName() ) )
+ )
+ );
+ }
+
+ @Test
+ public void testSelectQueryWithEmbeddedId(VertxTestContext context) {
test(
context, getMutinySessionFactory().withTransaction( s -> s
.createSelectionQuery( "from Flower", Flower.class )
.getResultList()
+ .invoke( list -> assertThat( list.stream().map( Flower::getName ) )
+ .containsExactlyInAnyOrder(
+ sunflower.getName(),
+ chrysanthemum.getName(),
+ rose.getName()
+ )
+ )
)
);
}
From 7471d527044e4b6831a139487314850591da4301 Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Tue, 21 Jan 2025 11:47:11 +0100
Subject: [PATCH 018/162] [#2072] Upgrade Hibernate ORM to 6.6.5.Final
---
README.md | 2 +-
gradle.properties | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index d3a3431af..7e6234007 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ Hibernate Reactive has been tested with:
- CockroachDB v24
- MS SQL Server 2022
- Oracle 23
-- [Hibernate ORM][] 6.6.4.Final
+- [Hibernate ORM][] 6.6.5.Final
- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.11
- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.11
- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.11
diff --git a/gradle.properties b/gradle.properties
index e8b659047..03d50309e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -35,12 +35,12 @@ org.gradle.java.installations.auto-download=false
#enableMavenLocalRepo = true
# The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`)
-hibernateOrmVersion = 6.6.4.Final
+hibernateOrmVersion = 6.6.5.Final
# 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.6.4.Final
+#hibernateOrmGradlePluginVersion = 6.6.5.Final
# If set to true, skip Hibernate ORM version parsing (default is true, if set to null)
# this is required when using intervals or weird versions or the build will fail
From a9b39f290282becfada82a086dbc3020a43cb7c9 Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Tue, 21 Jan 2025 12:17:06 +0100
Subject: [PATCH 019/162] [#2074] Upgrade Hibernate Validator to 8.0.2.Final
---
examples/native-sql-example/build.gradle | 2 +-
examples/session-example/build.gradle | 2 +-
integration-tests/hibernate-validator-postgres-it/build.gradle | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle
index fabdbc7fa..c79ec8984 100644
--- a/examples/native-sql-example/build.gradle
+++ b/examples/native-sql-example/build.gradle
@@ -27,7 +27,7 @@ dependencies {
implementation project( ':hibernate-reactive-core' )
// Hibernate Validator (optional)
- implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final'
+ implementation 'org.hibernate.validator:hibernate-validator:8.0.2.Final'
runtimeOnly 'org.glassfish.expressly:expressly:5.0.0'
// JPA metamodel generation for criteria queries (optional)
diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle
index 7cf7fb5f6..4da40ba69 100644
--- a/examples/session-example/build.gradle
+++ b/examples/session-example/build.gradle
@@ -27,7 +27,7 @@ dependencies {
implementation project( ':hibernate-reactive-core' )
// Hibernate Validator (optional)
- implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final'
+ implementation 'org.hibernate.validator:hibernate-validator:8.0.2.Final'
runtimeOnly 'org.glassfish.expressly:expressly:5.0.0'
// JPA metamodel generation for criteria queries (optional)
diff --git a/integration-tests/hibernate-validator-postgres-it/build.gradle b/integration-tests/hibernate-validator-postgres-it/build.gradle
index 017a52919..8236f6509 100644
--- a/integration-tests/hibernate-validator-postgres-it/build.gradle
+++ b/integration-tests/hibernate-validator-postgres-it/build.gradle
@@ -22,7 +22,7 @@ ext {
dependencies {
implementation project(':hibernate-reactive-core')
- implementation "org.hibernate.validator:hibernate-validator:8.0.1.Final"
+ implementation "org.hibernate.validator:hibernate-validator:8.0.2.Final"
runtimeOnly 'org.glassfish.expressly:expressly:5.0.0'
// JPA metamodel generation for criteria queries (optional)
From 434f0ec1a54fd4237f15ed327263653c79d6c488 Mon Sep 17 00:00:00 2001
From: Hibernate-CI
Date: Tue, 21 Jan 2025 14:13:24 +0000
Subject: [PATCH 020/162] Update project version to : `2.4.4.Final`
---
gradle/version.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle/version.properties b/gradle/version.properties
index 47693ff21..ad0f4adc1 100644
--- a/gradle/version.properties
+++ b/gradle/version.properties
@@ -1 +1 @@
-projectVersion=2.4.4-SNAPSHOT
\ No newline at end of file
+projectVersion=2.4.4.Final
\ No newline at end of file
From 16cc0bf85e7fe865c1548807e4b87fc4d44c8362 Mon Sep 17 00:00:00 2001
From: Hibernate-CI
Date: Tue, 21 Jan 2025 14:14:20 +0000
Subject: [PATCH 021/162] Update project version to : `2.4.5-SNAPSHOT`
---
gradle/version.properties | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gradle/version.properties b/gradle/version.properties
index ad0f4adc1..0b654a7ec 100644
--- a/gradle/version.properties
+++ b/gradle/version.properties
@@ -1 +1 @@
-projectVersion=2.4.4.Final
\ No newline at end of file
+projectVersion=2.4.5-SNAPSHOT
\ No newline at end of file
From e7c7bc0ac56d247604e84d43d4f1a9c212aa39d1 Mon Sep 17 00:00:00 2001
From: Andrea Boriero
Date: Tue, 21 Jan 2025 11:13:06 +0100
Subject: [PATCH 022/162] [#1724] Attempt to fix JDBCTimeZoneZonedTest sporadic
failures probably due lack of database Timestamp precision
---
.../timezones/JDBCTimeZoneZonedTest.java | 35 +++++++++++++++----
1 file changed, 28 insertions(+), 7 deletions(-)
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java
index 5b928c217..c86ee18bb 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java
@@ -10,10 +10,15 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;
import org.hibernate.cfg.Configuration;
+import org.hibernate.dialect.Dialect;
+import org.hibernate.dialect.MySQLDialect;
+import org.hibernate.dialect.SybaseDialect;
import org.hibernate.reactive.BaseReactiveTest;
import org.hibernate.reactive.annotations.DisabledFor;
@@ -31,7 +36,7 @@
import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2;
import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat;
-import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
+import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
@Timeout(value = 10, timeUnit = MINUTES)
@DisabledFor(value = DB2, reason = "Exception: IllegalStateException: Needed to have 6 in buffer but only had 0")
@@ -51,8 +56,24 @@ protected void setProperties(Configuration configuration) {
@Test
public void test(VertxTestContext context) {
- ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) );
- OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) );
+ final ZonedDateTime nowZoned;
+ final OffsetDateTime nowOffset;
+ final Dialect dialect = getDialect();
+ if ( dialect instanceof SybaseDialect || dialect instanceof MySQLDialect ) {
+ // Sybase has 1/300th sec precision
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") )
+ .with( ChronoField.NANO_OF_SECOND, 0L );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) )
+ .with( ChronoField.NANO_OF_SECOND, 0L );
+ }
+ else if ( dialect.getDefaultTimestampPrecision() == 6 ) {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS );
+ }
+ else {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) );
+ }
test( context, getSessionFactory()
.withTransaction( s -> {
Zoned z = new Zoned();
@@ -63,11 +84,11 @@ public void test(VertxTestContext context) {
.thenCompose( zid -> openSession()
.thenCompose( s -> s.find( Zoned.class, zid )
.thenAccept( z -> {
- assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
- assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
ZoneId systemZone = ZoneId.systemDefault();
ZoneOffset systemOffset = systemZone.getRules().getOffset( Instant.now() );
From f6dce68bc038304db80c4b0db24f74fb4b466921 Mon Sep 17 00:00:00 2001
From: Andrea Boriero
Date: Tue, 21 Jan 2025 13:47:59 +0100
Subject: [PATCH 023/162] [#1724] Align timezones tests to changes applied to
ORM corresponding tests
---
.../reactive/timezones/AutoZonedTest.java | 26 ++++++++++++++-----
.../reactive/timezones/ColumnZonedTest.java | 26 ++++++++++++++-----
.../reactive/timezones/DefaultZonedTest.java | 26 ++++++++++++++-----
.../timezones/JDBCTimeZoneZonedTest.java | 17 +++---------
.../reactive/timezones/PassThruZonedTest.java | 26 ++++++++++++++-----
5 files changed, 80 insertions(+), 41 deletions(-)
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/AutoZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/AutoZonedTest.java
index a799f5d6b..eb502bdb1 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/AutoZonedTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/AutoZonedTest.java
@@ -19,6 +19,7 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;
@@ -29,8 +30,11 @@
import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2;
import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat;
-import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
+import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
+/**
+ * Test adapted from {@link org.hibernate.orm.test.timezones.AutoZonedTest}
+ */
@Timeout(value = 10, timeUnit = MINUTES)
@DisabledFor(value = DB2, reason = "Exception: IllegalStateException: Needed to have 6 in buffer but only had 0")
public class AutoZonedTest extends BaseReactiveTest {
@@ -48,8 +52,16 @@ protected void setProperties(Configuration configuration) {
@Test
public void test(VertxTestContext context) {
- ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) );
- OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) );
+ final ZonedDateTime nowZoned;
+ final OffsetDateTime nowOffset;
+ if ( getDialect().getDefaultTimestampPrecision() == 6 ) {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS );
+ }
+ else {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) );
+ }
test( context, getSessionFactory()
.withTransaction( s -> {
Zoned z = new Zoned();
@@ -60,10 +72,10 @@ public void test(VertxTestContext context) {
.thenCompose( zid -> openSession()
.thenCompose( s -> s.find( Zoned.class, zid )
.thenAccept( z -> {
- assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
- assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
assertThat( z.zonedDateTime.toOffsetDateTime().getOffset() )
.isEqualTo( nowZoned.toOffsetDateTime().getOffset() );
assertThat( z.offsetDateTime.getOffset() )
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/ColumnZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/ColumnZonedTest.java
index f10e394e1..6e33da4f4 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/ColumnZonedTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/ColumnZonedTest.java
@@ -9,6 +9,7 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;
@@ -29,8 +30,11 @@
import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2;
import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat;
-import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
+import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
+/**
+ * Test adapted from {@link org.hibernate.orm.test.timezones.ColumnZonedTest}
+ */
@Timeout(value = 10, timeUnit = MINUTES)
@DisabledFor(value = DB2, reason = "java.sql.SQLException: An error occurred with a DB2 operation, SQLCODE=-180 SQLSTATE=22007")
public class ColumnZonedTest extends BaseReactiveTest {
@@ -48,8 +52,16 @@ protected void setProperties(Configuration configuration) {
@Test
public void test(VertxTestContext context) {
- ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) );
- OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) );
+ final ZonedDateTime nowZoned;
+ final OffsetDateTime nowOffset;
+ if ( getDialect().getDefaultTimestampPrecision() == 6 ) {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS );
+ }
+ else {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) );
+ }
test( context, getSessionFactory()
.withTransaction( s -> {
Zoned z = new Zoned();
@@ -60,10 +72,10 @@ public void test(VertxTestContext context) {
.thenCompose( zid -> openSession()
.thenCompose( s -> s.find( Zoned.class, zid )
.thenAccept( z -> {
- assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
- assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
assertThat( z.zonedDateTime.toOffsetDateTime().getOffset() )
.isEqualTo( nowZoned.toOffsetDateTime().getOffset() );
assertThat( z.offsetDateTime.getOffset() )
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/DefaultZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/DefaultZonedTest.java
index 6752b7219..4ce4dbea8 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/DefaultZonedTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/DefaultZonedTest.java
@@ -9,6 +9,7 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;
@@ -28,8 +29,11 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2;
import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat;
-import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
+import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
+/**
+ * Test adapted from {@link org.hibernate.orm.test.timezones.DefaultZonedTest}
+ */
@Timeout(value = 10, timeUnit = MINUTES)
@DisabledFor(value = DB2, reason = "Exception: IllegalStateException: Needed to have 6 in buffer but only had 0")
public class DefaultZonedTest extends BaseReactiveTest {
@@ -41,8 +45,16 @@ protected Collection> annotatedEntities() {
@Test
public void test(VertxTestContext context) {
- ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) );
- OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) );
+ final ZonedDateTime nowZoned;
+ final OffsetDateTime nowOffset;
+ if ( getDialect().getDefaultTimestampPrecision() == 6 ) {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS );
+ }
+ else {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) );
+ }
test( context, getSessionFactory()
.withTransaction( s -> {
Zoned z = new Zoned();
@@ -53,10 +65,10 @@ public void test(VertxTestContext context) {
.thenCompose( zid -> openSession()
.thenCompose( s -> s.find( Zoned.class, zid )
.thenAccept( z -> {
- assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
- assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
if ( getDialect().getTimeZoneSupport() == TimeZoneSupport.NATIVE ) {
assertThat( z.zonedDateTime.toOffsetDateTime().getOffset() )
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java
index c86ee18bb..71966bfc1 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/JDBCTimeZoneZonedTest.java
@@ -10,15 +10,11 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
-import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;
import org.hibernate.cfg.Configuration;
-import org.hibernate.dialect.Dialect;
-import org.hibernate.dialect.MySQLDialect;
-import org.hibernate.dialect.SybaseDialect;
import org.hibernate.reactive.BaseReactiveTest;
import org.hibernate.reactive.annotations.DisabledFor;
@@ -38,6 +34,9 @@
import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat;
import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
+/**
+ * Test adapted from {@link org.hibernate.orm.test.timezones.JDBCTimeZoneZonedTest}
+ */
@Timeout(value = 10, timeUnit = MINUTES)
@DisabledFor(value = DB2, reason = "Exception: IllegalStateException: Needed to have 6 in buffer but only had 0")
public class JDBCTimeZoneZonedTest extends BaseReactiveTest {
@@ -58,15 +57,7 @@ protected void setProperties(Configuration configuration) {
public void test(VertxTestContext context) {
final ZonedDateTime nowZoned;
final OffsetDateTime nowOffset;
- final Dialect dialect = getDialect();
- if ( dialect instanceof SybaseDialect || dialect instanceof MySQLDialect ) {
- // Sybase has 1/300th sec precision
- nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") )
- .with( ChronoField.NANO_OF_SECOND, 0L );
- nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) )
- .with( ChronoField.NANO_OF_SECOND, 0L );
- }
- else if ( dialect.getDefaultTimestampPrecision() == 6 ) {
+ if ( getDialect().getDefaultTimestampPrecision() == 6 ) {
nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS );
nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS );
}
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/PassThruZonedTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/PassThruZonedTest.java
index 988db4a58..3ce9f4256 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/PassThruZonedTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/timezones/PassThruZonedTest.java
@@ -10,6 +10,7 @@
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.List;
@@ -30,8 +31,11 @@
import static org.hibernate.cfg.AvailableSettings.TIMEZONE_DEFAULT_STORAGE;
import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2;
-import static org.hibernate.type.descriptor.DateTimeUtils.roundToDefaultPrecision;
+import static org.hibernate.type.descriptor.DateTimeUtils.adjustToDefaultPrecision;
+/**
+ * Test adapted from {@link org.hibernate.orm.test.timezones.PassThruZonedTest}
+ */
@Timeout(value = 10, timeUnit = MINUTES)
@DisabledFor(value = DB2, reason = "Exception: SQLException: An error occurred with a DB2 operation, SQLCODE=-180 SQLSTATE=22007")
public class PassThruZonedTest extends BaseReactiveTest {
@@ -49,8 +53,16 @@ protected void setProperties(Configuration configuration) {
@Test
public void test(VertxTestContext context) {
- ZonedDateTime nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of( "CET" ) );
- OffsetDateTime nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours( 3 ) );
+ final ZonedDateTime nowZoned;
+ final OffsetDateTime nowOffset;
+ if ( getDialect().getDefaultTimestampPrecision() == 6 ) {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") ).truncatedTo( ChronoUnit.MICROS );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) ).truncatedTo( ChronoUnit.MICROS );
+ }
+ else {
+ nowZoned = ZonedDateTime.now().withZoneSameInstant( ZoneId.of("CET") );
+ nowOffset = OffsetDateTime.now().withOffsetSameInstant( ZoneOffset.ofHours(3) );
+ }
test( context, getSessionFactory()
.withTransaction( s -> {
Zoned z = new Zoned();
@@ -61,10 +73,10 @@ public void test(VertxTestContext context) {
.thenCompose( zid -> openSession()
.thenCompose( s -> s.find( Zoned.class, zid )
.thenAccept( z -> {
- assertWithTruncationThat( roundToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
- assertWithTruncationThat( roundToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
- .isEqualTo( roundToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.zonedDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowZoned.toInstant(), getDialect() ) );
+ assertWithTruncationThat( adjustToDefaultPrecision( z.offsetDateTime.toInstant(), getDialect() ) )
+ .isEqualTo( adjustToDefaultPrecision( nowOffset.toInstant(), getDialect() ) );
ZoneId systemZone = ZoneId.systemDefault();
ZoneOffset systemOffset = systemZone.getRules().getOffset( Instant.now() );
From a7d192e1debd2ef290eb22f651dfecb1259ddff3 Mon Sep 17 00:00:00 2001
From: Gavin King
Date: Wed, 22 Jan 2025 10:43:07 +0100
Subject: [PATCH 024/162] remove unused/obsolete LOG
---
.../reactive/stage/impl/StageSelectionQueryImpl.java | 4 ----
1 file changed, 4 deletions(-)
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java
index 6a1be5d08..eaf1c9a53 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java
@@ -5,7 +5,6 @@
*/
package org.hibernate.reactive.stage.impl;
-import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.concurrent.CompletionStage;
@@ -16,8 +15,6 @@
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.query.Order;
import org.hibernate.query.Page;
-import org.hibernate.reactive.logging.impl.Log;
-import org.hibernate.reactive.logging.impl.LoggerFactory;
import org.hibernate.reactive.query.ReactiveSelectionQuery;
import org.hibernate.reactive.stage.Stage.SelectionQuery;
@@ -29,7 +26,6 @@
import jakarta.persistence.Parameter;
public class StageSelectionQueryImpl implements SelectionQuery {
- private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() );
private final ReactiveSelectionQuery delegate;
public StageSelectionQueryImpl(ReactiveSelectionQuery delegate) {
From fcb48cac95a9959319c9941bae822015a2f73b00 Mon Sep 17 00:00:00 2001
From: Gavin King
Date: Wed, 22 Jan 2025 10:41:57 +0100
Subject: [PATCH 025/162] fix whitespace in example persistence.xml files
---
.../src/main/resources/META-INF/persistence.xml | 2 +-
.../session-example/src/main/resources/META-INF/persistence.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/examples/native-sql-example/src/main/resources/META-INF/persistence.xml b/examples/native-sql-example/src/main/resources/META-INF/persistence.xml
index aaa9d3687..686442cb2 100644
--- a/examples/native-sql-example/src/main/resources/META-INF/persistence.xml
+++ b/examples/native-sql-example/src/main/resources/META-INF/persistence.xml
@@ -3,7 +3,7 @@
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
-
+
org.hibernate.reactive.provider.ReactivePersistenceProvider
org.hibernate.reactive.example.nativesql.Author
diff --git a/examples/session-example/src/main/resources/META-INF/persistence.xml b/examples/session-example/src/main/resources/META-INF/persistence.xml
index 7c2c13900..f3271f251 100644
--- a/examples/session-example/src/main/resources/META-INF/persistence.xml
+++ b/examples/session-example/src/main/resources/META-INF/persistence.xml
@@ -3,7 +3,7 @@
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
-
+
org.hibernate.reactive.provider.ReactivePersistenceProvider
org.hibernate.reactive.example.session.Author
From f9be2a2ba827088f39a31477a7fba0cd0628b903 Mon Sep 17 00:00:00 2001
From: Andrea Boriero
Date: Wed, 22 Jan 2025 11:02:30 +0100
Subject: [PATCH 026/162] [#1904] PropertyAccessException when creating a new
object with a one-to-one association
---
.../AbstractReactiveSaveEventListener.java | 89 ++++++++++---------
1 file changed, 45 insertions(+), 44 deletions(-)
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 e9d68b00e..c0ef66b17 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
@@ -144,55 +144,59 @@ else if ( generator instanceof Assigned ) {
// 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( gid -> performSaveWithId(
- entity,
- context,
- source,
- persister,
- generator,
- gid,
- requiresImmediateIdAccess,
- false
- ) );
+ return generateId( entity, source, (ReactiveIdentifierGenerator>) generator, persister )
+ .thenCompose( gid -> {
+ if ( gid == SHORT_CIRCUIT_INDICATOR ) {
+ source.getIdentifier( entity );
+ return voidFuture();
+ }
+ persister.setIdentifier( entity, gid, source );
+ return reactivePerformSave(
+ entity,
+ gid,
+ persister,
+ generatedOnExecution,
+ context,
+ source,
+ false
+ );
+ } );
}
generatedId = ( (BeforeExecutionGenerator) generator ).generate( source, entity, null, INSERT );
+ if ( generatedId == SHORT_CIRCUIT_INDICATOR ) {
+ source.getIdentifier( entity );
+ return voidFuture();
+ }
+ persister.setIdentifier( entity, generatedId, source );
}
final Object id = castToIdentifierType( generatedId, persister );
- return reactivePerformSave( entity, id, persister, generatedOnExecution, context, source, requiresImmediateIdAccess );
+ final boolean delayIdentityInserts = !source.isTransactionInProgress() && !requiresImmediateIdAccess && generatedOnExecution;
+ return reactivePerformSave( entity, id, persister, generatedOnExecution, context, source, delayIdentityInserts );
}
- private CompletionStage performSaveWithId(
+ private CompletionStage generateId(
Object entity,
- C context,
EventSource source,
- EntityPersister persister,
- Generator generator,
- Object generatedId,
- boolean requiresImmediateIdAccess,
- boolean generatedOnExecution) {
- if ( generatedId == null ) {
- throw new IdentifierGenerationException( "null id generated for: " + entity.getClass() );
- }
- if ( generatedId == SHORT_CIRCUIT_INDICATOR ) {
- source.getIdentifier( entity );
- return voidFuture();
- }
- if ( LOG.isDebugEnabled() ) {
- LOG.debugf(
- "Generated identifier: %s, using strategy: %s",
- persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ),
- generator.getClass().getName()
- );
- }
- final boolean delayIdentityInserts =
- !source.isTransactionInProgress()
- && !requiresImmediateIdAccess
- && generatedOnExecution;
- return reactivePerformSave( entity, generatedId, persister, false, context, source, delayIdentityInserts );
+ ReactiveIdentifierGenerator> generator,
+ EntityPersister persister) {
+ return generator
+ .generate( (ReactiveConnectionSupplier) source, entity )
+ .thenApply( id -> {
+ final Object generatedId = castToIdentifierType( id, persister );
+ if ( generatedId == null ) {
+ throw new IdentifierGenerationException( "null id generated for: " + entity.getClass() );
+ }
+ if ( LOG.isDebugEnabled() ) {
+ LOG.debugf(
+ "Generated identifier: %s, using strategy: %s",
+ persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ),
+ generator.getClass().getName()
+ );
+ }
+ return generatedId;
+ }
+ );
}
/**
@@ -232,10 +236,7 @@ protected CompletionStage reactivePerformSave(
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()'"
- );
+ return failedFuture( new IdentifierGenerationException( "Identifier of entity '" + persister.getEntityName() + "' must be manually assigned before calling 'persist()'" ) );
}
}
From 17dfc9906a1f85389bc36c038f589e09a2a2ec93 Mon Sep 17 00:00:00 2001
From: Andrea Boriero
Date: Wed, 22 Jan 2025 13:26:58 +0100
Subject: [PATCH 027/162] [#1904] Add test for PropertyAccessException when
creating a new object with a one-to-one association
---
.../OneToOneMapsIdAndGeneratedIdTest.java | 154 ++++++++++++++++++
1 file changed, 154 insertions(+)
create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneMapsIdAndGeneratedIdTest.java
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneMapsIdAndGeneratedIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneMapsIdAndGeneratedIdTest.java
new file mode 100644
index 000000000..7aca2c611
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneMapsIdAndGeneratedIdTest.java
@@ -0,0 +1,154 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.junit.jupiter.api.Test;
+
+import io.vertx.junit5.Timeout;
+import io.vertx.junit5.VertxTestContext;
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.MapsId;
+import jakarta.persistence.OneToOne;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Timeout(value = 10, timeUnit = MINUTES)
+public class OneToOneMapsIdAndGeneratedIdTest extends BaseReactiveTest {
+
+ @Override
+ protected Collection> annotatedEntities() {
+ return List.of( Person.class, NaturalPerson.class );
+ }
+
+ @Test
+ public void testPersist(VertxTestContext context) {
+ NaturalPerson naturalPerson = new NaturalPerson( "natual" );
+ Person person = new Person( "person", naturalPerson );
+
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( session -> session.persist( person ) )
+ .chain( () -> getMutinySessionFactory()
+ .withTransaction( session -> session
+ .find( Person.class, person.getId() )
+ .invoke( result -> {
+ assertThat( result ).isNotNull();
+ assertThat( result.getNaturalPerson() ).isNotNull();
+ assertThat( result.getNaturalPerson().getId() ).isEqualTo( result.getId() );
+ } ) )
+ )
+ );
+
+ }
+
+ @Entity
+ public static class Person {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+
+ @OneToOne(mappedBy = "person", cascade = CascadeType.ALL)
+ private NaturalPerson naturalPerson;
+
+ public Person() {
+ }
+
+ public Person(String name, NaturalPerson naturalPerson) {
+ this.name = name;
+ this.naturalPerson = naturalPerson;
+ naturalPerson.person = this;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public NaturalPerson getNaturalPerson() {
+ return naturalPerson;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ Person person = (Person) o;
+ return Objects.equals( name, person.name );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode( name );
+ }
+ }
+
+ @Entity
+ public static class NaturalPerson {
+
+ @Id
+ private Long id;
+
+ @Column
+ private String name;
+
+ @OneToOne(fetch = FetchType.LAZY)
+ @MapsId
+ private Person person;
+
+ public NaturalPerson() {
+ }
+
+ public NaturalPerson(String name) {
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Person getPerson() {
+ return person;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ NaturalPerson that = (NaturalPerson) o;
+ return Objects.equals( name, that.name );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode( name );
+ }
+ }
+
+
+}
From 92da2e61ddaffd07228f7fc8cadf0ce83f5da41c Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Fri, 24 Jan 2025 09:29:30 +0100
Subject: [PATCH 028/162] [#2086] Upgrade Gradle Wrapper to 8.12
---
gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 43583 bytes
gradle/wrapper/gradle-wrapper.properties | 2 +-
gradlew | 7 +++++--
gradlew.bat | 22 ++++++++++++----------
4 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index d64cd4917707c1f8861d8cb53dd15194d4248596..a4b76b9530d66f5e68d973ea569d8e19de379189 100644
GIT binary patch
delta 34592
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