diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4d1c7e327..fbc73fcb5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,3 +16,53 @@ updates: allow: - dependency-name: "actions/*" - dependency-name: "redhat-actions/*" + + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "weekly" + day: "tuesday" + open-pull-requests-limit: 20 + groups: + hibernate-validator: + patterns: + - "org.hibernate.validator*" + - "org.glassfish.expressly*" + hibernate: + patterns: + - "org.hibernate*" + vertx: + patterns: + - "io.vertx*" + mutiny: + patterns: + - "io.smallrye.reactive*" + # Testcontainers plus the JDBC driver we need for testing + testcontainers: + patterns: + - "org.testcontainers*" + - "com.ibm.db2*" + - "com.microsoft.sqlserver*" + - "org.postgresql*" + - "con.ongres.scram*" + - "com.fasterxml.jackson.core*" + - "com.mysql*" + - "org.mariadb.jdbc*" + + ignore: + # For Hibernate Validator, we will need to update major version manually as needed (but we only use it in tests) + - dependency-name: "org.glassfish.expressly*" + update-types: ["version-update-:semver-major"] + # Only patches for Hibernate ORM and Vert.x + - dependency-name: "org.hibernate*" + update-types: ["version-update:semver-major", "version-update:semver-minor"] + - dependency-name: "io.vertx*" + update-types: ["version-update:semver-major", "version-update:semver-minor"] + + # Dockerfiles in tooling/docker/, and database services we use for examples (MySQL and PostgreSQL) + - package-ecosystem: "docker" + directory: "/tooling/docker" + schedule: + interval: "weekly" + allow: + - dependency-type: "all" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 63f7c7764..9e1f766dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,15 +7,18 @@ on: - 'wip/**' - '2.*' - '3.*' + - '4.*' tags: - '2.*' - '3.*' + - '4.*' pull_request: branches: - 'main' - 'wip/**' - '2.*' - '3.*' + - '4.*' # For building snapshots workflow_call: inputs: @@ -49,8 +52,7 @@ jobs: services: # Label used to access the service container mysql: - # Docker Hub image - image: mysql:9.2.0 + image: container-registry.oracle.com/mysql/community-server:9.3.0 env: MYSQL_ROOT_PASSWORD: hreact MYSQL_DATABASE: hreact @@ -193,6 +195,7 @@ jobs: - { name: "21", java_version_numeric: 21, jvm_args: '--enable-preview' } - { name: "24", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' } - { name: "25-ea", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' } + - { name: "26-ea", java_version_numeric: 26, from: 'jdk.java.net', jvm_args: '--enable-preview' } steps: - name: Checkout ${{ inputs.branch }} uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c5f0ffbc8..ce053481f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,9 +2,9 @@ name: "CodeQL" on: push: - branches: [ "main", "1.0", "jakarta/main" ] + branches: [ "main", "4.0", "3.1", "3.0", "2.4" ] pull_request: - branches: [ "main" ] + branches: [ "main", "4.0", "3.1", "3.0", "2.4" ] schedule: - cron: "59 17 * * 2" diff --git a/.github/workflows/scheduler.yml b/.github/workflows/scheduler.yml index 2a25cf60a..57a1c58fc 100644 --- a/.github/workflows/scheduler.yml +++ b/.github/workflows/scheduler.yml @@ -11,7 +11,7 @@ jobs: build-snapshots: strategy: matrix: - branch: [ 'wip/2.3', 'wip/2.4', 'wip/3.0' ] + branch: [ 'wip/2.4', 'wip/3.0', 'wip/3.1', 'wip/4.0', 'wip/4.1' ] uses: ./.github/workflows/build.yml with: branch: ${{ matrix.branch }} diff --git a/AUTHORS.txt b/AUTHORS.txt new file mode 100644 index 000000000..caf0817d1 --- /dev/null +++ b/AUTHORS.txt @@ -0,0 +1,39 @@ +# This file lists copyright owners of the project. +# The list is not exhaustive: other copyright owners exist. +# See CONTRIBUTING.md for instructions regarding how to be added to this list. + +# Corporate contributors + +Red Hat, Inc. + +# Individual contributors + +Andrea Boriero +Andrew Guibert +Barry LaFond +Thinking Chen (cdmikechen) +Coding Xu (codingxu97) +Conor Farrell +Davide D'Alto +Derick Hermanson +Eric Dalquist +Eric Deandrea +Gail Badner +Gavin King +George Gastaldi +Georgios Andrianakis +Jay Erb +Julien Ponge +Marko Bekhta +Max Rydahl Andersen +(meepown) +Nesrin Aşan +Ravi Khadiwala +Renar Narubin +Sanne Grinovero +Scott Marlow +Stephane Epardaud +Steve Ebersole +Stuart Douglas +Thomas Segismont +Yoann Rodière diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2df00b224..bb825420f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,12 @@ All contributions are subject to the [Developer Certificate of Origin (DCO)](htt The DCO text is available verbatim in the [dco.txt](dco.txt) file in the root directory of the Hibernate Reactive repository. +Copyright owners are listed in [AUTHORS.txt](AUTHORS.txt). +Contributors with a valid copyright claim can request to be added to that list +by sending a pull request to the project's GitHub repository, +listing at least one relevant contribution in the pull request description. +Note: one-liner or repetitive patches may not be sufficient to claim copyright. + ## Guidelines While we try to keep requirements for contributing to a minimum, there are a few guidelines diff --git a/README.md b/README.md index 6047808fb..b0cc462fb 100644 --- a/README.md +++ b/README.md @@ -31,21 +31,25 @@ Learn more at . Hibernate Reactive has been tested with: - Java 17, 21, 24 -- PostgreSQL 16 +- PostgreSQL 17 - MySQL 9 - MariaDB 11 - Db2 12 -- CockroachDB v24 -- MS SQL Server 2022 +- CockroachDB v25 +- MS SQL Server 2025 - Oracle 23 -- [Hibernate ORM][] 7.0.0.Final -- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.15 -- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.15 -- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.15 -- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.15 -- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.15 +- [Hibernate ORM][] 7.1 +- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 5.0 +- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 5.0 +- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 5.0 +- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 5.0 +- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 5.0 - [Quarkus][Quarkus] via the Hibernate Reactive extension +The exact version of the libraries and images are in the +[catalog](https://github.com/hibernate/hibernate-reactive/blob/main/gradle/libs.versions.toml) +and in the [tooling/docker](https://github.com/hibernate/hibernate-reactive/tree/main/tooling/docker) folder. + [PostgreSQL]: https://www.postgresql.org [MySQL]: https://www.mysql.com [MariaDB]: https://mariadb.com diff --git a/build.gradle b/build.gradle index 452f7ea2c..7a45d616b 100644 --- a/build.gradle +++ b/build.gradle @@ -3,135 +3,18 @@ plugins { id 'java-library' id 'maven-publish' - id 'com.diffplug.spotless' version '6.25.0' - id 'org.asciidoctor.jvm.convert' version '4.0.2' apply false - id 'io.github.gradle-nexus.publish-plugin' version '1.3.0' + alias(libs.plugins.com.diffplug.spotless) + alias(libs.plugins.org.asciidoctor.jvm.convert) apply false } group = "org.hibernate.reactive" // leverage the ProjectVersion which comes from the `local.versions` plugin version = project.projectVersion.fullName -// 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: -// -// ./gradlew clean build -PhibernateOrmVersion=5.6.15-SNAPSHOT -ext { - // Mainly, to allow CI to test the latest versions of Vert.X - // Example: - // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT - if ( !project.hasProperty( 'vertxSqlClientVersion' ) ) { - vertxSqlClientVersion = '4.5.15' - } - - testcontainersVersion = '1.21.0' - - logger.lifecycle "Vert.x SQL Client Version: " + project.vertxSqlClientVersion +rootProject.afterEvaluate { + // Workaround since "libs.versions.NAME" notation cannot be used here + def libs = project.extensions.getByType(VersionCatalogsExtension).named('libs') + logger.lifecycle "ORM version: ${libs.findVersion('hibernateOrmVersion').get().requiredVersion}" + logger.lifecycle "ORM Gradle plugin version: ${libs.findVersion('hibernateOrmGradlePluginVersion').get().requiredVersion}" + logger.lifecycle "Vert.x SQL Client version: ${libs.findVersion('vertxSqlClientVersion').get().requiredVersion}" } - -subprojects { - apply plugin: 'java-library' - apply plugin: 'com.diffplug.spotless' - - group = rootProject.group - version = rootProject.version - - spotless { - //Don't fail during the check: rather than enforcing guidelines, we use this plugin to fix mistakes automatically. - enforceCheck false - java { - licenseHeaderFile rootProject.file('spotless.license.java') - removeUnusedImports() - trimTrailingWhitespace() - endWithNewline() - } - } - - tasks.compileJava.dependsOn(spotlessApply) - - repositories { - // Example: ./gradlew build -PenableMavenLocalRepo - if ( project.hasProperty('enableMavenLocalRepo') ) { - // Useful for local development, it should be disabled otherwise - mavenLocal() - } - // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep - if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } - } - - mavenCentral() - } - - ext.publishScript = rootProject.rootDir.absolutePath + '/publish.gradle' - - tasks.withType( JavaCompile ).configureEach { - options.encoding = 'UTF-8' - } - - if ( !gradle.ext.javaToolchainEnabled ) { - sourceCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion ) - targetCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion ) - } - else { - // Configure generated bytecode - // "sourceCompatibility" is not supported with toolchains. We have to work around that limitation. - tasks.compileJava.configure { - options.release = gradle.ext.javaVersions.main.release.asInt() - } - tasks.compileTestJava.configure { - options.release = gradle.ext.javaVersions.test.release.asInt() - } - - // Configure version of Java tools - java { - toolchain { - languageVersion = gradle.ext.javaVersions.main.compiler - } - } - tasks.compileTestJava { - javaCompiler = javaToolchains.compilerFor { - languageVersion = gradle.ext.javaVersions.test.compiler - } - } - tasks.test { - javaLauncher = javaToolchains.launcherFor { - languageVersion = gradle.ext.javaVersions.test.launcher - } - } - - // Configure JVM Options - tasks.withType( JavaCompile ).configureEach { - options.forkOptions.jvmArgs.addAll( getProperty( 'toolchain.compiler.jvmargs' ).toString().split( ' ' ) ) - } - tasks.withType( Javadoc ).configureEach { - options.setJFlags( getProperty( 'toolchain.javadoc.jvmargs' ).toString().split( ' ' ).toList().findAll( { !it.isEmpty() } ) ) - } - tasks.test { - // Configure JVM Options - jvmArgs(getProperty('toolchain.launcher.jvmargs').toString().split(' ')) - if ( project.hasProperty( 'test.jdk.launcher.args' ) ) { - jvmArgs( project.getProperty( 'test.jdk.launcher.args' ).toString().split( ' ' ) ) - } - } - - // Display version of Java tools - tasks.withType( JavaCompile ).configureEach { - doFirst { - logger.lifecycle "Compiling with '${javaCompiler.get().metadata.installationPath}'" - } - } - tasks.withType( Javadoc ).configureEach { - doFirst { - logger.lifecycle "Generating javadoc with '${javadocTool.get().metadata.installationPath}'" - } - } - tasks.test { - doFirst { - logger.lifecycle "Testing with '${javaLauncher.get().metadata.installationPath}'" - } - } - } -} - diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 8627950a1..6e4aacc33 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -1,9 +1,7 @@ #! /usr/bin/groovy /* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors */ /* @@ -17,11 +15,10 @@ import org.hibernate.jenkins.pipeline.helpers.version.Version // Global build configuration env.PROJECT = "reactive" env.JIRA_KEY = "HREACT" -def RELEASE_ON_PUSH = false // Set to `true` *only* on branches where you want a release on each push. +def RELEASE_ON_SCHEDULE = false // Set to `true` *only* on branches where you want a scheduled release. print "INFO: env.PROJECT = ${env.PROJECT}" print "INFO: env.JIRA_KEY = ${env.JIRA_KEY}" -print "INFO: RELEASE_ON_PUSH = ${RELEASE_ON_PUSH}" // -------------------------------------------- // Build conditions @@ -34,10 +31,17 @@ if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { } def manualRelease = currentBuild.getBuildCauses().toString().contains( 'UserIdCause' ) +def cronRelease = currentBuild.getBuildCauses().toString().contains( 'TimerTriggerCause' ) // Only do automatic release on branches where we opted in -if ( !manualRelease && !RELEASE_ON_PUSH ) { - print "INFO: Build skipped because automated releases are disabled on this branch. See constant RELEASE_ON_PUSH in ci/release/Jenkinsfile" +if ( !manualRelease && !cronRelease ) { + print "INFO: Build skipped because automated releases on push are disabled on this branch." + currentBuild.result = 'NOT_BUILT' + return +} + +if ( !manualRelease && cronRelease && !RELEASE_ON_SCHEDULE ) { + print "INFO: Build skipped because automated releases are disabled on this branch. See constant RELEASE_ON_SCHEDULE in ci/release/Jenkinsfile" currentBuild.result = 'NOT_BUILT' return } @@ -53,6 +57,7 @@ def checkoutReleaseScripts() { } } + // -------------------------------------------- // Pipeline @@ -60,12 +65,15 @@ pipeline { agent { label 'Release' } + triggers { + // Run every week Sunday 1 AM + cron('0 1 * * 0') + } tools { jdk 'OpenJDK 17 Latest' } options { buildDiscarder logRotator(daysToKeepStr: '30', numToKeepStr: '10') - rateLimitBuilds(throttle: [count: 1, durationName: 'day', userBoost: true]) disableConcurrentBuilds(abortPrevious: false) preserveStashes() } @@ -89,9 +97,13 @@ pipeline { ) } stages { - stage('Release check') { + stage('Check') { steps { script { + print "INFO: params.RELEASE_VERSION = ${params.RELEASE_VERSION}" + print "INFO: params.DEVELOPMENT_VERSION = ${params.DEVELOPMENT_VERSION}" + print "INFO: params.RELEASE_DRY_RUN? = ${params.RELEASE_DRY_RUN}" + checkoutReleaseScripts() def currentVersion = Version.parseDevelopmentVersion( sh( @@ -107,7 +119,9 @@ pipeline { echo "Release was requested manually" if ( !params.RELEASE_VERSION ) { - throw new IllegalArgumentException( 'Missing value for parameter RELEASE_VERSION. This parameter must be set explicitly to prevent mistakes.' ) + throw new IllegalArgumentException( + 'Missing value for parameter RELEASE_VERSION. This parameter must be set explicitly to prevent mistakes.' + ) } releaseVersion = Version.parseReleaseVersion( params.RELEASE_VERSION ) @@ -118,14 +132,15 @@ pipeline { else { echo "Release was triggered automatically" - // Avoid doing an automatic release for commits from a release - def lastCommitter = sh(script: 'git show -s --format=\'%an\'', returnStdout: true).trim() - def secondLastCommitter = sh(script: 'git show -s --format=\'%an\' HEAD~1', returnStdout: true).trim() - echo "Last two commits were performed by '${lastCommitter}'/'${secondLastCommitter}'." - - if (lastCommitter == 'Hibernate-CI' && secondLastCommitter == 'Hibernate-CI') { - print "INFO: Automatic release skipped because last commits were for the previous release" - currentBuild.result = 'ABORTED' + // Avoid doing an automatic release if there are no "releasable" commits since the last release (see release scripts for determination) + def releasableCommitCount = sh( + script: ".release/scripts/count-releasable-commits.sh ${env.PROJECT}", + returnStdout: true + ).trim().toInteger() + if ( releasableCommitCount <= 0 ) { + print "INFO: Automatic release skipped because no releasable commits were pushed since the previous release" + currentBuild.getRawBuild().getExecutor().interrupt(Result.NOT_BUILT) + sleep(1) // Interrupt is not blocking and does not take effect immediately. return } @@ -149,17 +164,12 @@ pipeline { env.RELEASE_VERSION = releaseVersion.toString() env.DEVELOPMENT_VERSION = developmentVersion.toString() - // Dry run is not supported at the moment - env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : "" + env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : "" env.JRELEASER_DRY_RUN = params.RELEASE_DRY_RUN - - // Determine version id to check if Jira version exists - // This step doesn't work for Hibernate Reactive (the project has been created with a different type on JIRA) - // sh ".release/scripts/determine-jira-version-id.sh ${env.JIRA_KEY} ${releaseVersion.withoutFinalQualifier}" } } } - stage('Release prepare') { + stage('Prepare') { steps { script { checkoutReleaseScripts() @@ -168,13 +178,13 @@ pipeline { configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { - - sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { + 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([ + "DISABLE_REMOTE_GRADLE_CACHE=true", // 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'" ]) { @@ -185,7 +195,7 @@ pipeline { } } } - stage('Publish release') { + stage('Publish') { steps { script { checkoutReleaseScripts() @@ -195,22 +205,47 @@ pipeline { configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { withCredentials([ - // TODO: Once we switch to maven-central publishing (from nexus2) we need to add a new credentials - // to use the following env variable names to set the user/password: - // - JRELEASER_MAVENCENTRAL_USERNAME - // - JRELEASER_MAVENCENTRAL_TOKEN - // Also use the new `credentialsId` for Maven Central, e.g.: - // usernamePassword(credentialsId: '???????', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'JRELEASER_NEXUS2_PASSWORD', usernameVariable: 'JRELEASER_NEXUS2_USERNAME'), - gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), - file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), - string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), - string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') + usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), + file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), + string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), + string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') ]) { sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { // performs documentation upload and Sonatype release // push to github - sh ".release/scripts/publish.sh -j ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" + withEnv([ + "DISABLE_REMOTE_GRADLE_CACHE=true" + ]) { + sh ".release/scripts/publish.sh -j ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" + } + } + } + } + } + } + } + stage('Update website') { + steps { + script { + checkoutReleaseScripts() + + configFileProvider([ + configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), + configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") + ]) { + withCredentials([ + 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'] ) { + dir( '.release/hibernate.org' ) { + checkout scmGit( + branches: [[name: '*/production']], + extensions: [], + userRemoteConfigs: [[credentialsId: 'ed25519.Hibernate-CI.github.com', url: 'https://github.com/hibernate/hibernate.org.git']] + ) + sh "../scripts/website-release.sh ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION}" + } } } } diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile index fea160b23..7fb8f9444 100644 --- a/ci/snapshot-publish.Jenkinsfile +++ b/ci/snapshot-publish.Jenkinsfile @@ -40,10 +40,9 @@ pipeline { steps { script { withCredentials([ - // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh // TODO: Once we switch to maven-central publishing (from nexus2) we need to update credentialsId: // https://docs.gradle.org/current/samples/sample_publishing_credentials.html#:~:text=via%20environment%20variables - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), + usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') ]) { diff --git a/documentation/build.gradle b/documentation/build.gradle index aa6a27622..01c67f967 100644 --- a/documentation/build.gradle +++ b/documentation/build.gradle @@ -4,6 +4,7 @@ import java.time.Year plugins { id "org.asciidoctor.jvm.convert" + id "hr-java-library" } ext { @@ -52,7 +53,7 @@ def aggregateJavadocsTask = tasks.register( 'aggregateJavadocs', Javadoc ) { use = true options.encoding = 'UTF-8' - def matcher = hibernateOrmVersion =~ /\d+\.\d+/ + def matcher = libs.versions.hibernateOrmVersion =~ /\d+\.\d+/ def ormMinorVersion = matcher.find() ? matcher.group() : "5.6"; links = [ diff --git a/documentation/src/main/asciidoc/reference/introduction.adoc b/documentation/src/main/asciidoc/reference/introduction.adoc index 4f2237edb..ee5090c21 100644 --- a/documentation/src/main/asciidoc/reference/introduction.adoc +++ b/documentation/src/main/asciidoc/reference/introduction.adoc @@ -89,7 +89,7 @@ Optionally, you might also add any of the following additional features: | Hibernate Validator | `org.hibernate.validator:hibernate-validator` and `org.glassfish:jakarta.el` | Compile-time checking for your HQL queries | `org.hibernate:query-validator` | Second-level cache support via JCache and EHCache | `org.hibernate.orm:hibernate-jcache` along with `org.ehcache:ehcache` -| SCRAM authentication support for PostgreSQL | `com.ongres.scram:client:2.1` +| SCRAM authentication support for PostgreSQL | `com.ongres.scram:scram-client:3.1` |=== You might also add the Hibernate {enhancer}[bytecode enhancer] to your diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle index c79ec8984..36c2494fe 100644 --- a/examples/native-sql-example/build.gradle +++ b/examples/native-sql-example/build.gradle @@ -8,17 +8,20 @@ buildscript { mavenLocal() } // Optional: Enables snapshots repository - // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep - if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + // Example: ./gradlew build -PenableCentralSonatypeSnapshotsRep + if ( project.hasProperty('enableCentralSonatypeSnapshotsRep') ) { + maven { url 'https://central.sonatype.com/repository/maven-snapshots/' } } mavenCentral() } } plugins { + id "hr-java-library" + id "hr-print-resolved-version" + // Optional: Hibernate Gradle plugin to enable bytecode enhancements - id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}" + alias(libs.plugins.org.hibernate.orm) } description = 'Hibernate Reactive native SQL Example' @@ -27,24 +30,24 @@ dependencies { implementation project( ':hibernate-reactive-core' ) // Hibernate Validator (optional) - implementation 'org.hibernate.validator:hibernate-validator:8.0.2.Final' - runtimeOnly 'org.glassfish.expressly:expressly:5.0.0' + implementation(libs.org.hibernate.validator.hibernate.validator) + runtimeOnly(libs.org.glassfish.expressly.expressly) // JPA metamodel generation for criteria queries (optional) - annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}" + annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen) // database driver for PostgreSQL - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // logging (optional) - runtimeOnly "org.apache.logging.log4j:log4j-core:2.20.0" + runtimeOnly(libs.org.apache.logging.log4j.log4j.core) // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly(libs.com.ongres.scram.scram.client) } // Optional: enable the bytecode enhancements -hibernate { enhancement } +hibernate { enhancement {} } // Create tasks to run the different API available. // diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle index 4da40ba69..621df2bb4 100644 --- a/examples/session-example/build.gradle +++ b/examples/session-example/build.gradle @@ -8,17 +8,20 @@ buildscript { mavenLocal() } // Optional: Enables snapshots repository - // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep - if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + // Example: ./gradlew build -PenableCentralSonatypeSnapshotsRep + if ( project.hasProperty('enableCentralSonatypeSnapshotsRep') ) { + maven { url 'https://central.sonatype.com/repository/maven-snapshots/' } } mavenCentral() } } plugins { + id "hr-java-library" + id "hr-print-resolved-version" + // Optional: Hibernate Gradle plugin to enable bytecode enhancements - id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}" + alias(libs.plugins.org.hibernate.orm) } description = 'Hibernate Reactive Session Examples' @@ -27,25 +30,25 @@ dependencies { implementation project( ':hibernate-reactive-core' ) // Hibernate Validator (optional) - implementation 'org.hibernate.validator:hibernate-validator:8.0.2.Final' - runtimeOnly 'org.glassfish.expressly:expressly:5.0.0' + implementation(libs.org.hibernate.validator.hibernate.validator) + runtimeOnly(libs.org.glassfish.expressly.expressly) // JPA metamodel generation for criteria queries (optional) - annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}" + annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen) // database drivers for PostgreSQL and MySQL - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" - runtimeOnly "io.vertx:vertx-mysql-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) + runtimeOnly(libs.io.vertx.vertx.mysql.client) // logging (optional) - runtimeOnly "org.apache.logging.log4j:log4j-core:2.20.0" + runtimeOnly(libs.org.apache.logging.log4j.log4j.core) // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly(libs.com.ongres.scram.scram.client) } // Optional: enable the bytecode enhancements -hibernate { enhancement } +hibernate { enhancement {} } // Create tasks to run the different API available. // diff --git a/gradle.properties b/gradle.properties index 5cd937337..d098a7035 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,9 @@ org.gradle.java.installations.auto-download=false ######################################################################### # Additional custom gradle build properties. -# Please, leave these properties commented upstream +# Please, leave these properties commented upstream. +# They are meant to be used for local builds or WIP branches. +# The same properties can be set from the command line. ########################################################################## # Enable Testcontainers + Docker when present (value ignored) @@ -28,28 +30,28 @@ org.gradle.java.installations.auto-download=false # Db2, MySql, PostgreSQL, CockroachDB, SqlServer, Oracle #db = MSSQL -# Enable the SonatypeOS maven repository (mainly for Vert.x snapshots) when present (value ignored) -#enableSonatypeOpenSourceSnapshotsRep = true +# Enable the maven Central Snapshot repository, when set to any value (the value is ignored) +#enableCentralSonatypeSnapshotsRep = true # Enable the maven local repository (for local development when needed) when present (value ignored) #enableMavenLocalRepo = true +### Settings the following properties will override the version defined in gradle/libs.versions.toml + # The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) -hibernateOrmVersion = 7.0.0.Final +#hibernateOrmVersion = 7.1.0.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 = 7.0.0.Final +#hibernateOrmGradlePluginVersion = 7.1.0.Final # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) # this is required when using intervals or weird versions or the build will fail #skipOrmVersionParsing = true # Override default Vert.x Sql client version -#vertxSqlClientVersion = 4.5.15-SNAPSHOT +#vertxSqlClientVersion = 5.0.2-SNAPSHOT # Override default Vert.x Web client and server versions. For integration tests, both default to vertxSqlClientVersion -#vertxWebVersion = 4.5.15 -#vertxWebtClientVersion = 4.5.15 +#vertxWebVersion = 5.0.2 +#vertxWebtClientVersion = 5.0.2 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..5f6355381 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,60 @@ +[versions] +assertjVersion = "3.27.3" +hibernateOrmVersion = "7.1.0.Final" +hibernateOrmGradlePluginVersion = "7.1.0.Final" +jacksonDatabindVersion = "2.19.2" +jbossLoggingAnnotationVersion = "3.0.4.Final" +jbossLoggingVersion = "3.6.1.Final" +junitVersion = "5.13.4" +junitPlatformVersion = "1.13.3" +log4jVersion = "2.25.1" +testcontainersVersion = "1.21.3" +vertxSqlClientVersion = "5.0.2" +vertxWebVersion= "5.0.2" +vertxWebClientVersion = "5.0.2" + +[libraries] +com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } +com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" } +com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.1.1.jre11-preview" } +com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.4.0" } +com-ongres-scram-scram-client = { group = "com.ongres.scram", name = "scram-client", version = "3.1" } +io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.4" } +io-vertx-vertx-db2-client = { group = "io.vertx", name = "vertx-db2-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-junit5 = { group = "io.vertx", name = "vertx-junit5", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-micrometer-metrics = { group = "io.vertx", name = "vertx-micrometer-metrics", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-mssql-client = { group = "io.vertx", name = "vertx-mssql-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-mysql-client = { group = "io.vertx", name = "vertx-mysql-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-oracle-client = { group = "io.vertx", name = "vertx-oracle-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-pg-client = { group = "io.vertx", name = "vertx-pg-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-sql-client = { group = "io.vertx", name = "vertx-sql-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-web = { group = "io.vertx", name = "vertx-web", version.ref = "vertxWebVersion" } +io-vertx-vertx-web-client = { group = "io.vertx", name = "vertx-web-client", version.ref = "vertxWebClientVersion" } +org-apache-logging-log4j-log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4jVersion" } +org-assertj-assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertjVersion" } +org-ehcache-ehcache = { group = "org.ehcache", name = "ehcache", version = "3.10.8" } +org-glassfish-expressly-expressly = { group = "org.glassfish.expressly", name = "expressly", version = "5.0.0" } +org-hibernate-orm-hibernate-core = { group = "org.hibernate.orm", name = "hibernate-core", version.ref = "hibernateOrmVersion" } +org-hibernate-orm-hibernate-jcache = { group = "org.hibernate.orm", name = "hibernate-jcache", version.ref = "hibernateOrmVersion" } +org-hibernate-orm-hibernate-jpamodelgen = { group = "org.hibernate.orm", name = "hibernate-jpamodelgen", version.ref = "hibernateOrmVersion" } +org-hibernate-validator-hibernate-validator = { group = "org.hibernate.validator", name = "hibernate-validator", version = "8.0.2.Final" } +org-jboss-logging-jboss-logging = { group = "org.jboss.logging", name = "jboss-logging", version.ref = "jbossLoggingVersion" } +org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", name = "jboss-logging-annotations", version.ref = "jbossLoggingAnnotationVersion" } +org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" } +org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" } +org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" } +org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "1.13.4" } +org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.4" } +org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.7" } +org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" } +org-testcontainers-db2 = { group = "org.testcontainers", name = "db2", version.ref = "testcontainersVersion" } +org-testcontainers-mariadb = { group = "org.testcontainers", name = "mariadb", version.ref = "testcontainersVersion" } +org-testcontainers-mssqlserver = { group = "org.testcontainers", name = "mssqlserver", version.ref = "testcontainersVersion" } +org-testcontainers-mysql = { group = "org.testcontainers", name = "mysql", version.ref = "testcontainersVersion" } +org-testcontainers-oracle-xe = { group = "org.testcontainers", name = "oracle-xe", version.ref = "testcontainersVersion" } +org-testcontainers-postgresql = { group = "org.testcontainers", name = "postgresql", version.ref = "testcontainersVersion" } + +[plugins] +com-diffplug-spotless = { id = "com.diffplug.spotless", version = "7.2.1" } +org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.4" } +org-hibernate-orm = { id = "org.hibernate.orm", version.ref = "hibernateOrmGradlePluginVersion" } diff --git a/gradle/version.properties b/gradle/version.properties index e3e58d698..5788ca34e 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.0.Final \ No newline at end of file +projectVersion=4.1.0.Final \ No newline at end of file diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index 01f593d80..97c1b8bd8 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -1,5 +1,11 @@ +plugins { + id "hr-java-library" + id "hr-test-containers" + id "hr-print-resolved-version" +} + ext { - mavenPomName = 'Hibernate Reactive Core' + mavenPomName = 'Hibernate Reactive Core' } description = 'The core module of Hibernate Reactive' @@ -8,77 +14,78 @@ apply from: publishScript dependencies { - api "org.hibernate.orm:hibernate-core:${hibernateOrmVersion}" + api(libs.org.hibernate.orm.hibernate.core) - api 'io.smallrye.reactive:mutiny:2.9.0' + api(libs.io.smallrye.reactive.mutiny) //Logging - implementation 'org.jboss.logging:jboss-logging:3.6.1.Final' - annotationProcessor 'org.jboss.logging:jboss-logging:3.6.1.Final' + implementation(libs.org.jboss.logging.jboss.logging) + annotationProcessor(libs.org.jboss.logging.jboss.logging) - compileOnly 'org.jboss.logging:jboss-logging-annotations:3.0.4.Final' - annotationProcessor 'org.jboss.logging:jboss-logging-annotations:3.0.4.Final' - annotationProcessor 'org.jboss.logging:jboss-logging-processor:3.0.4.Final' + compileOnly(libs.org.jboss.logging.jboss.logging.annotations) + annotationProcessor(libs.org.jboss.logging.jboss.logging.annotations) + annotationProcessor(libs.org.jboss.logging.jboss.logging.processor) //Specific implementation details of Hibernate Reactive: - implementation "io.vertx:vertx-sql-client:${vertxSqlClientVersion}" + implementation(libs.io.vertx.vertx.sql.client) // Testing - testImplementation 'org.assertj:assertj-core:3.27.3' - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) // Drivers - testImplementation "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" - testImplementation "io.vertx:vertx-mysql-client:${vertxSqlClientVersion}" - testImplementation "io.vertx:vertx-db2-client:${vertxSqlClientVersion}" - testImplementation "io.vertx:vertx-mssql-client:${vertxSqlClientVersion}" - testImplementation "io.vertx:vertx-oracle-client:${vertxSqlClientVersion}" + testImplementation(libs.io.vertx.vertx.pg.client) + testImplementation(libs.io.vertx.vertx.mysql.client) + testImplementation(libs.io.vertx.vertx.db2.client) + testImplementation(libs.io.vertx.vertx.mssql.client) + testImplementation(libs.io.vertx.vertx.oracle.client) // Metrics - testImplementation "io.vertx:vertx-micrometer-metrics:${vertxSqlClientVersion}" + testImplementation(libs.io.vertx.vertx.micrometer.metrics) // Optional dependency of vertx-pg-client, essential when connecting via SASL SCRAM - testImplementation 'com.ongres.scram:client:2.1' + testImplementation(libs.com.ongres.scram.scram.client) // JUnit Jupiter - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.3' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.3' + testImplementation(libs.org.junit.jupiter.junit.jupiter.api) + testRuntimeOnly(libs.org.junit.jupiter.junit.jupiter.engine) + testRuntimeOnly(libs.org.junit.platform.junit.platform.launcher) // JDBC driver to test with ORM and PostgreSQL - testRuntimeOnly "org.postgresql:postgresql:42.7.5" + testRuntimeOnly(libs.org.postgresql.postgresql) // JDBC driver for Testcontainers with MS SQL Server - testRuntimeOnly "com.microsoft.sqlserver:mssql-jdbc:12.10.0.jre11" + testRuntimeOnly(libs.com.microsoft.sqlserver.mssql.jdbc) // JDBC driver for Testcontainers with MariaDB Server - testRuntimeOnly "org.mariadb.jdbc:mariadb-java-client:3.5.3" + testRuntimeOnly(libs.org.mariadb.jdbc.mariadb.java.client) // JDBC driver for Testcontainers with MYSQL Server - testRuntimeOnly "com.mysql:mysql-connector-j:9.3.0" + testRuntimeOnly(libs.com.mysql.mysql.connector.j) // JDBC driver for Db2 server, for testing - testRuntimeOnly "com.ibm.db2:jcc:12.1.0.0" + testRuntimeOnly(libs.com.ibm.db2.jcc) // EHCache - testRuntimeOnly ("org.ehcache:ehcache:3.10.8") { + testRuntimeOnly(libs.org.ehcache.ehcache) { capabilities { requireCapability 'org.ehcache.modules:ehcache-xml-jakarta' } } - testRuntimeOnly ("org.hibernate.orm:hibernate-jcache:${hibernateOrmVersion}") + testRuntimeOnly(libs.org.hibernate.orm.hibernate.jcache) // log4j - testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.20.0' + testRuntimeOnly(libs.org.apache.logging.log4j.log4j.core) // Testcontainers - testImplementation "org.testcontainers:postgresql:${testcontainersVersion}" - testImplementation "org.testcontainers:mysql:${testcontainersVersion}" - testImplementation "org.testcontainers:mariadb:${testcontainersVersion}" - testImplementation "org.testcontainers:db2:${testcontainersVersion}" - testImplementation "org.testcontainers:cockroachdb:${testcontainersVersion}" - testImplementation "org.testcontainers:mssqlserver:${testcontainersVersion}" - testImplementation "org.testcontainers:oracle-xe:${testcontainersVersion}" + testImplementation(libs.org.testcontainers.postgresql) + testImplementation(libs.org.testcontainers.mysql) + testImplementation(libs.org.testcontainers.mariadb) + testImplementation(libs.org.testcontainers.db2) + testImplementation(libs.org.testcontainers.cockroachdb) + testImplementation(libs.org.testcontainers.mssqlserver) + testImplementation(libs.org.testcontainers.oracle.xe) } // Reproducible Builds @@ -89,81 +96,3 @@ tasks.withType(AbstractArchiveTask).configureEach { preserveFileTimestamps = false reproducibleFileOrder = true } - -// Print a summary of the results of the tests (number of failures, successes and skipped) -def loggingSummary(db, result, desc) { - if ( !desc.parent ) { // will match the outermost suite - def output = "${db} results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)" - def repeatLength = output.length() + 1 - logger.lifecycle '\n' + ('-' * repeatLength) + '\n' + output + '\n' + ('-' * repeatLength) - } -} - -// Example: -// gradle test -Pdb=MySQL -test { - def selectedDb = project.hasProperty( 'db' ) - ? project.properties['db'] - : 'PostgreSQL' - doFirst { - systemProperty 'db', selectedDb - } - afterSuite { desc, result -> - loggingSummary( selectedDb, result, desc ) - } -} - -// Configuration for the tests -tasks.withType( Test ).configureEach { - defaultCharacterEncoding = "UTF-8" - useJUnitPlatform() - testLogging { - showStandardStreams = project.hasProperty('showStandardOutput') - showStackTraces = true - exceptionFormat = 'full' - displayGranularity = 1 - events = ['PASSED', 'FAILED', 'SKIPPED'] - } - systemProperty 'docker', project.hasProperty( 'docker' ) ? 'true' : 'false' - systemProperty 'org.hibernate.reactive.common.InternalStateAssertions.ENFORCE', 'true' - - if ( project.hasProperty( 'includeTests' ) ) { - // Example: ./gradlew testAll -PincludeTests=DefaultPortTest - filter { - includeTestsMatching project.properties['includeTests'] ?: '*' as String - } - } -} - -def createTestDbTask(dbName) { - tasks.register( "testDb${dbName}", Test ) { - description = "Run tests for ${dbName}" - - doFirst() { - systemProperty 'db', dbName - } - afterSuite { desc, result -> - loggingSummary( dbName, result, desc ) - } - } -} - -// Rule to recognize calls to testDb -// and run the tests on the selected db -// Example: -// gradle testDbMySQL testDbDB2 -tasks.addRule( "Pattern testDb" ) { String taskName -> - if ( taskName.startsWith( "testDb" ) ) { - def dbName = taskName.substring( "testDb".length() ) - createTestDbTask( dbName ) - } -} - -// The dbs we want to test when running testAll -def dbs = ['MariaDB', 'MySQL', 'PostgreSQL', 'DB2', 'CockroachDB', 'MSSQLServer', 'Oracle'] -dbs.forEach { createTestDbTask it } - -tasks.register( "testAll", Test ) { - description = "Run tests for ${dbs}" - dependsOn = dbs.collect( [] as HashSet ) { db -> "testDb${db}" } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java index 4448c9b6c..02c28af5f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/context/impl/VertxContext.java @@ -8,7 +8,7 @@ import java.lang.invoke.MethodHandles; import io.vertx.core.Vertx; -import io.vertx.core.impl.ContextInternal; +import io.vertx.core.internal.ContextInternal; import org.hibernate.reactive.context.Context; import org.hibernate.reactive.logging.impl.Log; @@ -36,7 +36,7 @@ public void injectServices(ServiceRegistryImplementor serviceRegistry) { @Override public void put(Key key, T instance) { - final io.vertx.core.Context context = Vertx.currentContext(); + final ContextInternal context = currentContext(); if ( context != null ) { if ( trace ) LOG.tracef( "Putting key,value in context: [%1$s, %2$s]", key, instance ); context.putLocal( key, instance ); @@ -47,9 +47,13 @@ public void put(Key key, T instance) { } } + private static ContextInternal currentContext() { + return (ContextInternal) Vertx.currentContext(); + } + @Override public T get(Key key) { - final io.vertx.core.Context context = Vertx.currentContext(); + final ContextInternal context = currentContext(); if ( context != null ) { T local = context.getLocal( key ); if ( trace ) LOG.tracef( "Getting value %2$s from context for key %1$s", key, local ); @@ -63,7 +67,7 @@ public T get(Key key) { @Override public void remove(Key key) { - final io.vertx.core.Context context = Vertx.currentContext(); + final ContextInternal context = currentContext(); if ( context != null ) { boolean removed = context.removeLocal( key ); if ( trace ) LOG.tracef( "Key %s removed from context: %s", key, removed ); @@ -75,7 +79,7 @@ public void remove(Key key) { @Override public void execute(Runnable runnable) { - final io.vertx.core.Context currentContext = Vertx.currentContext(); + final io.vertx.core.Context currentContext = currentContext(); if ( currentContext == null ) { if ( trace ) LOG.tracef( "Not in a Vert.x context, checking the VertxInstance service" ); final io.vertx.core.Context newContext = vertxInstance.getVertx().getOrCreateContext(); @@ -83,6 +87,7 @@ public void execute(Runnable runnable) { // that could lead to unintentionally share the same session with other streams. ContextInternal newContextInternal = (ContextInternal) newContext; final ContextInternal duplicate = newContextInternal.duplicate(); + if ( trace ) LOG.tracef( "Using duplicated context from VertxInstance: %s", duplicate ); duplicate.runOnContext( x -> runnable.run() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java index 9804a02f6..4f3d53e58 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java @@ -27,7 +27,6 @@ import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEventListener; -import org.hibernate.loader.internal.CacheLoadHelper; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMappingsList; import org.hibernate.metamodel.mapping.CompositeIdentifierMapping; @@ -48,7 +47,7 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSecondLevelCache; -import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSessionCache; +import static org.hibernate.reactive.loader.internal.ReactiveCacheLoadHelper.loadFromSessionCache; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.session.impl.SessionUtil.checkEntityFound; @@ -653,15 +652,17 @@ private CompletionStage doLoad( return nullFuture(); } else { - final CacheLoadHelper.PersistenceContextEntry persistenceContextEntry = - loadFromSessionCache( keyToLoad, event.getLockOptions(), options, event.getSession() ); - final Object entity = persistenceContextEntry.entity(); - if ( entity != null ) { - return persistenceContextEntry.isManaged() ? initializeIfNecessary( entity ) : nullFuture(); - } - else { - return loadFromCacheOrDatasource( event, persister, keyToLoad ); - } + return loadFromSessionCache( keyToLoad, event.getLockOptions(), options, event.getSession() ).thenCompose( + persistenceContextEntry -> { + final Object entity = persistenceContextEntry.entity(); + if ( entity != null ) { + return persistenceContextEntry.isManaged() ? initializeIfNecessary( entity ) : nullFuture(); + } + else { + return loadFromCacheOrDatasource( event, persister, keyToLoad ); + } + } + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java index 7121ea5c4..83ae3290a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java @@ -6,6 +6,7 @@ package org.hibernate.reactive.id; import org.hibernate.Incubating; +import org.hibernate.generator.EventType; import org.hibernate.generator.Generator; import org.hibernate.id.IdentifierGenerator; import org.hibernate.reactive.session.ReactiveConnectionSupplier; @@ -33,4 +34,8 @@ public interface ReactiveIdentifierGenerator extends Generator { * @param session the reactive session */ CompletionStage generate(ReactiveConnectionSupplier session, Object entity); + + default CompletionStage generate(ReactiveConnectionSupplier session, Object owner, Object currentValue, EventType eventType) { + return generate( session, owner ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java index c820f513f..329792f28 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java @@ -14,9 +14,9 @@ import io.vertx.core.Context; import io.vertx.core.Vertx; -import io.vertx.core.net.impl.pool.CombinerExecutor; -import io.vertx.core.net.impl.pool.Executor; -import io.vertx.core.net.impl.pool.Task; +import io.vertx.core.internal.pool.CombinerExecutor; +import io.vertx.core.internal.pool.Executor; +import io.vertx.core.internal.pool.Task; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -44,7 +44,7 @@ public abstract class BlockingIdentifierGenerator implements ReactiveIdentifierG //modification access. //This replaces the synchronization blocks one would see in a similar //service in Hibernate ORM, but using a non-blocking cooperative design. - private final CombinerExecutor executor = new CombinerExecutor( state ); + private final CombinerExecutor executor = new CombinerExecutor<>( state ); /** * Allocate a new block, by obtaining the next "hi" value from the database diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java new file mode 100644 index 000000000..cb6b03ab5 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java @@ -0,0 +1,130 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.id.impl; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.Generator; +import org.hibernate.generator.GeneratorCreationContext; +import org.hibernate.id.CompositeNestedGeneratedValueGenerator; +import org.hibernate.id.IdentifierGenerationException; +import org.hibernate.mapping.Component; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.reactive.id.ReactiveIdentifierGenerator; +import org.hibernate.reactive.session.ReactiveConnectionSupplier; +import org.hibernate.reactive.tuple.entity.ReactiveEntityMetamodel; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; + +public class ReactiveCompositeNestedGeneratedValueGenerator extends CompositeNestedGeneratedValueGenerator implements + ReactiveIdentifierGenerator { + + public ReactiveCompositeNestedGeneratedValueGenerator( + CompositeNestedGeneratedValueGenerator generator, + GeneratorCreationContext creationContext, + RuntimeModelCreationContext runtimeModelCreationContext) { + super( + generator.getGenerationContextLocator(), + generator.getCompositeType(), + reactivePlans( generator, creationContext, runtimeModelCreationContext ) + ); + } + + private static List reactivePlans( + CompositeNestedGeneratedValueGenerator generator, + GeneratorCreationContext creationContext, + RuntimeModelCreationContext runtimeModelCreationContext) { + final List plans = new ArrayList<>(); + for ( GenerationPlan plan : generator.getGenerationPlans() ) { + final GenerationPlan reactivePlane = new Component.ValueGenerationPlan( + (BeforeExecutionGenerator) ReactiveEntityMetamodel.augmentWithReactiveGenerator( + plan.getGenerator(), + creationContext, + runtimeModelCreationContext + ), + plan.getInjector(), + plan.getPropertyIndex() + ); + plans.add( reactivePlane ); + } + return plans; + } + + @Override + public CompletionStage generate(ReactiveConnectionSupplier reactiveConnectionSupplier, Object object) { + SharedSessionContractImplementor session = (SharedSessionContractImplementor) reactiveConnectionSupplier; + final Object context = getGenerationContextLocator().locateGenerationContext( session, object ); + + final List generatedValues = getCompositeType().isMutable() + ? null + : new ArrayList<>( getGenerationPlans().size() ); + return loop( getGenerationPlans(), generationPlan -> generateIdentifier( + reactiveConnectionSupplier, + object, + generationPlan, + session, + generatedValues, + context + ) ) + .thenCompose( v -> handleGeneratedValues( generatedValues, context, session ) ); + } + + private CompletionStage generateIdentifier( + ReactiveConnectionSupplier reactiveConnectionSupplier, + Object object, + GenerationPlan generationPlan, + SharedSessionContractImplementor session, + List generatedValues, + Object context) { + final Generator generator = generationPlan.getGenerator(); + if ( generator.generatedBeforeExecution( object, session ) ) { + if ( generator instanceof ReactiveIdentifierGenerator reactiveIdentifierGenerator ) { + return reactiveIdentifierGenerator + .generate( reactiveConnectionSupplier, object ) + .thenAccept( generated -> { + if ( generatedValues != null ) { + generatedValues.add( generated ); + } + else { + generationPlan.getInjector().set( context, generated ); + } + } ); + } + else { + final Object currentValue = generator.allowAssignedIdentifiers() + ? getCompositeType().getPropertyValue( context, generationPlan.getPropertyIndex(), session ) + : null; + return completedFuture( ( (BeforeExecutionGenerator) generator ) + .generate( session, object, currentValue, INSERT ) ); + } + } + else { + throw new IdentifierGenerationException( "Identity generation isn't supported for composite ids" ); + } + } + + private CompletionStage handleGeneratedValues( + List generatedValues, + Object context, + SharedSessionContractImplementor session) { + if ( generatedValues != null ) { + final Object[] values = getCompositeType().getPropertyValues( context ); + for ( int i = 0; i < generatedValues.size(); i++ ) { + values[getGenerationPlans().get( i ).getPropertyIndex()] = generatedValues.get( i ); + } + return completedFuture( getCompositeType().replacePropertyValues( context, values, session ) ); + } + else { + return completedFuture( context ); + } + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java index 2b4063173..90b098652 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java @@ -91,7 +91,7 @@ public TableMutationBuilder createTableMutationBuilder( return new TableInsertReturningBuilder( persister, tableReference, generatedColumns, sessionFactory ); } else { - return new TableUpdateReturningBuilder<>( persister, tableReference, generatedColumns, sessionFactory ); + return new TableUpdateReturningBuilder( persister, tableReference, generatedColumns, sessionFactory ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java index 44c90bfdf..e15c42956 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java @@ -9,10 +9,23 @@ import java.util.List; import java.util.concurrent.CompletionStage; +import org.hibernate.Hibernate; +import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.ObjectDeletedException; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; +import org.hibernate.event.monitor.spi.DiagnosticEvent; +import org.hibernate.event.monitor.spi.EventMonitor; +import org.hibernate.event.spi.EventSource; +import org.hibernate.internal.OptimisticLockHelper; +import org.hibernate.loader.LoaderLogging; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.tree.expression.JdbcParameter; @@ -25,6 +38,8 @@ import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import static java.util.Objects.requireNonNull; +import static org.hibernate.reactive.util.impl.CompletionStages.supplyStage; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * @see org.hibernate.loader.ast.internal.LoaderHelper @@ -92,4 +107,132 @@ public static CompletionStage> loadByArrayParameter( ReactiveListResultsConsumer.UniqueSemantic.FILTER ); } + + /** + * A Reactive implementation of {@link org.hibernate.loader.ast.internal.LoaderHelper#upgradeLock(Object, EntityEntry, LockOptions, SharedSessionContractImplementor)} + */ + public static CompletionStage upgradeLock( + Object object, + EntityEntry entry, + LockOptions lockOptions, + SharedSessionContractImplementor session) { + final LockMode requestedLockMode = lockOptions.getLockMode(); + if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) { + // Request is for a more restrictive lock than the lock already held + final ReactiveEntityPersister persister = (ReactiveEntityPersister) entry.getPersister(); + + if ( entry.getStatus().isDeletedOrGone()) { + throw new ObjectDeletedException( + "attempted to lock a deleted instance", + entry.getId(), + persister.getEntityName() + ); + } + + if ( LoaderLogging.LOADER_LOGGER.isTraceEnabled() ) { + LoaderLogging.LOADER_LOGGER.tracef( + "Locking `%s( %s )` in `%s` lock-mode", + persister.getEntityName(), + entry.getId(), + requestedLockMode + ); + } + + final boolean cachingEnabled = persister.canWriteToCache(); + SoftLock lock = null; + Object ck = null; + try { + if ( cachingEnabled ) { + final EntityDataAccess cache = persister.getCacheAccessStrategy(); + ck = cache.generateCacheKey( entry.getId(), persister, session.getFactory(), session.getTenantIdentifier() ); + lock = cache.lockItem( session, ck, entry.getVersion() ); + } + + if ( persister.isVersioned() && entry.getVersion() == null ) { + // This should be an empty entry created for an uninitialized bytecode proxy + if ( !Hibernate.isPropertyInitialized( object, persister.getVersionMapping().getPartName() ) ) { + Hibernate.initialize( object ); + entry = session.getPersistenceContextInternal().getEntry( object ); + assert entry.getVersion() != null; + } + else { + throw new IllegalStateException( String.format( + "Trying to lock versioned entity %s but found null version", + MessageHelper.infoString( persister.getEntityName(), entry.getId() ) + ) ); + } + } + + if ( persister.isVersioned() && requestedLockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) { + // todo : should we check the current isolation mode explicitly? + OptimisticLockHelper.forceVersionIncrement( object, entry, session ); + } + else if ( entry.isExistsInDatabase() ) { + final EventMonitor eventMonitor = session.getEventMonitor(); + final DiagnosticEvent entityLockEvent = eventMonitor.beginEntityLockEvent(); + return reactiveLock( object, entry, lockOptions, session, persister, eventMonitor, entityLockEvent, cachingEnabled, ck, lock ); + } + else { + // should only be possible for a stateful session + if ( session instanceof EventSource eventSource ) { + eventSource.forceFlush( entry ); + } + } + entry.setLockMode(requestedLockMode); + } + finally { + // the database now holds a lock + the object is flushed from the cache, + // so release the soft lock + if ( cachingEnabled ) { + persister.getCacheAccessStrategy().unlockItem( session, ck, lock ); + } + } + } + return voidFuture(); + } + + private static CompletionStage reactiveLock( + Object object, + EntityEntry entry, + LockOptions lockOptions, + SharedSessionContractImplementor session, + ReactiveEntityPersister persister, + EventMonitor eventMonitor, + DiagnosticEvent entityLockEvent, + boolean cachingEnabled, + Object ck, + SoftLock lock) { + return supplyStage( () -> supplyStage( () -> persister.reactiveLock( entry.getId(), entry.getVersion(), object, lockOptions, session ) ) + .whenComplete( (v, e) -> completeLockEvent( entry, lockOptions, session, persister, eventMonitor, entityLockEvent, cachingEnabled, ck, lock, e == null ) ) ) + .whenComplete( (v, e) -> { + if ( cachingEnabled ) { + persister.getCacheAccessStrategy().unlockItem( session, ck, lock ); + } + } ); + } + + private static void completeLockEvent( + EntityEntry entry, + LockOptions lockOptions, + SharedSessionContractImplementor session, + ReactiveEntityPersister persister, + EventMonitor eventMonitor, + DiagnosticEvent entityLockEvent, + boolean cachingEnabled, + Object ck, + SoftLock lock, + boolean succes) { + eventMonitor.completeEntityLockEvent( + entityLockEvent, + entry.getId(), + persister.getEntityName(), + lockOptions.getLockMode(), + succes, + session + ); + if ( cachingEnabled ) { + persister.getCacheAccessStrategy().unlockItem( session, ck, lock ); + } + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/internal/ReactiveCacheLoadHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/internal/ReactiveCacheLoadHelper.java new file mode 100644 index 000000000..1e6f3eddd --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/internal/ReactiveCacheLoadHelper.java @@ -0,0 +1,46 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.loader.internal; + +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.spi.LoadEventListener; +import org.hibernate.loader.internal.CacheLoadHelper; +import org.hibernate.loader.internal.CacheLoadHelper.PersistenceContextEntry.EntityStatus; + +import java.util.concurrent.CompletionStage; + +import static org.hibernate.loader.internal.CacheLoadHelper.PersistenceContextEntry.EntityStatus.MANAGED; +import static org.hibernate.reactive.loader.ast.internal.ReactiveLoaderHelper.upgradeLock; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; + +/** + * A reactive implementation of {@link CacheLoadHelper} + */ +public class ReactiveCacheLoadHelper { + + public static CompletionStage loadFromSessionCache( + EntityKey keyToLoad, + LockOptions lockOptions, + LoadEventListener.LoadType options, + SharedSessionContractImplementor session) { + final Object old = session.getEntityUsingInterceptor( keyToLoad ); + EntityStatus entityStatus = MANAGED; + if ( old != null ) { + // this object was already loaded + final EntityEntry oldEntry = session.getPersistenceContext().getEntry( old ); + entityStatus = CacheLoadHelper.entityStatus( keyToLoad, options, session, oldEntry, old ); + if ( entityStatus == MANAGED ) { + return upgradeLock( old, oldEntry, lockOptions, session ) + .thenApply(v -> new CacheLoadHelper.PersistenceContextEntry( old, MANAGED ) ); + } + } + return completedFuture( new CacheLoadHelper.PersistenceContextEntry( old, entityStatus ) ); + } + +} 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 4f205492e..fa29cd8b5 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 @@ -7,6 +7,7 @@ +import java.sql.SQLException; import java.sql.SQLWarning; import jakarta.persistence.PersistenceException; @@ -266,6 +267,9 @@ public interface Log extends BasicLogger { @Message(id = 83, value = "Unexpected request of a non reactive connection") HibernateException unexpectedConnectionRequest(); + @Message(id = 84, value = "The application requested a JDBC connection, but Hibernate Reactive doesn't use JDBC. This could be caused by a bug or the use of an unsupported feature in Hibernate Reactive") + SQLException notUsingJdbc(); + // Same method that exists in CoreMessageLogger @LogMessage(level = WARN) @Message(id = 104, value = "firstResult/maxResults specified with collection fetch; applying in memory!" ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java index 3f5244fa9..204a23d3a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java @@ -13,6 +13,7 @@ import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableFetchImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlSelection; @@ -20,6 +21,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; public class ReactiveEmbeddedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping { @@ -104,4 +106,13 @@ public String getSqlAliasStem() { public String getFetchableName() { return delegate.getFetchableName(); } + + @Override + public Fetchable getFetchable(int position) { + Fetchable fetchable = delegate.getFetchable( position ); + if ( fetchable instanceof ToOneAttributeMapping ) { + return new ReactiveToOneAttributeMapping( (ToOneAttributeMapping) fetchable ); + } + return fetchable; + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java index 0ebebe603..129b9e236 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java @@ -80,7 +80,6 @@ import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.logSqlException; import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -303,25 +302,25 @@ default CompletionStage reactiveGetCurrentVersion(Object id, SharedSessi return getReactiveConnection( session ) .selectJdbc( delegate().getVersionSelectString(), params ) - .thenCompose( resultSet -> currentVersion( session, resultSet ) ); + .thenApply( resultSet -> currentVersion( session, resultSet ) ); } - private CompletionStage currentVersion(SharedSessionContractImplementor session, ResultSet resultSet) { + private Object currentVersion(SharedSessionContractImplementor session, ResultSet resultSet) { try { if ( !resultSet.next() ) { - return nullFuture(); + return null; } if ( !isVersioned() ) { - return completedFuture( this ); + return this; } - return completedFuture( getVersionType() - .getJdbcMapping() - .getJdbcValueExtractor() - .extract( resultSet, 1, session ) ); + return getVersionType() + .getJdbcMapping() + .getJdbcValueExtractor() + .extract( resultSet, 1, session ); } catch (SQLException sqle) { //can never happen - return failedFuture( new JDBCException( "error reading version", sqle ) ); + throw new JDBCException( "error reading version", sqle ); } } @@ -474,13 +473,9 @@ default CompletionStage reactiveInitializeEnhancedEntityUsedAsProxy( Object entity, String nameOfAttributeBeingAccessed, SharedSessionContractImplementor session) { - final BytecodeEnhancementMetadata enhancementMetadata = getEntityPersister().getBytecodeEnhancementMetadata(); final BytecodeLazyAttributeInterceptor currentInterceptor = enhancementMetadata.extractLazyInterceptor( entity ); - if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - final EnhancementAsProxyLazinessInterceptor proxyInterceptor = - (EnhancementAsProxyLazinessInterceptor) currentInterceptor; - + if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor proxyInterceptor ) { final EntityKey entityKey = proxyInterceptor.getEntityKey(); final Object identifier = entityKey.getIdentifier(); @@ -494,19 +489,17 @@ default CompletionStage reactiveInitializeEnhancedEntityUsedAsProxy( .handleEntityNotFound( entityKey.getEntityName(), identifier ); } + final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata + .injectInterceptor( entity, identifier, session ); + if ( nameOfAttributeBeingAccessed == null ) { return null; } else { - final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata - .injectInterceptor( entity, identifier, session ); return interceptor.readObject( - entity, - nameOfAttributeBeingAccessed, - interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) - ? getPropertyValue( entity, nameOfAttributeBeingAccessed ) - : ( (LazyPropertyInitializer) this ) - .initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ) + entity, nameOfAttributeBeingAccessed, interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) + ? getPropertyValue( entity, nameOfAttributeBeingAccessed ) + : ( (LazyPropertyInitializer) this ).initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ) ); } } ); @@ -528,11 +521,12 @@ private CompletionStage loadFromDatabaseOrCache( return completedFuture( loaded ); } } - return ( (ReactiveSingleIdEntityLoader) determineLoaderToUse( session ) ) - .load( identifier, entity, LockOptions.NONE, session ); + final LockOptions lockOptions = new LockOptions(); + return ( (ReactiveSingleIdEntityLoader) determineLoaderToUse( session, lockOptions ) ) + .load( identifier, entity, lockOptions, session ); } - SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session); + SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions); boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java index 0182ef403..2443d4d9b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java @@ -139,8 +139,8 @@ protected AttributeMapping buildPluralAttributeMapping( } @Override - public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { - return super.determineLoaderToUse( session ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions) { + return super.determineLoaderToUse( session, lockOptions ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java index 3006a0eae..2f5878737 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java @@ -86,8 +86,8 @@ public GeneratedValuesMutationDelegate createInsertDelegate() { } @Override - public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { - return super.determineLoaderToUse( session ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions) { + return super.determineLoaderToUse( session, lockOptions ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java index 52871e30f..bba3cf86d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java @@ -139,8 +139,8 @@ protected AttributeMapping buildPluralAttributeMapping( } @Override - public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { - return super.determineLoaderToUse( session ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions) { + return super.determineLoaderToUse( session, lockOptions ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPool.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPool.java index 67476f71e..5249a79e5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPool.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/DefaultSqlClientPool.java @@ -13,6 +13,7 @@ import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; @@ -31,13 +32,13 @@ import io.vertx.core.Future; import io.vertx.core.Vertx; +import io.vertx.core.net.NetClientOptions; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.PoolOptions; import io.vertx.sqlclient.SqlConnectOptions; +import io.vertx.sqlclient.impl.Utils; import io.vertx.sqlclient.spi.Driver; -import static java.util.Collections.singletonList; - /** * A pool of reactive connections backed by a Vert.x {@link Pool}. * The {@code Pool} itself is backed by an instance of {@link Vertx} @@ -189,7 +190,7 @@ protected Pool createPool(URI uri) { * * @return the new {@link Pool} */ - protected Pool createPool(URI uri, SqlConnectOptions connectOptions, PoolOptions poolOptions, Vertx vertx) { + protected Pool createPool(URI uri, T connectOptions, PoolOptions poolOptions, Vertx vertx) { try { // First try to load the Pool using the standard ServiceLoader pattern // This only works if exactly 1 Driver is on the classpath. @@ -198,8 +199,9 @@ protected Pool createPool(URI uri, SqlConnectOptions connectOptions, PoolOptions catch (ServiceConfigurationError e) { // Backup option if multiple drivers are on the classpath. // We will be able to remove this once Vertx 3.9.2 is available - final Driver driver = findDriver( uri, e ); - return driver.createPool( vertx, singletonList( connectOptions ), poolOptions ); + final Driver driver = findDriver( uri, e ); + Supplier> database = Utils.singletonSupplier( driver.downcast( connectOptions ) ); + return driver.createPool( vertx, database, poolOptions, new NetClientOptions(), null ); } } @@ -222,15 +224,14 @@ protected URI jdbcUrl(Map configurationValues) { * so we need to disambiguate according to the scheme specified * in the given {@link URI}. * - * @param uri the JDBC URL or database URI + * @param uri the JDBC URL or database URI * @param originalError the error that was thrown - * * @return the disambiguated {@link Driver} */ - private Driver findDriver(URI uri, ServiceConfigurationError originalError) { + private Driver findDriver(URI uri, ServiceConfigurationError originalError) { String scheme = scheme( uri ); - List selected = new ArrayList<>(); - for ( Driver d : ServiceLoader.load( Driver.class ) ) { + List> selected = new ArrayList<>(); + for ( Driver d : ServiceLoader.load( Driver.class ) ) { String driverName = d.getClass().getCanonicalName(); if ( matchesScheme( driverName, scheme ) ) { LOG.selectedDriver( driverName ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java index 2343a9644..13b0ae6b8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java @@ -22,6 +22,7 @@ import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator; import org.hibernate.engine.jndi.internal.JndiServiceInitiator; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; +import org.hibernate.internal.util.cache.InternalCacheFactoryInitiator; import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; import org.hibernate.reactive.context.impl.VertxContextInitiator; @@ -161,6 +162,9 @@ private static List> buildInitialServiceInitiatorLis // Custom for Hibernate Reactive: BatchLoaderFactory serviceInitiators.add( ReactiveBatchLoaderFactoryInitiator.INSTANCE ); + // [standard] InternalCacheFactoryService + serviceInitiators.add( InternalCacheFactoryInitiator.INSTANCE ); + // --- end of services defined by Hibernate ORM // --- custom ones follow: diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java index 5c3380f65..a02c3e152 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java @@ -6,10 +6,14 @@ package org.hibernate.reactive.provider.service; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.reactive.logging.impl.Log; import java.sql.Connection; import java.sql.SQLException; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + /** * A dummy Hibernate {@link ConnectionProvider} throws an * exception if a JDBC connection is requested. @@ -17,12 +21,13 @@ * @author Gavin King */ public class NoJdbcConnectionProvider implements ConnectionProvider { + private static final Log LOG = make( Log.class, lookup() ); public static final NoJdbcConnectionProvider INSTANCE = new NoJdbcConnectionProvider(); @Override public Connection getConnection() throws SQLException { - throw new SQLException( "Not using JDBC" ); + throw LOG.notUsingJdbc(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java index 6ca5b2024..d58ae57a9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java @@ -142,19 +142,14 @@ private ReactiveSelectQueryPlan reactiveSelectPlan() { private ReactiveNonSelectQueryPlan reactiveNonSelectPlan() { final QueryInterpretationCache.Key cacheKey = generateNonSelectInterpretationsKey(); if ( cacheKey != null ) { - NonSelectQueryPlan queryPlan = getSession().getFactory().getQueryEngine() - .getInterpretationCache().getNonSelectQueryPlan( cacheKey ); + NonSelectQueryPlan queryPlan = getSession().getFactory().getQueryEngine().getInterpretationCache().getNonSelectQueryPlan( cacheKey ); if ( queryPlan != null ) { return (ReactiveNonSelectQueryPlan) queryPlan; } } - final String sqlString = expandParameterLists(); - ReactiveNonSelectQueryPlan queryPlan = new ReactiveNativeNonSelectQueryPlan( - sqlString, - getQuerySpaces(), - getParameterOccurrences() - ); + final String sqlString = expandParameterLists( 1 ); + ReactiveNonSelectQueryPlan queryPlan = new ReactiveNativeNonSelectQueryPlan( sqlString, getQuerySpaces(), getParameterOccurrences() ); if ( cacheKey != null ) { getSession().getFactory().getQueryEngine().getInterpretationCache() .cacheNonSelectQueryPlan( cacheKey, queryPlan ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java index f1eca36df..bae87b8b3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java @@ -11,7 +11,6 @@ import java.util.Set; import java.util.concurrent.CompletionStage; -import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.results.ResultSetMapping; import org.hibernate.query.spi.DomainQueryExecutionContext; @@ -23,7 +22,6 @@ import org.hibernate.query.sql.spi.ParameterOccurrence; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.query.internal.ReactiveResultSetMappingProcessor; import org.hibernate.reactive.query.spi.ReactiveNativeSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; @@ -61,8 +59,7 @@ public ReactiveNativeSelectQueryPlanImpl( resultSetMapping.addAffectedTableNames( affectedTableNames, sessionFactory ); } this.affectedTableNames = affectedTableNames; - Dialect dialect = sessionFactory.getJdbcServices().getDialect(); - this.sql = Parameters.instance( dialect ).process( parser.process() ); + this.sql = parser.process(); this.parameterList = parameterList; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java index 388488b26..aae4316a3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java @@ -20,7 +20,7 @@ import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; +import org.hibernate.reactive.query.sqm.internal.ReactiveSqmQueryImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; /** @@ -49,10 +49,10 @@ public SqmQueryImplementor toQuery(SharedSessionContractImplementor session) public SqmQueryImplementor toQuery(SharedSessionContractImplementor session, Class resultType) { // A bit of a hack, I'm sure that if we have a better look at this we can avoid the instanceof if ( delegate instanceof NamedHqlQueryMementoImpl ) { - return new ReactiveQuerySqmImpl<>( (NamedHqlQueryMementoImpl) delegate, resultType, session ); + return new ReactiveSqmQueryImpl<>( (NamedHqlQueryMementoImpl) delegate, resultType, session ); } if ( delegate instanceof NamedCriteriaQueryMementoImpl ) { - return new ReactiveQuerySqmImpl<>( (NamedCriteriaQueryMementoImpl) delegate, resultType, session ); + return new ReactiveSqmQueryImpl<>( (NamedCriteriaQueryMementoImpl) delegate, resultType, session ); } else { throw new UnsupportedOperationException( "NamedSqmQueryMemento not recognized: " + delegate.getClass() ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java index 0794ae435..a43d917e9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java @@ -24,7 +24,7 @@ /** * @see org.hibernate.query.sqm.SqmSelectionQuery */ -public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery, SqmQuery { +public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery, SqmQuery { @Override ReactiveSqmSelectionQuery setParameter(String name, Object value); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java index 7c7cfd062..4f499f5cf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java @@ -6,40 +6,25 @@ package org.hibernate.reactive.query.sqm.internal; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; import org.hibernate.ScrollMode; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; -import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.Query; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.ScrollableResultsImplementor; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.sql.SqmTranslator; -import org.hibernate.query.sqm.sql.SqmTranslatorFactory; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; @@ -68,7 +53,7 @@ public class ConcreteSqmSelectReactiveQueryPlan extends ConcreteSqmSelectQuer private final SqmSelectStatement sqm; private final DomainParameterXref domainParameterXref; - private volatile CacheableSqmInterpretation cacheableSqmInterpretation; + private volatile CacheableSqmInterpretation cacheableSqmInterpretation; public ConcreteSqmSelectReactiveQueryPlan( SqmSelectStatement sqm, @@ -91,27 +76,46 @@ private static CompletionStage> listInterpreter( String hql, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, RowTransformer rowTransformer) { final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); - // I'm using a supplier so that the whenComplete at the end will catch any errors, like a finally-block - Supplier fetchHandlerSupplier = () -> SubselectFetch - .createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.selectStatement, JdbcParametersList.empty(), jdbcParameterBindings ); - return completedFuture( fetchHandlerSupplier ) - .thenApply( Supplier::get ) - .thenCompose( subSelectFetchKeyHandler -> session + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); + + return CompletionStages + .supplyStage( () -> { + final var subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( + session.getPersistenceContext() + .getBatchFetchQueue(), + sqmInterpretation.statement(), + JdbcParametersList.empty(), + jdbcParameterBindings + ); + return session .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) - .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE - .list( jdbcSelect, - jdbcParameterBindings, - ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), - rowTransformer, - ReactiveListResultsConsumer.UniqueSemantic.ALLOW - ) - ) - ) + .thenCompose( required -> { + final Expression fetchExpression = sqmInterpretation.statement() + .getQueryPart() + .getFetchClauseExpression(); + final int resultCountEstimate = fetchExpression != null + ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) + : -1; + return StandardReactiveSelectExecutor.INSTANCE.list( + jdbcSelect, + jdbcParameterBindings, + ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( + hql, + executionContext, + jdbcSelect, + subSelectFetchKeyHandler + ), + rowTransformer, + (Class) executionContext.getResultType(), + ReactiveListResultsConsumer.UniqueSemantic.ALLOW, + resultCountEstimate + ); + } ); + } ) .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } @@ -119,43 +123,47 @@ private static CompletionStage executeQueryInterpreter( String hql, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, RowTransformer rowTransformer, ReactiveResultsConsumer resultsConsumer) { final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); - // I'm using a supplier so that the whenComplete at the end will catch any errors, like a finally-block - Supplier fetchHandlerSupplier = () -> SubselectFetch - .createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.selectStatement, JdbcParametersList.empty(), jdbcParameterBindings ); - return completedFuture( fetchHandlerSupplier ) - .thenApply( Supplier::get ) - .thenCompose( subSelectFetchKeyHandler -> session - .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) - .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE - .executeQuery( - jdbcSelect, - jdbcParameterBindings, - ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( - hql, - executionContext, - jdbcSelect, - subSelectFetchKeyHandler - ), - rowTransformer, - null, - resultCountEstimate( sqmInterpretation, jdbcParameterBindings ), - resultsConsumer - ) - ) - ) + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); + return CompletionStages + .supplyStage( () -> { + final var subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( + session.getPersistenceContext() + .getBatchFetchQueue(), + sqmInterpretation.statement(), + JdbcParametersList.empty(), + jdbcParameterBindings + ); + return session + .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) + .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE + .executeQuery( + jdbcSelect, + jdbcParameterBindings, + ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( + hql, + executionContext, + jdbcSelect, + subSelectFetchKeyHandler + ), + rowTransformer, + null, + resultCountEstimate( sqmInterpretation, jdbcParameterBindings ), + resultsConsumer + ) + ); + }) .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } private static int resultCountEstimate( - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings) { - final Expression fetchExpression = sqmInterpretation.selectStatement.getQueryPart() + final Expression fetchExpression = sqmInterpretation.statement().getQueryPart() .getFetchClauseExpression(); return fetchExpression != null ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) @@ -191,16 +199,16 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec // to protect access. However, synchronized is much simpler here. We will verify // during throughput testing whether this is an issue and consider changes then - CacheableSqmInterpretation localCopy = cacheableSqmInterpretation; + CacheableSqmInterpretation localCopy = cacheableSqmInterpretation; JdbcParameterBindings jdbcParameterBindings = null; if ( localCopy == null ) { synchronized ( this ) { localCopy = cacheableSqmInterpretation; if ( localCopy == null ) { - localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext ); - jdbcParameterBindings = localCopy.firstParameterBindings; - localCopy.firstParameterBindings = null; + final MutableObject mutableValue = new MutableObject<>(); + localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue ); + jdbcParameterBindings = mutableValue.get(); cacheableSqmInterpretation = localCopy; } } @@ -208,15 +216,15 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec else { // If the translation depends on parameter bindings, or it isn't compatible with the current query options, // we have to rebuild the JdbcSelect, which is still better than having to translate from SQM to SQL AST again - if ( localCopy.jdbcSelect.dependsOnParameterBindings() ) { + if ( localCopy.jdbcOperation().dependsOnParameterBindings() ) { jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext ); } // If the translation depends on the limit or lock options, we have to rebuild the JdbcSelect // We could avoid this by putting the lock options into the cache key - if ( !localCopy.jdbcSelect.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext ); - jdbcParameterBindings = localCopy.firstParameterBindings; - localCopy.firstParameterBindings = null; + if ( !localCopy.jdbcOperation().isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { + final MutableObject mutableValue = new MutableObject<>(); + localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue ); + jdbcParameterBindings = mutableValue.get(); cacheableSqmInterpretation = localCopy; } } @@ -228,130 +236,12 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec return interpreter.interpret( context, executionContext, localCopy, jdbcParameterBindings ); } - // Copy and paste from ORM - private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) { - return SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - sqmInterpretation.getJdbcParamsXref(), - new SqmParameterMappingModelResolutionAccess() { - //this is pretty ugly! - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypes().get(parameter); - } - }, - executionContext.getSession() - ); - } - - private static CacheableSqmInterpretation buildCacheableSqmInterpretation( - SqmSelectStatement sqm, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext executionContext) { - final SharedSessionContractImplementor session = executionContext.getSession(); - final SessionFactoryImplementor sessionFactory = session.getFactory(); - final QueryEngine queryEngine = sessionFactory.getQueryEngine(); - - final SqmTranslatorFactory sqmTranslatorFactory = queryEngine.getSqmTranslatorFactory(); - - final SqmTranslator sqmConverter = sqmTranslatorFactory.createSelectTranslator( - sqm, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - sessionFactory.getSqlTranslationEngine(), - true - ); - -// tableGroupAccess = sqmConverter.getFromClauseAccess(); - final SqmTranslation sqmInterpretation = sqmConverter.translate(); - final FromClauseAccess tableGroupAccess = sqmConverter.getFromClauseAccess(); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final SqlAstTranslator selectTranslator = sqlAstTranslatorFactory - .buildSelectTranslator( sessionFactory, sqmInterpretation.getSqlAst() ); - final Map, Map, List>> jdbcParamsXref = SqmUtil - .generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam ); - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter); - } - }, - session - ); - - final JdbcOperationQuerySelect jdbcSelect = selectTranslator.translate( - jdbcParameterBindings, - executionContext.getQueryOptions() - ); - - return new CacheableSqmInterpretation( - sqmInterpretation.getSqlAst(), - jdbcSelect, - tableGroupAccess, - jdbcParamsXref, - sqmInterpretation.getSqmParameterMappingModelTypeResolutions(), - jdbcParameterBindings - ); - } - private interface SqmInterpreter { CompletionStage interpret( X context, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings); } - private static class CacheableSqmInterpretation { - private final SelectStatement selectStatement; - private final JdbcOperationQuerySelect jdbcSelect; - private final FromClauseAccess tableGroupAccess; - private final Map, Map, List>> jdbcParamsXref; - private final Map, MappingModelExpressible> sqmParameterMappingModelTypes; - private transient JdbcParameterBindings firstParameterBindings; - - CacheableSqmInterpretation( - SelectStatement selectStatement, - JdbcOperationQuerySelect jdbcSelect, - FromClauseAccess tableGroupAccess, - Map, Map, List>> jdbcParamsXref, - Map, MappingModelExpressible> sqmParameterMappingModelTypes, - JdbcParameterBindings firstParameterBindings) { - this.selectStatement = selectStatement; - this.jdbcSelect = jdbcSelect; - this.tableGroupAccess = tableGroupAccess; - this.jdbcParamsXref = jdbcParamsXref; - this.sqmParameterMappingModelTypes = sqmParameterMappingModelTypes; - this.firstParameterBindings = firstParameterBindings; - } - - SelectStatement getSelectStatement() { - return selectStatement; - } - - JdbcOperationQuerySelect getJdbcSelect() { - return jdbcSelect; - } - - FromClauseAccess getTableGroupAccess() { - return tableGroupAccess; - } - - Map, Map, List>> getJdbcParamsXref() { - return jdbcParamsXref; - } - - public Map, MappingModelExpressible> getSqmParameterMappingModelTypes() { - return sqmParameterMappingModelTypes; - } - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java new file mode 100644 index 000000000..a1759ccbc --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java @@ -0,0 +1,35 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.internal; + +import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.AbstractMultiTableMutationQueryPlan; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.tree.SqmDmlStatement; +import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; + +import java.util.concurrent.CompletionStage; + +public abstract class ReactiveAbstractMultiTableMutationQueryPlan, F> + extends AbstractMultiTableMutationQueryPlan implements ReactiveNonSelectQueryPlan { + + public ReactiveAbstractMultiTableMutationQueryPlan( + S statement, + DomainParameterXref domainParameterXref, + F strategy) { + super( statement, domainParameterXref, strategy ); + } + + @Override + public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), getStatement() ); + final Interpretation interpretation = getInterpretation( context ); + return ((ReactiveHandler)interpretation.handler()).reactiveExecute( interpretation.jdbcParameterBindings(), context ); + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java index 90bdf6c49..30a77ba2d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java @@ -5,35 +5,32 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; /** * @see org.hibernate.query.sqm.internal.MultiTableDeleteQueryPlan */ -public class ReactiveMultiTableDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; - private final ReactiveSqmMultiTableMutationStrategy deleteStrategy; +public class ReactiveMultiTableDeleteQueryPlan + extends ReactiveAbstractMultiTableMutationQueryPlan, SqmMultiTableMutationStrategy> { public ReactiveMultiTableDeleteQueryPlan( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, ReactiveSqmMultiTableMutationStrategy deleteStrategy) { - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; - this.deleteStrategy = deleteStrategy; + super( sqmDelete, domainParameterXref, deleteStrategy ); } @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete ); - return deleteStrategy.reactiveExecuteDelete( sqmDelete, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmDeleteStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java index ed0e68b01..502f4f53c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java @@ -5,35 +5,32 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; /** * @see org.hibernate.query.sqm.internal.MultiTableInsertQueryPlan */ -public class ReactiveMultiTableInsertQueryPlan implements ReactiveNonSelectQueryPlan { - private final SqmInsertStatement sqmInsert; - private final DomainParameterXref domainParameterXref; - private final ReactiveSqmMultiTableInsertStrategy mutationStrategy; +public class ReactiveMultiTableInsertQueryPlan + extends ReactiveAbstractMultiTableMutationQueryPlan, SqmMultiTableInsertStrategy> { public ReactiveMultiTableInsertQueryPlan( SqmInsertStatement sqmInsert, DomainParameterXref domainParameterXref, ReactiveSqmMultiTableInsertStrategy mutationStrategy) { - this.sqmInsert = sqmInsert; - this.domainParameterXref = domainParameterXref; - this.mutationStrategy = mutationStrategy; + super( sqmInsert, domainParameterXref, mutationStrategy ); } @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmInsert ); - return mutationStrategy.reactiveExecuteInsert( sqmInsert, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmInsertStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableInsertStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java index 605186079..0715e06b5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java @@ -5,35 +5,31 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; /** * @see org.hibernate.query.sqm.internal.MultiTableUpdateQueryPlan */ -public class ReactiveMultiTableUpdateQueryPlan implements ReactiveNonSelectQueryPlan { - private final SqmUpdateStatement sqmUpdate; - private final DomainParameterXref domainParameterXref; - private final ReactiveSqmMultiTableMutationStrategy mutationStrategy; +public class ReactiveMultiTableUpdateQueryPlan + extends ReactiveAbstractMultiTableMutationQueryPlan, SqmMultiTableMutationStrategy> { public ReactiveMultiTableUpdateQueryPlan( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - ReactiveSqmMultiTableMutationStrategy mutationStrategy) { - this.sqmUpdate = sqmUpdate; - this.domainParameterXref = domainParameterXref; - this.mutationStrategy = mutationStrategy; + SqmMultiTableMutationStrategy mutationStrategy) { + super( sqmUpdate, domainParameterXref, mutationStrategy ); } @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmUpdate ); - return mutationStrategy.reactiveExecuteUpdate( sqmUpdate, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmUpdateStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java index eb055a38a..83113911b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java @@ -5,215 +5,89 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.sql.PreparedStatement; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - import org.hibernate.action.internal.BulkOperationCleanupAction; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SoftDeleteMapping; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SimpleDeleteQueryPlan; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.sql.SqmTranslator; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcParametersList; -import org.hibernate.sql.results.internal.SqlSelectionImpl; -import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.usingLockingAndPaging; - -public class ReactiveSimpleDeleteQueryPlan extends SimpleDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - private final EntityMappingType entityDescriptor; - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; +import java.lang.invoke.MethodHandles; +import java.util.concurrent.CompletionStage; - private JdbcOperationQueryMutation jdbcOperation; +public class ReactiveSimpleDeleteQueryPlan + extends SimpleDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - private SqmTranslation sqmInterpretation; - private Map, Map, List>> jdbcParamsXref; + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); public ReactiveSimpleDeleteQueryPlan( - EntityMappingType entityDescriptor, + EntityPersister entityDescriptor, SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref) { super( entityDescriptor, sqmDelete, domainParameterXref ); - this.entityDescriptor = entityDescriptor; - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; } @Override - protected SqlAstTranslator createTranslator(DomainQueryExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final SqmTranslator translator = factory.getQueryEngine().getSqmTranslatorFactory().createMutationTranslator( - sqmDelete, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ); - - sqmInterpretation = (SqmTranslation) translator.translate(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - return factory.getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, createDeleteAst() ); + protected int execute(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + throw LOG.nonReactiveMethodCall( "executeReactiveUpdate" ); } - // Copy and paste from superclass - private MutationStatement createDeleteAst() { - final MutationStatement ast; - if ( entityDescriptor.getSoftDeleteMapping() == null ) { - ast = sqmInterpretation.getSqlAst(); - } - else { - final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); - final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); - final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); - final Assignment assignment = columnMapping.createSoftDeleteAssignment( targetTable ); - - ast = new UpdateStatement( - targetTable, - Collections.singletonList( assignment ), - sqlDeleteAst.getRestriction() - ); - } - return ast; + @Override + public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), getStatement() ); + final Interpretation interpretation = getInterpretation( context ); + final ExecutionContext executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return executeReactive( interpretation.interpretation(), interpretation.jdbcParameterBindings(), executionContext ); } - @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete ); + protected CompletionStage executeReactive(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { final SharedSessionContractImplementor session = executionContext.getSession(); - final SessionFactoryImplementor factory = session.getFactory(); - SqlAstTranslator sqlAstTranslator = null; - if ( jdbcOperation == null ) { - sqlAstTranslator = createTranslator( executionContext ); - } - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter); - } - }, - session + return CompletionStages.loop(getCollectionTableDeletes(), delete -> + executeReactive( delete, jdbcParameterBindings, executionContext.getSession(), executionContext ) + ).thenCompose( v -> CompletionStages.loop( + getCollectionTableDeletes(), delete -> + executeReactive( + delete, + jdbcParameterBindings, + executionContext.getSession(), + executionContext + ) + ) + .thenCompose( unused -> executeReactive( + sqmInterpretation.jdbcOperation(), + jdbcParameterBindings, + session, + executionContext + ) ) ); - - if ( jdbcOperation != null - && !jdbcOperation.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - sqlAstTranslator = createTranslator( executionContext ); - } - - if ( sqlAstTranslator != null ) { - jdbcOperation = sqlAstTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - final boolean missingRestriction = sqmDelete.getWhereClause() == null - || sqmDelete.getWhereClause().getPredicate() == null; - if ( missingRestriction ) { - assert domainParameterXref.getSqmParameterCount() == 0; - assert jdbcParamsXref.isEmpty(); - } - - final SqmJdbcExecutionContextAdapter executionContextAdapter = usingLockingAndPaging( executionContext ); - - return ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - entityDescriptor, - (tableReference, attributeMapping) -> { - if ( missingRestriction ) { - return null; - } - - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final Expression fkColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor.getKeyPart(), - null, - factory - ); - - final QuerySpec matchingIdSubQuery = new QuerySpec( false ); - - final MutatingTableReferenceGroupWrapper tableGroup = new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - sqmInterpretation.getSqlAst().getTargetTable() - ); - final Expression fkTargetColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression( - tableGroup, - fkDescriptor.getTargetPart(), - sqmInterpretation.getSqlExpressionResolver(), - factory - ); - matchingIdSubQuery.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( 0, fkTargetColumnExpression ) ); - - matchingIdSubQuery.getFromClause().addRoot( - tableGroup - ); - - matchingIdSubQuery.applyPredicate( sqmInterpretation.getSqlAst().getRestriction() ); - - return new InSubQueryPredicate( fkColumnExpression, matchingIdSubQuery, false ); - }, - ( missingRestriction ? JdbcParameterBindings.NO_BINDINGS : jdbcParameterBindings ), - executionContextAdapter - ) - .thenCompose( unused -> StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcOperation, - jdbcParameterBindings, - session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, - ReactiveSimpleDeleteQueryPlan::doNothing, - executionContextAdapter ) - ); } - private static void doNothing(Integer i, PreparedStatement ps) { + private static CompletionStage executeReactive( + JdbcOperationQueryMutation delete, + JdbcParameterBindings jdbcParameterBindings, + SharedSessionContractImplementor executionContext, + ExecutionContext executionContext1) { + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + delete, + jdbcParameterBindings, + sql -> executionContext + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext1 + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java deleted file mode 100644 index bf67d16ea..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java +++ /dev/null @@ -1,121 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.internal; - - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcParametersList; - -/** - * @see org.hibernate.query.sqm.internal.SimpleInsertQueryPlan - */ -public class ReactiveSimpleInsertQueryPlan implements ReactiveNonSelectQueryPlan { - - private final SqmInsertStatement sqmInsert; - - private final DomainParameterXref domainParameterXref; - - private Map, MappingModelExpressible> paramTypeResolutions; - - private JdbcOperationQueryMutation jdbcInsert; - private Map, Map, List>> jdbcParamsXref; - - public ReactiveSimpleInsertQueryPlan( - SqmInsertStatement sqmInsert, - DomainParameterXref domainParameterXref) { - this.sqmInsert = sqmInsert; - this.domainParameterXref = domainParameterXref; - } - - @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmInsert ); - final SharedSessionContractImplementor session = executionContext.getSession(); - SqlAstTranslator insertTranslator = jdbcInsert == null - ? createInsertTranslator( executionContext ) - : null; - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - session - ); - - if ( jdbcInsert != null && !jdbcInsert.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - insertTranslator = createInsertTranslator( executionContext ); - } - - if ( insertTranslator != null ) { - jdbcInsert = insertTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcInsert, - jdbcParameterBindings, - session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, - (i, ps) -> {}, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ) - ); - } - - // Copied from Hibernate ORM SimpleInsertQueryPlan#createInsertTranslator - private SqlAstTranslator createInsertTranslator(DomainQueryExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - - final SqmTranslation sqmInterpretation = factory.getQueryEngine().getSqmTranslatorFactory() - .createMutationTranslator( - sqmInsert, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ) - .translate(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - this.paramTypeResolutions = sqmInterpretation.getSqmParameterMappingModelTypeResolutions(); - - return factory.getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqmInterpretation.getSqlAst() ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java new file mode 100644 index 000000000..1a2a942f5 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java @@ -0,0 +1,56 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.internal; + +import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SimpleNonSelectQueryPlan; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.tree.SqmDmlStatement; +import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import java.util.concurrent.CompletionStage; + +public class ReactiveSimpleNonSelectQueryPlan extends + SimpleNonSelectQueryPlan implements ReactiveNonSelectQueryPlan { + + public ReactiveSimpleNonSelectQueryPlan( + SqmDmlStatement statement, + DomainParameterXref domainParameterXref) { + super( statement, domainParameterXref ); + } + + @Override + public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), getStatement() ); + final Interpretation interpretation = getInterpretation( context ); + final ExecutionContext executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return executeReactive( interpretation.interpretation(), interpretation.jdbcParameterBindings(), executionContext ); + } + + protected CompletionStage executeReactive(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + final SharedSessionContractImplementor session = executionContext.getSession(); + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + sqmInterpretation.jdbcOperation(), + jdbcParameterBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java deleted file mode 100644 index f49f848f5..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java +++ /dev/null @@ -1,117 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.internal; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryEngine; -import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.sql.SqmTranslator; -import org.hibernate.query.sqm.sql.SqmTranslatorFactory; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.spi.FromClauseAccess; -import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcParametersList; - - -/** - * @see org.hibernate.query.sqm.internal.SimpleUpdateQueryPlan - */ -public class ReactiveSimpleUpdateQueryPlan implements ReactiveNonSelectQueryPlan { - - private final SqmUpdateStatement sqmUpdate; - private final DomainParameterXref domainParameterXref; - - private JdbcOperationQueryMutation jdbcUpdate; - private FromClauseAccess tableGroupAccess; - private Map, Map, List>> jdbcParamsXref; - private Map, MappingModelExpressible> sqmParamMappingTypeResolutions; - - public ReactiveSimpleUpdateQueryPlan(SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref) { - this.sqmUpdate = sqmUpdate; - this.domainParameterXref = domainParameterXref; - } - - @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmUpdate ); - final SharedSessionContractImplementor session = executionContext.getSession(); - SqlAstTranslator updateTranslator = jdbcUpdate == null - ? createUpdateTranslator( executionContext ) - : null; - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmParamMappingTypeResolutions.get(parameter); - } - }, - session - ); - - if ( jdbcUpdate != null && !jdbcUpdate.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - updateTranslator = createUpdateTranslator( executionContext ); - } - - if ( updateTranslator != null ) { - jdbcUpdate = updateTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcUpdate, - jdbcParameterBindings, - session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, - (i, ps) -> {}, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ) - ); - } - - // I can probably change ORM to reuse this - private SqlAstTranslator createUpdateTranslator(DomainQueryExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final QueryEngine queryEngine = factory.getQueryEngine(); - - final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); - final SqmTranslator translator = translatorFactory.createMutationTranslator( - sqmUpdate, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ); - - final SqmTranslation sqmInterpretation = translator.translate(); - tableGroupAccess = sqmInterpretation.getFromClauseAccess(); - this.jdbcParamsXref = SqmUtil - .generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam ); - this.sqmParamMappingTypeResolutions = sqmInterpretation.getSqmParameterMappingModelTypeResolutions(); - return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqmInterpretation.getSqlAst() ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java similarity index 80% rename from hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java rename to hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java index 5242ab747..b0bc9f823 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java @@ -44,11 +44,14 @@ import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.sqm.internal.QuerySqmImpl; import org.hibernate.query.sqm.internal.SqmInterpretationsKey; +import org.hibernate.query.sqm.internal.SqmQueryImpl; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; +import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; @@ -71,15 +74,15 @@ import jakarta.persistence.metamodel.Type; /** - * A reactive {@link QuerySqmImpl} + * A reactive {@link SqmQueryImpl} */ -public class ReactiveQuerySqmImpl extends QuerySqmImpl implements ReactiveSqmQueryImplementor { +public class ReactiveSqmQueryImpl extends SqmQueryImpl implements ReactiveSqmQueryImplementor { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final ReactiveAbstractSelectionQuery selectionQueryDelegate; - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( NamedHqlQueryMementoImpl memento, Class expectedResultType, SharedSessionContractImplementor session) { @@ -87,7 +90,7 @@ public ReactiveQuerySqmImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( NamedCriteriaQueryMementoImpl memento, Class resultType, SharedSessionContractImplementor session) { @@ -95,7 +98,7 @@ public ReactiveQuerySqmImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( String hql, HqlInterpretation hqlInterpretation, Class resultType, @@ -104,7 +107,7 @@ public ReactiveQuerySqmImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( SqmStatement criteria, Class resultType, SharedSessionContractImplementor session) { @@ -415,13 +418,13 @@ private ReactiveNonSelectQueryPlan buildUpdateQueryPlan() { final ReactiveSqmMultiTableMutationStrategy multiTableStrategy = (ReactiveSqmMultiTableMutationStrategy) entityDescriptor.getSqmMultiTableMutationStrategy(); return multiTableStrategy == null - ? new ReactiveSimpleUpdateQueryPlan( sqmUpdate, getDomainParameterXref() ) + ? new ReactiveSimpleNonSelectQueryPlan( sqmUpdate, getDomainParameterXref() ) : new ReactiveMultiTableUpdateQueryPlan( sqmUpdate, getDomainParameterXref(), multiTableStrategy ); } private ReactiveNonSelectQueryPlan buildInsertQueryPlan() { //noinspection rawtypes - final SqmInsertStatement sqmInsert = (SqmInsertStatement) getSqmStatement(); + final SqmInsertStatement sqmInsert = (SqmInsertStatement) getSqmStatement(); final String entityNameToInsert = sqmInsert.getTarget().getModel().getHibernateEntityName(); final EntityPersister persister = getSessionFactory() @@ -438,87 +441,101 @@ private ReactiveNonSelectQueryPlan buildInsertQueryPlan() { } } } - if ( !useMultiTableInsert ) { - return new ReactiveSimpleInsertQueryPlan( sqmInsert, getDomainParameterXref() ); - } - else { + if ( useMultiTableInsert ) { return new ReactiveMultiTableInsertQueryPlan( sqmInsert, getDomainParameterXref(), (ReactiveSqmMultiTableInsertStrategy) persister.getSqmMultiTableInsertStrategy() ); } + else if ( sqmInsert instanceof SqmInsertValuesStatement insertValues + && insertValues.getValuesList().size() != 1 + && !getSessionFactory().getJdbcServices().getDialect().supportsValuesListForInsert() ) { + // Split insert-values queries if the dialect doesn't support values lists + final List valuesList = insertValues.getValuesList(); + final ReactiveNonSelectQueryPlan[] planParts = new ReactiveNonSelectQueryPlan[valuesList.size()]; + for ( int i = 0; i < valuesList.size(); i++ ) { + final SqmInsertValuesStatement subInsert = + insertValues.copyWithoutValues( SqmCopyContext.simpleContext() ); + subInsert.values( valuesList.get( i ) ); + planParts[i] = new ReactiveSimpleNonSelectQueryPlan( subInsert, getDomainParameterXref() ); + } + + return new ReactiveAggregatedNonSelectQueryPlan( planParts ); + } + + return new ReactiveSimpleNonSelectQueryPlan( sqmInsert, getDomainParameterXref() ); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // QueryOptions @Override - public ReactiveQuerySqmImpl setHint(String hintName, Object value) { + public ReactiveSqmQueryImpl setHint(String hintName, Object value) { super.setHint( hintName, value ); return this; } @Override - public ReactiveQuerySqmImpl addQueryHint(String hint) { + public ReactiveSqmQueryImpl addQueryHint(String hint) { super.addQueryHint( hint ); return this; } @Override - public ReactiveQuerySqmImpl setLockOptions(LockOptions lockOptions) { + public ReactiveSqmQueryImpl setLockOptions(LockOptions lockOptions) { super.setLockOptions( lockOptions ); return this; } @Override - public ReactiveQuerySqmImpl setLockMode(String alias, LockMode lockMode) { + public ReactiveSqmQueryImpl setLockMode(String alias, LockMode lockMode) { super.setLockMode( alias, lockMode ); return this; } @Override - public ReactiveQuerySqmImpl setTupleTransformer(TupleTransformer transformer) { + public ReactiveSqmQueryImpl setTupleTransformer(TupleTransformer transformer) { throw new UnsupportedOperationException(); } @Override - public ReactiveQuerySqmImpl setResultListTransformer(ResultListTransformer transformer) { + public ReactiveSqmQueryImpl setResultListTransformer(ResultListTransformer transformer) { super.setResultListTransformer( transformer ); return this; } @Override @Deprecated - public ReactiveQuerySqmImpl setResultTransformer(ResultTransformer transformer) { + public ReactiveSqmQueryImpl setResultTransformer(ResultTransformer transformer) { throw new UnsupportedOperationException(); } @Override - public ReactiveQuerySqmImpl setMaxResults(int maxResult) { + public ReactiveSqmQueryImpl setMaxResults(int maxResult) { super.setMaxResults( maxResult ); return this; } @Override - public ReactiveQuerySqmImpl setFirstResult(int startPosition) { + public ReactiveSqmQueryImpl setFirstResult(int startPosition) { super.setFirstResult( startPosition ); return this; } @Override - public ReactiveQuerySqmImpl setHibernateFlushMode(FlushMode flushMode) { + public ReactiveSqmQueryImpl setHibernateFlushMode(FlushMode flushMode) { super.setHibernateFlushMode( flushMode ); return this; } @Override - public ReactiveQuerySqmImpl setFlushMode(FlushModeType flushMode) { + public ReactiveSqmQueryImpl setFlushMode(FlushModeType flushMode) { super.setFlushMode( flushMode ); return this; } @Override - public ReactiveQuerySqmImpl setLockMode(LockModeType lockMode) { + public ReactiveSqmQueryImpl setLockMode(LockModeType lockMode) { super.setLockMode( lockMode ); return this; } @@ -527,313 +544,313 @@ public ReactiveQuerySqmImpl setLockMode(LockModeType lockMode) { // covariance @Override - public ReactiveQuerySqmImpl applyGraph(RootGraph graph, GraphSemantic semantic) { + public ReactiveSqmQueryImpl applyGraph(RootGraph graph, GraphSemantic semantic) { super.applyGraph( graph, semantic ); return this; } @Override - public ReactiveQuerySqmImpl applyLoadGraph(RootGraph graph) { + public ReactiveSqmQueryImpl applyLoadGraph(RootGraph graph) { super.applyLoadGraph( graph ); return this; } @Override - public ReactiveQuerySqmImpl applyFetchGraph(RootGraph graph) { + public ReactiveSqmQueryImpl applyFetchGraph(RootGraph graph) { super.applyFetchGraph( graph ); return this; } @Override - public ReactiveQuerySqmImpl setComment(String comment) { + public ReactiveSqmQueryImpl setComment(String comment) { super.setComment( comment ); return this; } @Override - public ReactiveQuerySqmImpl setCacheMode(CacheMode cacheMode) { + public ReactiveSqmQueryImpl setCacheMode(CacheMode cacheMode) { super.setCacheMode( cacheMode ); return this; } @Override - public ReactiveQuerySqmImpl setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { + public ReactiveSqmQueryImpl setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { super.setCacheRetrieveMode( cacheRetrieveMode ); return this; } @Override - public ReactiveQuerySqmImpl setCacheStoreMode(CacheStoreMode cacheStoreMode) { + public ReactiveSqmQueryImpl setCacheStoreMode(CacheStoreMode cacheStoreMode) { super.setCacheStoreMode( cacheStoreMode ); return this; } @Override - public ReactiveQuerySqmImpl setCacheable(boolean cacheable) { + public ReactiveSqmQueryImpl setCacheable(boolean cacheable) { super.setCacheable( cacheable ); return this; } @Override - public ReactiveQuerySqmImpl setCacheRegion(String cacheRegion) { + public ReactiveSqmQueryImpl setCacheRegion(String cacheRegion) { super.setCacheRegion( cacheRegion ); return this; } @Override - public ReactiveQuerySqmImpl setHibernateLockMode(LockMode lockMode) { + public ReactiveSqmQueryImpl setHibernateLockMode(LockMode lockMode) { super.setHibernateLockMode( lockMode ); return this; } @Override - public ReactiveQuerySqmImpl setTimeout(int timeout) { + public ReactiveSqmQueryImpl setTimeout(int timeout) { super.setTimeout( timeout ); return this; } @Override - public ReactiveQuerySqmImpl setFetchSize(int fetchSize) { + public ReactiveSqmQueryImpl setFetchSize(int fetchSize) { super.setFetchSize( fetchSize ); return this; } @Override - public ReactiveQuerySqmImpl setReadOnly(boolean readOnly) { + public ReactiveSqmQueryImpl setReadOnly(boolean readOnly) { super.setReadOnly( readOnly ); return this; } @Override - public ReactiveQuerySqmImpl setProperties(Object bean) { + public ReactiveSqmQueryImpl setProperties(Object bean) { super.setProperties( bean ); return this; } @Override - public ReactiveQuerySqmImpl setProperties(Map bean) { + public ReactiveSqmQueryImpl setProperties(Map bean) { super.setProperties( bean ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(String name, Object value) { + public ReactiveSqmQueryImpl setParameter(String name, Object value) { super.setParameter( name, value ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(String name, P value, Class

javaType) { + public

ReactiveSqmQueryImpl setParameter(String name, P value, Class

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

ReactiveQuerySqmImpl setParameter(String name, P value, Type

type) { + public

ReactiveSqmQueryImpl setParameter(String name, P value, Type

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

ReactiveQuerySqmImpl setParameter(int position, P value, Class

javaType) { + public

ReactiveSqmQueryImpl setParameter(int position, P value, Class

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

ReactiveQuerySqmImpl setParameter(int position, P value, Type

type) { + public

ReactiveSqmQueryImpl setParameter(int position, P value, Type

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

ReactiveQuerySqmImpl setParameter(QueryParameter

parameter, P value) { + public

ReactiveSqmQueryImpl setParameter(QueryParameter

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

ReactiveQuerySqmImpl setParameter(QueryParameter

parameter, P value, Class

javaType) { + public

ReactiveSqmQueryImpl setParameter(QueryParameter

parameter, P value, Class

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

ReactiveQuerySqmImpl setParameter(QueryParameter

parameter, P value, Type

type) { + public

ReactiveSqmQueryImpl setParameter(QueryParameter

parameter, P value, Type

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

ReactiveQuerySqmImpl setParameter(Parameter

parameter, P value) { + public

ReactiveSqmQueryImpl setParameter(Parameter

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

ReactiveQuerySqmImpl setParameterList(String name, Collection values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(String name, Collection values, Class

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

ReactiveQuerySqmImpl setParameterList(String name, Collection values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(String name, Collection values, Type

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

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

javaType) { + public

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

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

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

type) { + public

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

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

ReactiveQuerySqmImpl setParameterList(int position, Collection values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(int position, Collection values, Class

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

ReactiveQuerySqmImpl setParameterList(int position, Collection values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(int position, Collection values, Type

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

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

javaType) { + public

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

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

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

type) { + public

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

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, Collection values) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, Collection values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, Collection values, Class

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, Collection values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, Collection values, Type

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, P[] values) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, P[] values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, P[] values, Class

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, P[] values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, P[] values, Type

type) { super.setParameterList( parameter, values, type ); return this; } @Override - public ReactiveQuerySqmImpl setFollowOnLocking(boolean enable) { + public ReactiveSqmQueryImpl setFollowOnLocking(boolean enable) { super.setFollowOnLocking( enable ); return this; } @@ -844,7 +861,7 @@ public void applyGraph(RootGraphImplementor graph, GraphSemantic semantic) { } @Override - public ReactiveQuerySqmImpl enableFetchProfile(String profileName) { + public ReactiveSqmQueryImpl enableFetchProfile(String profileName) { selectionQueryDelegate.enableFetchProfile( profileName ); return this; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java index d23ae3225..cab920f73 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java @@ -5,14 +5,30 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal; +import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.mutation.internal.Handler; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.Handler */ -public interface ReactiveHandler { +public interface ReactiveHandler extends Handler { + Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + @Override + default int execute(DomainQueryExecutionContext executionContext) { + throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + } + + @Override + default int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext) { + throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + } /** * Execute the multi-table update or delete indicated by the SQM AST @@ -22,5 +38,17 @@ public interface ReactiveHandler { * * @return The "number of rows affected" count */ - CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext); + default CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext){ + return reactiveExecute( createJdbcParameterBindings( executionContext ), executionContext ); + } + + /** + * Execute the multi-table update or delete indicated by the SQM AST + * passed in when this Handler was created. + * + * @param jdbcParameterBindings The parameter bindings for JDBC parameters + * @param executionContext Contextual information needed for execution + * @return The "number of rows affected" count + */ + CompletionStage reactiveExecute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java deleted file mode 100644 index b2e36a296..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java +++ /dev/null @@ -1,161 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal; - -import java.sql.PreparedStatement; -import java.util.concurrent.CompletionStage; -import java.util.function.BiFunction; - -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.reactive.util.impl.CompletionStages.Completable; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; - -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -/** - * @see org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper - */ -public class ReactiveSqmMutationStrategyHelper { - - private ReactiveSqmMutationStrategyHelper() { - } - - public static CompletionStage cleanUpCollectionTables( - EntityMappingType entityDescriptor, - BiFunction restrictionProducer, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - if ( !entityDescriptor.getEntityPersister().hasCollections() ) { - // none to clean-up - return voidFuture(); - } - - try { - final Completable stage = new Completable<>(); - entityDescriptor - .visitSubTypeAttributeMappings( attributeMapping -> { - if ( attributeMapping instanceof PluralAttributeMapping ) { - cleanUpCollectionTable( - (PluralAttributeMapping) attributeMapping, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( stage::complete ); - } - else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { - cleanUpCollectionTables( - (EmbeddedAttributeMapping) attributeMapping, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( stage::complete ); - } - else { - stage.complete( null, null ); - } - } - ); - return stage.getStage(); - } - catch (Throwable throwable) { - return failedFuture( throwable ); - } - } - - private static CompletionStage cleanUpCollectionTables( - EmbeddedAttributeMapping attributeMapping, - BiFunction restrictionProducer, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - try { - final Completable completable = new Completable<>(); - attributeMapping.visitSubParts( - modelPart -> { - if ( modelPart instanceof PluralAttributeMapping ) { - cleanUpCollectionTable( - (PluralAttributeMapping) modelPart, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( completable::complete ); - } - else if ( modelPart instanceof EmbeddedAttributeMapping ) { - cleanUpCollectionTables( - (EmbeddedAttributeMapping) modelPart, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( completable::complete ); - } - }, - null - ); - return completable.getStage(); - } - catch (Throwable throwable) { - return failedFuture( throwable ); - } - } - - private static CompletionStage cleanUpCollectionTable( - PluralAttributeMapping attributeMapping, - BiFunction restrictionProducer, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - final String separateCollectionTable = attributeMapping.getSeparateCollectionTable(); - if ( separateCollectionTable == null ) { - // one-to-many - update the matching rows in the associated table setting the fk column(s) to null - // not yet implemented - do nothing - return voidFuture(); - } - - final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - - // element-collection or many-to-many - delete the collection-table row - - final NamedTableReference tableReference = new NamedTableReference( - separateCollectionTable, - DeleteStatement.DEFAULT_ALIAS, - true - ); - - final DeleteStatement sqlAstDelete = new DeleteStatement( - tableReference, - restrictionProducer.apply( tableReference, attributeMapping ) - ); - - JdbcOperationQueryMutation jdbcDelete = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, sqlAstDelete ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcDelete, - jdbcParameterBindings, - executionContext.getSession().getJdbcCoordinator().getStatementPreparer()::prepareStatement, - ReactiveSqmMutationStrategyHelper::doNothing, - executionContext - ) - .thenCompose( CompletionStages::voidFuture ); - } - - private static void doNothing(Integer i, PreparedStatement ps) { - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java index 806b75718..c0eb4ee3a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java @@ -5,189 +5,49 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler; -import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.cte.CteContainer; -import org.hibernate.sql.ast.tree.cte.CteMaterialization; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.cte.CteTableGroup; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.basic.BasicResult; -import org.hibernate.sql.results.internal.SqlSelectionImpl; + +import java.util.concurrent.CompletionStage; /** * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler */ public interface ReactiveAbstractCteMutationHandler extends ReactiveAbstractMutationHandler { - CteTable getCteTable(); - - DomainParameterXref getDomainParameterXref(); - - CteMutationStrategy getStrategy(); - - void addDmlCtes( - CteContainer statement, - CteStatement idSelectCte, - MultiTableSqmMutationConverter sqmConverter, - Map, List> parameterResolutions, - SessionFactoryImplementor factory); - /** - * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler#execute(DomainQueryExecutionContext) + * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler#execute(JdbcParameterBindings, DomainQueryExecutionContext) */ @Override - default CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final SqmDeleteOrUpdateStatement sqmMutationStatement = getSqmDeleteOrUpdateStatement(); - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final EntityMappingType entityDescriptor = getEntityDescriptor(); - final String explicitDmlTargetAlias; - // We need an alias because we try to acquire a WRITE lock for these rows in the CTE - if ( sqmMutationStatement.getTarget().getExplicitAlias() == null ) { - explicitDmlTargetAlias = "dml_target"; - } - else { - explicitDmlTargetAlias = sqmMutationStatement.getTarget().getExplicitAlias(); - } - - final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( - entityDescriptor, - sqmMutationStatement, - sqmMutationStatement.getTarget(), - explicitDmlTargetAlias, - getDomainParameterXref(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - factory.getSqlTranslationEngine() - ); - final Map, List> parameterResolutions; - if ( getDomainParameterXref().getSqmParameterCount() == 0 ) { - parameterResolutions = Collections.emptyMap(); - } - else { - parameterResolutions = new IdentityHashMap<>(); - } - - final Predicate restriction = sqmConverter.visitWhereClause( sqmMutationStatement.getWhereClause() ); - sqmConverter.pruneTableGroupJoins(); - - final CteStatement idSelectCte = new CteStatement( - getCteTable(), - MatchingIdSelectionHelper.generateMatchingIdSelectStatement( - entityDescriptor, - sqmMutationStatement, - true, - restriction, - sqmConverter, - executionContext - ), - // The id-select cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - - // Create the main query spec that will return the count of - final QuerySpec querySpec = new QuerySpec( true, 1 ); - final List> domainResults = new ArrayList<>( 1 ); - final SelectStatement statement = new SelectStatement( querySpec, domainResults ); - final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( factory, statement ); - - final Expression count = createCountStar( factory, sqmConverter ); - domainResults.add( - new BasicResult<>( - 0, - null, - ( (SqlExpressible) count ).getJdbcMapping() - ) - ); - querySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, count ) ); - querySpec.getFromClause().addRoot( - new CteTableGroup( - new NamedTableReference( - idSelectCte.getCteTable().getTableExpression(), - AbstractCteMutationHandler.CTE_TABLE_IDENTIFIER - ) - ) - ); - - // Add all CTEs - statement.addCteStatement( idSelectCte ); - addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), sqmConverter ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter ); - } - }, - executionContext.getSession() - ); + default CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext executionContext){ final LockOptions lockOptions = executionContext.getQueryOptions().getLockOptions(); - final LockMode lockMode = lockOptions.getAliasSpecificLockMode( explicitDmlTargetAlias ); // Acquire a WRITE lock for the rows that are about to be modified - lockOptions.setAliasSpecificLockMode( explicitDmlTargetAlias, LockMode.WRITE ); - final JdbcOperationQuerySelect select = translator.translate( - jdbcParameterBindings, - executionContext.getQueryOptions() - ); - lockOptions.setAliasSpecificLockMode( explicitDmlTargetAlias, lockMode ); - + lockOptions.setLockMode( LockMode.WRITE ); return ( (ReactiveSharedSessionContractImplementor) executionContext.getSession() ) - .reactiveAutoFlushIfRequired( select.getAffectedTableNames() ) - .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE.list( - select, - jdbcParameterBindings, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), - row -> row[0], - ReactiveListResultsConsumer.UniqueSemantic.NONE - ) - .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) + .reactiveAutoFlushIfRequired( getSelect().getAffectedTableNames() ) + .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE + .list( + getSelect(), + jdbcParameterBindings, + SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), + row -> row[0], + null, + ReactiveListResultsConsumer.UniqueSemantic.NONE, + 1 + ) + .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) ); } - Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter); + JdbcOperationQuerySelect getSelect(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java index a1f555253..67c3641b8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java @@ -5,60 +5,37 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.cte.CteDeleteHandler; import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.sql.ast.tree.cte.CteContainer; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.cte.CteDeleteHandler */ public class ReactiveCteDeleteHandler extends CteDeleteHandler implements ReactiveAbstractCteMutationHandler { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - protected ReactiveCteDeleteHandler( CteTable cteTable, SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory ); - } - - @Override - public void addDmlCtes( - CteContainer statement, - CteStatement idSelectCte, - MultiTableSqmMutationConverter sqmConverter, - Map, List> parameterResolutions, - SessionFactoryImplementor factory) { - super.addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory, context, firstJdbcParameterBindingsConsumer ); } @Override - public Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter) { - return super.createCountStar( factory, sqmConverter ); + public JdbcOperationQuerySelect getSelect() { + return super.getSelect(); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java index 603563e0f..9361df67f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java @@ -5,93 +5,35 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.lang.invoke.MethodHandles; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; -import org.hibernate.id.OptimizableGenerator; -import org.hibernate.id.enhanced.Optimizer; -import org.hibernate.internal.util.collections.Stack; -import org.hibernate.metamodel.mapping.BasicValuedMapping; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SqlExpressible; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.results.internal.TableGroupImpl; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.BinaryArithmeticOperator; -import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.SqmInsertStrategyHelper; import org.hibernate.query.sqm.mutation.internal.cte.CteInsertHandler; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; -import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; -import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; -import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.SqlAstJoinType; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; -import org.hibernate.sql.ast.tree.Statement; -import org.hibernate.sql.ast.tree.cte.CteColumn; -import org.hibernate.sql.ast.tree.cte.CteMaterialization; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.cte.CteTableGroup; -import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.QueryLiteral; -import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableGroupJoin; -import org.hibernate.sql.ast.tree.from.ValuesTableGroup; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.insert.Values; -import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; -import org.hibernate.sql.ast.tree.select.QueryPart; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.basic.BasicResult; -import org.hibernate.sql.results.internal.SqlSelectionImpl; + +import java.lang.invoke.MethodHandles; +import java.util.concurrent.CompletionStage; public class ReactiveCteInsertHandler extends CteInsertHandler implements ReactiveHandler { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final SessionFactoryImplementor sessionFactory; - public ReactiveCteInsertHandler( CteTable cteTable, SqmInsertStatement sqmStatement, DomainParameterXref domainParameterXref, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmStatement, domainParameterXref, sessionFactory ); - this.sessionFactory = sessionFactory; + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmStatement, domainParameterXref, context, firstJdbcParameterBindingsConsumer ); } @Override @@ -99,470 +41,23 @@ public int execute(DomainQueryExecutionContext executionContext) { throw LOG.nonReactiveMethodCall( "reactiveExecute" ); } - // Pretty much a copy and paste of the method in the super class - // We should refactor this @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final SqmInsertStatement sqmInsertStatement = getSqmStatement(); - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final EntityPersister entityDescriptor = getEntityDescriptor().getEntityPersister(); - final String explicitDmlTargetAlias; - if ( sqmInsertStatement.getTarget().getExplicitAlias() == null ) { - explicitDmlTargetAlias = "dml_target"; - } - else { - explicitDmlTargetAlias = sqmInsertStatement.getTarget().getExplicitAlias(); - } - - final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( - entityDescriptor, - sqmInsertStatement, - sqmInsertStatement.getTarget(), - explicitDmlTargetAlias, - getDomainParameterXref(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - factory.getSqlTranslationEngine() - ); - final TableGroup insertingTableGroup = sqmConverter.getMutatingTableGroup(); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // visit the insertion target using our special converter, collecting - // information about the target paths - - final int size = getSqmStatement().getInsertionTargetPaths().size(); - final List, Assignment>> targetPathColumns = new ArrayList<>( size ); - final List targetPathCteColumns = new ArrayList<>( size ); - final NamedTableReference entityTableReference = new NamedTableReference( - getCteTable().getTableExpression(), - TemporaryTable.DEFAULT_ALIAS, - true - ); - final InsertSelectStatement insertStatement = new InsertSelectStatement( entityTableReference ); - - final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = sqmConverter.visitInsertionTargetPaths( - (assignable, columnReferences) -> { - final SqmPathInterpretation pathInterpretation = (SqmPathInterpretation) assignable; - final int offset = CteTable.determineModelPartStartIndex( - entityDescriptor, - pathInterpretation.getExpressionType() - ); - if ( offset == -1 ) { - throw new IllegalStateException( "Couldn't find matching cte column for: " + ( (Expression) assignable ).getExpressionType() ); - } - final int end = offset + pathInterpretation.getExpressionType().getJdbcTypeCount(); - // Find a matching cte table column and set that at the current index - final List columns = getCteTable().getCteColumns().subList( offset, end ); - insertStatement.addTargetColumnReferences( columnReferences ); - targetPathCteColumns.addAll( columns ); - targetPathColumns.add( - new AbstractMap.SimpleEntry<>( - columns, - new Assignment( - assignable, - (Expression) assignable - ) - ) - ); - }, - sqmInsertStatement, - entityDescriptor, - insertingTableGroup - ); - - final boolean assignsId = targetPathCteColumns.contains( getCteTable().getCteColumns().get( 0 ) ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Create the statement that represent the source for the entity cte - - final Stack processingStateStack = sqmConverter.getProcessingStateStack(); - final SqlAstProcessingState oldState = processingStateStack.pop(); - final Statement queryStatement; - if ( sqmInsertStatement instanceof SqmInsertSelectStatement ) { - final QueryPart queryPart = sqmConverter.visitQueryPart( ( (SqmInsertSelectStatement) sqmInsertStatement ).getSelectQueryPart() ); - queryPart.visitQuerySpecs( - querySpec -> { - // This returns true if the insertion target uses a sequence with an optimizer - // in which case we will fill the row_number column instead of the id column - if ( additionalInsertValues.applySelections( querySpec, sessionFactory ) ) { - final CteColumn rowNumberColumn = getCteTable().getCteColumns() - .get( getCteTable().getCteColumns().size() - 1 ); - final ColumnReference columnReference = new ColumnReference( - (String) null, - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - insertStatement.getTargetColumns().set( - insertStatement.getTargetColumns().size() - 1, - columnReference - ); - targetPathCteColumns.set( - targetPathCteColumns.size() - 1, - rowNumberColumn - ); - } - if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - SqmInsertStrategyHelper.createRowNumberingExpression( - querySpec, - sessionFactory - ) - ) - ); - } - } - ); - queryStatement = new SelectStatement( queryPart ); - } - else { - final List sqmValuesList = ( (SqmInsertValuesStatement) sqmInsertStatement ).getValuesList(); - final List valuesList = new ArrayList<>( sqmValuesList.size() ); - for ( SqmValues sqmValues : sqmValuesList ) { - final Values values = sqmConverter.visitValues( sqmValues ); - additionalInsertValues.applyValues( values ); - valuesList.add( values ); - } - final QuerySpec querySpec = new QuerySpec( true ); - final NavigablePath navigablePath = new NavigablePath( entityDescriptor.getRootPathName() ); - final List columnNames = new ArrayList<>( targetPathColumns.size() ); - final String valuesAlias = insertingTableGroup.getPrimaryTableReference().getIdentificationVariable(); - for ( Map.Entry, Assignment> entry : targetPathColumns ) { - for ( ColumnReference columnReference : entry.getValue().getAssignable().getColumnReferences() ) { - columnNames.add( columnReference.getColumnExpression() ); - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - columnReference.getQualifier().equals( valuesAlias ) - ? columnReference - : new ColumnReference( - valuesAlias, - columnReference.getColumnExpression(), - false, - null, - columnReference.getJdbcMapping() - ) - ) - ); - } - } - final ValuesTableGroup valuesTableGroup = new ValuesTableGroup( - navigablePath, - entityDescriptor.getEntityPersister(), - valuesList, - insertingTableGroup.getPrimaryTableReference().getIdentificationVariable(), - columnNames, - true, - factory - ); - querySpec.getFromClause().addRoot( valuesTableGroup ); - queryStatement = new SelectStatement( querySpec ); - } - processingStateStack.push( oldState ); - sqmConverter.pruneTableGroupJoins(); - - if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - // Add the row number to the assignments - final CteColumn rowNumberColumn = getCteTable().getCteColumns() - .get( getCteTable().getCteColumns().size() - 1 ); - final ColumnReference columnReference = new ColumnReference( - (String) null, - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - insertStatement.getTargetColumns().add( columnReference ); - targetPathCteColumns.add( rowNumberColumn ); - } - - final CteTable entityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); - - // Create the main query spec that will return the count of rows - final QuerySpec querySpec = new QuerySpec( true, 1 ); - final List> domainResults = new ArrayList<>( 1 ); - final SelectStatement statement = new SelectStatement( querySpec, domainResults ); - - final CteStatement entityCte; - if ( additionalInsertValues.requiresRowNumberIntermediate() ) { - final CteTable fullEntityCteTable = getCteTable(); - final String baseTableName = "base_" + entityCteTable.getTableExpression(); - final CteStatement baseEntityCte = new CteStatement( - entityCteTable.withName( baseTableName ), - queryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( baseEntityCte ); - - final CteColumn rowNumberColumn = fullEntityCteTable.getCteColumns().get( - fullEntityCteTable.getCteColumns().size() - 1 - ); - final ColumnReference rowNumberColumnReference = new ColumnReference( - "e", - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - final CteColumn idColumn = fullEntityCteTable.getCteColumns().get( 0 ); - final BasicValuedMapping idType = (BasicValuedMapping) idColumn.getJdbcMapping(); - final Optimizer optimizer = ( (OptimizableGenerator) entityDescriptor.getGenerator() ).getOptimizer(); - final BasicValuedMapping integerType = (BasicValuedMapping) rowNumberColumn.getJdbcMapping(); - final Expression rowNumberMinusOneModuloIncrement = new BinaryArithmeticExpression( - new BinaryArithmeticExpression( - rowNumberColumnReference, - BinaryArithmeticOperator.SUBTRACT, - new QueryLiteral<>( - 1, - (BasicValuedMapping) rowNumberColumn.getJdbcMapping() - ), - integerType - ), - BinaryArithmeticOperator.MODULO, - new QueryLiteral<>( - optimizer.getIncrementSize(), - integerType - ), - integerType - ); - - // Create the CTE that fetches a new sequence value for the row numbers that need it - { - final QuerySpec rowsWithSequenceQuery = new QuerySpec( true ); - rowsWithSequenceQuery.getFromClause().addRoot( - new CteTableGroup( new NamedTableReference( baseTableName, "e" ) ) - ); - rowsWithSequenceQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - rowNumberColumnReference - ) - ); - final String fragment = ( (BulkInsertionCapableIdentifierGenerator) entityDescriptor.getGenerator() ) - .determineBulkInsertionIdentifierGenerationSelectFragment( - sessionFactory.getSqlStringGenerationContext() - ); - rowsWithSequenceQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 1, - new SelfRenderingSqlFragmentExpression( fragment ) - ) - ); - rowsWithSequenceQuery.applyPredicate( - new ComparisonPredicate( - rowNumberMinusOneModuloIncrement, - ComparisonOperator.EQUAL, - new QueryLiteral<>( - 0, - integerType - ) - ) - ); - final CteTable rowsWithSequenceCteTable = new CteTable( - ROW_NUMBERS_WITH_SEQUENCE_VALUE, - List.of( rowNumberColumn, idColumn ) - ); - final SelectStatement rowsWithSequenceStatement = new SelectStatement( rowsWithSequenceQuery ); - final CteStatement rowsWithSequenceCte = new CteStatement( - rowsWithSequenceCteTable, - rowsWithSequenceStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( rowsWithSequenceCte ); - } - - // Create the CTE that represents the entity cte - { - final QuerySpec entityQuery = new QuerySpec( true ); - final NavigablePath navigablePath = new NavigablePath( baseTableName ); - final TableGroup baseTableGroup = new TableGroupImpl( - navigablePath, - null, - new NamedTableReference( baseTableName, "e" ), - null - ); - final TableGroup rowsWithSequenceTableGroup = new CteTableGroup( - new NamedTableReference( - ROW_NUMBERS_WITH_SEQUENCE_VALUE, - "t" - ) - ); - baseTableGroup.addTableGroupJoin( - new TableGroupJoin( - rowsWithSequenceTableGroup.getNavigablePath(), - SqlAstJoinType.LEFT, - rowsWithSequenceTableGroup, - new ComparisonPredicate( - new BinaryArithmeticExpression( - rowNumberColumnReference, - BinaryArithmeticOperator.SUBTRACT, - rowNumberMinusOneModuloIncrement, - integerType - ), - ComparisonOperator.EQUAL, - new ColumnReference( - "t", - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ) - ) - ) - ); - entityQuery.getFromClause().addRoot( baseTableGroup ); - entityQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - new BinaryArithmeticExpression( - new ColumnReference( - "t", - idColumn.getColumnExpression(), - false, - null, - idColumn.getJdbcMapping() - ), - BinaryArithmeticOperator.ADD, - new BinaryArithmeticExpression( - rowNumberColumnReference, - BinaryArithmeticOperator.SUBTRACT, - new ColumnReference( - "t", - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ), - integerType - ), - idType - ) - ) - ); - final CteTable finalEntityCteTable; - if ( targetPathCteColumns.contains( getCteTable().getCteColumns().get( 0 ) ) ) { - finalEntityCteTable = entityCteTable; - } - else { - targetPathCteColumns.add( 0, getCteTable().getCteColumns().get( 0 ) ); - finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); - } - final List cteColumns = finalEntityCteTable.getCteColumns(); - for ( int i = 1; i < cteColumns.size(); i++ ) { - final CteColumn cteColumn = cteColumns.get( i ); - entityQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - i, - new ColumnReference( - "e", - cteColumn.getColumnExpression(), - false, - null, - cteColumn.getJdbcMapping() - ) - ) - ); - } - - final SelectStatement entityStatement = new SelectStatement( entityQuery ); - entityCte = new CteStatement( - finalEntityCteTable, - entityStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( entityCte ); - } - } - else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - final String baseTableName = "base_" + entityCteTable.getTableExpression(); - final CteStatement baseEntityCte = new CteStatement( - entityCteTable.withName( baseTableName ), - queryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( baseEntityCte ); - targetPathCteColumns.add( 0, getCteTable().getCteColumns().get( 0 ) ); - final CteTable finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); - final QuerySpec finalQuerySpec = new QuerySpec( true ); - final SelectStatement finalQueryStatement = new SelectStatement( finalQuerySpec ); - entityCte = new CteStatement( - finalEntityCteTable, - finalQueryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - } - else { - entityCte = new CteStatement( - entityCteTable, - queryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( entityCte ); - } - - // Add all CTEs - final String baseInsertCte = addDmlCtes( - statement, - entityCte, - targetPathColumns, - assignsId, - sqmConverter, - factory - ); - - final Expression count = createCountStar( factory, sqmConverter ); - domainResults - .add( new BasicResult<>( 0, null, ( (SqlExpressible) count ).getJdbcMapping() ) ); - querySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, count ) ); - querySpec.getFromClause().addRoot( - new CteTableGroup( - new NamedTableReference( - // We want to return the insertion count of the base table - baseInsertCte, - CTE_TABLE_IDENTIFIER - ) - ) - ); - - // Execute the statement - final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( factory, statement ); - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), sqmConverter ), - new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions() - .get( parameter ); - } - }, - executionContext.getSession() - ); - final JdbcOperationQuerySelect select = translator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - return ( (ReactiveSharedSessionContractImplementor) executionContext.getSession() ) - .reactiveAutoFlushIfRequired( select.getAffectedTableNames() ) - .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE.list( - select, + public CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext context) { + return ( (ReactiveSharedSessionContractImplementor) context.getSession() ) + .reactiveAutoFlushIfRequired( getSelect().getAffectedTableNames() ) + .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE + .list( + getSelect(), jdbcParameterBindings, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), + SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ), row -> row[0], - ReactiveListResultsConsumer.UniqueSemantic.NONE + null, + ReactiveListResultsConsumer.UniqueSemantic.NONE, + 1 ) - .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) ); + .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java index d7259ebb1..e0a0e10fd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java @@ -5,16 +5,18 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.util.concurrent.CompletionStage; - +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.InsertHandler; import org.hibernate.query.sqm.mutation.internal.cte.CteInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveCteInsertStrategy extends CteInsertStrategy implements ReactiveSqmMultiTableInsertStrategy { @@ -31,11 +33,16 @@ public ReactiveCteInsertStrategy( } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new ReactiveCteInsertHandler( getEntityCteTable(), sqmInsertStatement, domainParameterXref, getSessionFactory() ) - .reactiveExecute( context ); + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final InsertHandler multiTableHandler = new ReactiveCteInsertHandler( + getEntityCteTable(), + sqmInsertStatement, + domainParameterXref, + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java index 6da230aad..445cfe2f6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java @@ -5,17 +5,21 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.util.concurrent.CompletionStage; - +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveCteMutationStrategy extends CteMutationStrategy implements ReactiveSqmMultiTableMutationStrategy { @@ -28,22 +32,53 @@ public ReactiveCteMutationStrategy(EntityPersister rootDescriptor, RuntimeModelC } @Override - public CompletionStage reactiveExecuteUpdate( - SqmUpdateStatement sqmUpdateStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - checkMatch( sqmUpdateStatement ); - return new ReactiveCteUpdateHandler( getIdCteTable(), sqmUpdateStatement, domainParameterXref, this, getSessionFactory() ) - .reactiveExecute( context ); + public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = sqmStatement instanceof SqmDeleteStatement sqmDelete + ? buildHandler( sqmDelete, domainParameterXref, context, firstJdbcParameterBindings) + : buildHandler( (SqmUpdateStatement) sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } @Override - public CompletionStage reactiveExecuteDelete( - SqmDeleteStatement sqmDeleteStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - checkMatch( sqmDeleteStatement ); - return new ReactiveCteDeleteHandler( getIdCteTable(), sqmDeleteStatement, domainParameterXref, this, getSessionFactory() ) - .reactiveExecute( context ); + public ReactiveHandler buildHandler(SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { + checkMatch( sqmDelete ); + if ( getRootDescriptor().getSoftDeleteMapping() != null ) { + return new ReactiveCteSoftDeleteHandler( + getIdCteTable(), + sqmDelete, + domainParameterXref, + this, + getSessionFactory(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveCteDeleteHandler( + getIdCteTable(), + sqmDelete, + domainParameterXref, + this, + getSessionFactory(), + context, + firstJdbcParameterBindingsConsumer + ); + } } + + @Override + public MultiTableHandler buildHandler(SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { + checkMatch( sqmUpdate ); + return new ReactiveCteUpdateHandler( + getIdCteTable(), + sqmUpdate, + domainParameterXref, + this, + getSessionFactory(), + context, + firstJdbcParameterBindingsConsumer + ); + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java new file mode 100644 index 000000000..8e0740d14 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java @@ -0,0 +1,43 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.mutation.internal.cte; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; +import org.hibernate.query.sqm.mutation.internal.cte.CteSoftDeleteHandler; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +public class ReactiveCteSoftDeleteHandler extends CteSoftDeleteHandler implements ReactiveAbstractCteMutationHandler { + protected ReactiveCteSoftDeleteHandler( + CteTable cteTable, + SqmDeleteStatement sqmDeleteStatement, + DomainParameterXref domainParameterXref, + CteMutationStrategy strategy, + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( + cteTable, + sqmDeleteStatement, + domainParameterXref, + strategy, + sessionFactory, + context, + firstJdbcParameterBindingsConsumer + ); + } + + @Override + public JdbcOperationQuerySelect getSelect() { + return super.getSelect(); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java index 0730ff33e..c855f35ad 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java @@ -5,59 +5,35 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; - import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; import org.hibernate.query.sqm.mutation.internal.cte.CteUpdateHandler; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.sql.ast.tree.cte.CteContainer; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see CteUpdateHandler */ public class ReactiveCteUpdateHandler extends CteUpdateHandler implements ReactiveAbstractCteMutationHandler { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public ReactiveCteUpdateHandler( CteTable cteTable, SqmUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory ); - } - - @Override - public void addDmlCtes( - CteContainer statement, - CteStatement idSelectCte, - MultiTableSqmMutationConverter sqmConverter, - Map, List> parameterResolutions, - SessionFactoryImplementor factory) { - super.addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory, context, firstJdbcParameterBindingsConsumer ); } @Override - public Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter) { - return super.createCountStar( factory, sqmConverter ); + public JdbcOperationQuerySelect getSelect() { + return super.getSelect(); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java deleted file mode 100644 index 33acc38f6..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java +++ /dev/null @@ -1,68 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.cte; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.InsertExecutionDelegate; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; -import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTableBasedInsertHandler; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.insert.ConflictClause; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.exec.spi.ExecutionContext; - -/** - * @see InsertExecutionDelegate - */ -public class ReactiveInsertExecutionDelegate extends InsertExecutionDelegate implements ReactiveTableBasedInsertHandler.ReactiveExecutionDelegate { - - public ReactiveInsertExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable entityTable, - AfterUseAction afterUseAction, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup insertingTableGroup, - Map tableReferenceByAlias, - List assignments, - InsertSelectStatement insertStatement, - ConflictClause conflictClause, - JdbcParameter sessionUidParameter, - DomainQueryExecutionContext executionContext) { - super( - sqmConverter, - entityTable, - afterUseAction, - sessionUidAccess, - domainParameterXref, - insertingTableGroup, - tableReferenceByAlias, - assignments, - insertStatement, - conflictClause, - sessionUidParameter, - executionContext - ); - } - - @Override - public CompletionStage reactiveExecute(ExecutionContext executionContext) { - // FIXME: Why is this null? - return null; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java index e8f7bbea6..164403171 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java @@ -7,6 +7,8 @@ import java.lang.invoke.MethodHandles; import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.UUID; import java.util.concurrent.CompletionStage; import java.util.function.Function; @@ -16,42 +18,38 @@ import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableColumn; +import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.query.sqm.ComparisonOperator; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; +import org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithTemporaryTableHelper; import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTemporaryTableHelper.TemporaryTableCreationWork; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.spi.NavigablePath; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.QueryLiteral; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.StandardTableGroup; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.internal.SqlSelectionImpl; import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTemporaryTableHelper.cleanTemporaryTableRows; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -64,79 +62,6 @@ public final class ReactiveExecuteWithTemporaryTableHelper { private ReactiveExecuteWithTemporaryTableHelper() { } - public static CompletionStage saveMatchingIdsIntoIdTable( - MultiTableSqmMutationConverter sqmConverter, - Predicate suppliedPredicate, - TemporaryTable idTable, - Function sessionUidAccess, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - - final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup(); - - assert mutatingTableGroup.getModelPart() instanceof EntityMappingType; - final EntityMappingType mutatingEntityDescriptor = (EntityMappingType) mutatingTableGroup.getModelPart(); - - final NamedTableReference idTableReference = new NamedTableReference( - idTable.getTableExpression(), - InsertSelectStatement.DEFAULT_ALIAS - ); - final InsertSelectStatement idTableInsert = new InsertSelectStatement( idTableReference ); - - for ( int i = 0; i < idTable.getColumns().size(); i++ ) { - final TemporaryTableColumn column = idTable.getColumns().get( i ); - idTableInsert.addTargetColumnReferences( - new ColumnReference( - idTableReference, - column.getColumnName(), - // id columns cannot be formulas and cannot have custom read and write expressions - false, - null, - column.getJdbcMapping() - ) - ); - } - - final QuerySpec matchingIdSelection = new QuerySpec( true, 1 ); - idTableInsert.setSourceSelectStatement( matchingIdSelection ); - - matchingIdSelection.getFromClause().addRoot( mutatingTableGroup ); - - mutatingEntityDescriptor.getIdentifierMapping().forEachSelectable( - (selectionIndex, selection) -> { - final TableReference tableReference = mutatingTableGroup.resolveTableReference( - mutatingTableGroup.getNavigablePath(), - selection.getContainingTableExpression() - ); - matchingIdSelection.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - selectionIndex + 1, - sqmConverter.getSqlExpressionResolver().resolveSqlExpression( - tableReference, - selection - ) - ) - ); - } - ); - - if ( idTable.getSessionUidColumn() != null ) { - final int jdbcPosition = matchingIdSelection.getSelectClause().getSqlSelections().size(); - matchingIdSelection.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - jdbcPosition, - new QueryLiteral<>( - UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ), - (BasicValuedMapping) idTable.getSessionUidColumn().getJdbcMapping() - ) - ) - ); - } - - matchingIdSelection.applyPredicate( suppliedPredicate ); - return saveIntoTemporaryTable( idTableInsert, jdbcParameterBindings, executionContext ); - } - public static CompletionStage saveIntoTemporaryTable( InsertSelectStatement temporaryTableInsert, JdbcParameterBindings jdbcParameterBindings, @@ -150,12 +75,14 @@ public static CompletionStage saveIntoTemporaryTable( // Acquire a WRITE lock for the rows that are about to be modified lockOptions.setLockMode( LockMode.WRITE ); // Visit the table joins and reset the lock mode if we encounter OUTER joins that are not supported - if ( temporaryTableInsert.getSourceSelectStatement() != null + final QueryPart sourceSelectStatement = temporaryTableInsert.getSourceSelectStatement(); + if ( sourceSelectStatement != null && !jdbcEnvironment.getDialect().supportsOuterJoinForUpdate() ) { - temporaryTableInsert.getSourceSelectStatement().visitQuerySpecs( + sourceSelectStatement.visitQuerySpecs( querySpec -> querySpec.getFromClause().visitTableJoins( tableJoin -> { - if ( tableJoin.getJoinType() != SqlAstJoinType.INNER ) { + if ( tableJoin.isInitialized() + && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { lockOptions.setLockMode( lockMode ); } } @@ -166,131 +93,66 @@ public static CompletionStage saveIntoTemporaryTable( .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); lockOptions.setLockMode( lockMode ); + return saveIntoTemporaryTable(jdbcInsert, jdbcParameterBindings, executionContext); + } + + public static CompletionStage saveIntoTemporaryTable( + JdbcOperationQueryMutation jdbcInsert, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { return StandardReactiveJdbcMutationExecutor.INSTANCE .executeReactive( jdbcInsert, jdbcParameterBindings, - executionContext.getSession().getJdbcCoordinator().getStatementPreparer()::prepareStatement, - ReactiveExecuteWithTemporaryTableHelper::doNothing, + sql -> executionContext.getSession().getJdbcCoordinator() + .getStatementPreparer().prepareStatement( sql ), + (integer, preparedStatement) -> {}, executionContext - ); } public static QuerySpec createIdTableSelectQuerySpec( TemporaryTable idTable, - Function sessionUidAccess, + JdbcParameter sessionUidParameter, EntityMappingType entityDescriptor, ExecutionContext executionContext) { - return createIdTableSelectQuerySpec( idTable, null, sessionUidAccess, entityDescriptor, executionContext ); + return createIdTableSelectQuerySpec( idTable, null, sessionUidParameter, entityDescriptor, executionContext ); } public static QuerySpec createIdTableSelectQuerySpec( TemporaryTable idTable, ModelPart fkModelPart, - Function sessionUidAccess, + JdbcParameter sessionUidParameter, EntityMappingType entityDescriptor, ExecutionContext executionContext) { - final QuerySpec querySpec = new QuerySpec( false ); - - final NamedTableReference idTableReference = new NamedTableReference( - idTable.getTableExpression(), - TemporaryTable.DEFAULT_ALIAS, - true - ); - final TableGroup idTableGroup = new StandardTableGroup( - true, - new NavigablePath( idTableReference.getTableExpression() ), - entityDescriptor, - null, - idTableReference, - null, - executionContext.getSession().getFactory() - ); - - querySpec.getFromClause().addRoot( idTableGroup ); - - applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart ); - applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidAccess, executionContext ); - - return querySpec; + return ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( idTable, fkModelPart, sessionUidParameter, entityDescriptor, executionContext ); } - // TODO: I think we can reuse the method in ExecuteWithTemporaryTableHelper - private static void applyIdTableSelections( - QuerySpec querySpec, - TableReference tableReference, - TemporaryTable idTable, - ModelPart fkModelPart) { - if ( fkModelPart == null ) { - final int size = idTable.getEntityDescriptor().getIdentifierMapping().getJdbcTypeCount(); - for ( int i = 0; i < size; i++ ) { - final TemporaryTableColumn temporaryTableColumn = idTable.getColumns().get( i ); - if ( temporaryTableColumn != idTable.getSessionUidColumn() ) { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - i, - new ColumnReference( - tableReference, - temporaryTableColumn.getColumnName(), - false, - null, - temporaryTableColumn.getJdbcMapping() - ) - ) - ); - } - } - } - else { - fkModelPart.forEachSelectable( - (i, selectableMapping) -> querySpec.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( - i, - new ColumnReference( - tableReference, - selectableMapping.getSelectionExpression(), - false, - null, - selectableMapping.getJdbcMapping() - ) - ) - ) - ); - } + @Deprecated(forRemoval = true, since = "3.1") + public static CompletionStage performBeforeTemporaryTableUseActions( + TemporaryTable temporaryTable, + ExecutionContext executionContext) { + return performBeforeTemporaryTableUseActions( + temporaryTable, + executionContext.getSession().getDialect().getTemporaryTableBeforeUseAction(), + executionContext + ).thenCompose( CompletionStages::voidFuture ); } - private static void applyIdTableRestrictions( - QuerySpec querySpec, - TableReference idTableReference, - TemporaryTable idTable, - Function sessionUidAccess, + public static CompletionStage performBeforeTemporaryTableUseActions( + TemporaryTable temporaryTable, + TemporaryTableStrategy temporaryTableStrategy, ExecutionContext executionContext) { - if ( idTable.getSessionUidColumn() != null ) { - querySpec.applyPredicate( - new ComparisonPredicate( - new ColumnReference( - idTableReference, - idTable.getSessionUidColumn().getColumnName(), - false, null, - idTable.getSessionUidColumn().getJdbcMapping() - ), - ComparisonOperator.EQUAL, - new QueryLiteral<>( - UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ), - (BasicValuedMapping) idTable.getSessionUidColumn().getJdbcMapping() - ) - ) - ); - } + return performBeforeTemporaryTableUseActions( temporaryTable, temporaryTableStrategy.getTemporaryTableBeforeUseAction(), executionContext ); } - public static CompletionStage performBeforeTemporaryTableUseActions( + public static CompletionStage performBeforeTemporaryTableUseActions( TemporaryTable temporaryTable, + BeforeUseAction beforeUseAction, ExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); - if ( dialect.getTemporaryTableBeforeUseAction() == BeforeUseAction.CREATE ) { + if ( beforeUseAction == BeforeUseAction.CREATE ) { final TemporaryTableCreationWork temporaryTableCreationWork = new TemporaryTableCreationWork( temporaryTable, factory ); final TempTableDdlTransactionHandling ddlTransactionHandling = dialect.getTemporaryTableDdlTransactionHandling(); if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) { @@ -298,7 +160,7 @@ public static CompletionStage performBeforeTemporaryTableUseActions( } throw LOG.notYetImplemented(); } - return voidFuture(); + return falseFuture(); } public static CompletionStage performAfterTemporaryTableUseActions( @@ -308,14 +170,51 @@ public static CompletionStage performAfterTemporaryTableUseActions( ExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); - switch ( afterUseAction ) { - case CLEAN: - return cleanTemporaryTableRows( temporaryTable, dialect.getTemporaryTableExporter(), sessionUidAccess, executionContext.getSession() ); - case DROP: - return dropAction( temporaryTable, executionContext, factory, dialect ); - default: - return voidFuture(); + return switch ( afterUseAction ) { + case CLEAN -> cleanTemporaryTableRows( temporaryTable, dialect.getTemporaryTableExporter(), sessionUidAccess, executionContext.getSession() ); + case DROP -> dropAction( temporaryTable, executionContext, factory, dialect ); + default -> voidFuture(); + }; + } + + public static CompletionStage loadInsertedRowNumbers( + String sqlSelect, + TemporaryTable temporaryTable, + Function sessionUidAccess, + int rows, + ExecutionContext executionContext) { + final TemporaryTableSessionUidColumn sessionUidColumn = temporaryTable.getSessionUidColumn(); + final SharedSessionContractImplementor session = executionContext.getSession(); + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + PreparedStatement preparedStatement = null; + preparedStatement = jdbcCoordinator.getStatementPreparer().prepareStatement( sqlSelect ); + Object[] parameters = new Object[1]; + if ( sessionUidColumn != null ) { + parameters[0] = UUID.fromString( sessionUidAccess.apply( session ) ); + } + final Integer[] rowNumbers = new Integer[rows]; + return reactiveConnection(session).selectJdbc( sqlSelect, parameters ) + .thenApply( resultSet -> getRowNumbers( rows, resultSet, rowNumbers ) ); + } + + private static Integer[] getRowNumbers(int rows, ResultSet resultSet, Integer[] rowNumbers) { + int rowIndex = 0; + try { + while ( resultSet.next() ) { + rowNumbers[rowIndex++] = resultSet.getInt( 1 ); + } + return rowNumbers; + } + catch ( IndexOutOfBoundsException e ) { + throw new IllegalArgumentException( "Expected " + rows + " to be inserted but found more", e ); } + catch ( SQLException ex ) { + throw new IllegalStateException( ex ); + } + } + + private static ReactiveConnection reactiveConnection(SharedSessionContractImplementor session) { + return ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); } private static CompletionStage dropAction( @@ -327,10 +226,11 @@ private static CompletionStage dropAction( if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) { return new ReactiveTemporaryTableHelper .TemporaryTableDropWork( temporaryTable, factory ) - .reactiveExecute( ( (ReactiveConnectionSupplier) executionContext.getSession() ).getReactiveConnection() ); + .reactiveExecute( ( (ReactiveConnectionSupplier) executionContext.getSession() ).getReactiveConnection() ) + .thenCompose( CompletionStages::voidFuture ); } - throw LOG.notYetImplemented(); + return failedFuture( LOG.notYetImplemented() ); } private static void doNothing(Integer integer, PreparedStatement preparedStatement) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java index 2f7f7bb37..9ca1dfce8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java @@ -6,16 +6,19 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.InsertHandler; import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy @@ -48,23 +51,23 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage.thenCompose( v -> new ReactiveTableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final InsertHandler multiTableHandler = new ReactiveTableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, // generally a global temp table should already track a Connection-specific uid, // but just in case a particular env needs it... - ReactiveGlobalTemporaryTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } - @Override public boolean isPrepared() { return prepared; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java index 66b001520..0995dc70e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java @@ -6,22 +6,28 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy; import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy */ -public class ReactiveGlobalTemporaryTableMutationStrategy extends GlobalTemporaryTableStrategy +public class ReactiveGlobalTemporaryTableMutationStrategy extends GlobalTemporaryTableMutationStrategy implements ReactiveGlobalTemporaryTableStrategy, ReactiveSqmMultiTableMutationStrategy { private final CompletableFuture tableCreatedStage = new CompletableFuture<>(); @@ -50,35 +56,70 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteUpdate( - SqmUpdateStatement sqmUpdateStatement, + public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = sqmStatement instanceof SqmDeleteStatement sqmDelete + ? buildHandler( sqmDelete, domainParameterXref, context, firstJdbcParameterBindings ) + : buildHandler( (SqmUpdateStatement) sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); + } + + @Override + public MultiTableHandler buildHandler( + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedUpdateHandler( - sqmUpdateStatement, - domainParameterXref, - getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + return new ReactiveTableBasedUpdateHandler( + sqmUpdate, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); } @Override - public CompletionStage reactiveExecuteDelete( - SqmDeleteStatement sqmDeleteStatement, + public MultiTableHandler buildHandler( + SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedDeleteHandler( - sqmDeleteStatement, - domainParameterXref, - getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactiveGlobalTemporaryTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new ReactiveTableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveTableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java index ae2298ddd..21a9b3ced 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java @@ -5,15 +5,15 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.util.concurrent.CompletionStage; - -import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveLocalTemporaryTableInsertStrategy extends LocalTemporaryTableInsertStrategy implements ReactiveSqmMultiTableInsertStrategy { @@ -23,27 +23,21 @@ public ReactiveLocalTemporaryTableInsertStrategy(LocalTemporaryTableInsertStrate } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new ReactiveTableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = new ReactiveTableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - afterUserAction(), - ReactiveLocalTemporaryTableInsertStrategy::throwUnexpectedCallToSessionUIDError, - getSessionFactory() - ).reactiveExecute( context ); - } - - private static String throwUnexpectedCallToSessionUIDError(SharedSessionContractImplementor session) { - throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } - private AfterUseAction afterUserAction() { - return isDropIdTables() - ? AfterUseAction.DROP - : getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java index fe07871eb..3e26378d9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java @@ -5,16 +5,16 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.util.concurrent.CompletionStage; - -import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveLocalTemporaryTableMutationStrategy extends LocalTemporaryTableMutationStrategy implements ReactiveSqmMultiTableMutationStrategy { @@ -23,44 +23,61 @@ public ReactiveLocalTemporaryTableMutationStrategy(LocalTemporaryTableMutationSt super( mutationStrategy.getTemporaryTable(), mutationStrategy.getSessionFactory() ); } - private static String throwUnexpectedAccessToSessionUID(SharedSessionContractImplementor session) { - // Should probably go in the LOG - throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); - } - @Override - public CompletionStage reactiveExecuteUpdate( + public MultiTableHandler buildHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { return new ReactiveTableBasedUpdateHandler( sqmUpdate, domainParameterXref, getTemporaryTable(), - afterUseAction(), - ReactiveLocalTemporaryTableMutationStrategy::throwUnexpectedAccessToSessionUID, - getSessionFactory() - ).reactiveExecute( context ); + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); } @Override - public CompletionStage reactiveExecuteDelete( + public MultiTableHandler buildHandler( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new ReactiveTableBasedDeleteHandler( - sqmDelete, - domainParameterXref, - getTemporaryTable(), - afterUseAction(), - ReactiveLocalTemporaryTableMutationStrategy::throwUnexpectedAccessToSessionUID, - getSessionFactory() - ).reactiveExecute( context ); - } - - private AfterUseAction afterUseAction() { - return isDropIdTables() - ? AfterUseAction.DROP - : getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new ReactiveTableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveTableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); + } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java index 9a44a44cf..a5b0c36c3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java @@ -10,12 +10,16 @@ import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactivePersistentTableInsertStrategy extends PersistentTableInsertStrategy implements ReactivePersistentTableStrategy, ReactiveSqmMultiTableInsertStrategy { @@ -45,18 +49,19 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage.thenCompose( v -> new ReactiveTableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = new ReactiveTableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java index 57cd14f4b..76109c78e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java @@ -5,18 +5,22 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; public class ReactivePersistentTableMutationStrategy extends PersistentTableMutationStrategy @@ -47,37 +51,56 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteUpdate( - SqmUpdateStatement sqmUpdateStatement, + public MultiTableHandler buildHandler( + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedUpdateHandler( - sqmUpdateStatement, - domainParameterXref, - getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + return new ReactiveTableBasedUpdateHandler( + sqmUpdate, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); } @Override - public CompletionStage reactiveExecuteDelete( - SqmDeleteStatement sqmDeleteStatement, + public MultiTableHandler buildHandler( + SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedDeleteHandler( - sqmDeleteStatement, - domainParameterXref, - getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new ReactiveTableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveTableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } } - @Override public CompletionStage getDropTableActionStage() { return tableDroppedStage; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java deleted file mode 100644 index 0e1097b63..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java +++ /dev/null @@ -1,640 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.temptable; - -import java.lang.invoke.MethodHandles; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.MutableInteger; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SelectableConsumer; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; -import org.hibernate.query.sqm.mutation.internal.temptable.ColumnReferenceCheckingSqlAstWalker; -import org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithoutIdTableHelper; -import org.hibernate.query.sqm.mutation.internal.temptable.RestrictedDeleteExecutionDelegate; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.reactive.util.impl.CompletionStages.Completable; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.spi.SqlExpressionResolver; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.expression.SqlTuple; -import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.from.UnionTableReference; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.predicate.PredicateCollector; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; - -import static org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -/** - * The reactive version of {@link RestrictedDeleteExecutionDelegate} - */ -// Basically a copy of RestrictedDeleteExecutionDelegate, we will probably need to refactor this code to avoid -// duplication -public class ReactiveRestrictedDeleteExecutionDelegate - implements ReactiveTableBasedDeleteHandler.ReactiveExecutionDelegate { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - private final EntityMappingType entityDescriptor; - private final TemporaryTable idTable; - private final AfterUseAction afterUseAction; - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; - private final SessionFactoryImplementor sessionFactory; - - private final Function sessionUidAccess; - private final MultiTableSqmMutationConverter converter; - - public ReactiveRestrictedDeleteExecutionDelegate( - EntityMappingType entityDescriptor, - TemporaryTable idTable, - AfterUseAction afterUseAction, - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - Function sessionUidAccess, - QueryOptions queryOptions, - LoadQueryInfluencers loadQueryInfluencers, - QueryParameterBindings queryParameterBindings, - SessionFactoryImplementor sessionFactory) { - this.entityDescriptor = entityDescriptor; - this.idTable = idTable; - this.afterUseAction = afterUseAction; - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; - this.sessionUidAccess = sessionUidAccess; - this.sessionFactory = sessionFactory; - this.converter = new MultiTableSqmMutationConverter( - entityDescriptor, - sqmDelete, - sqmDelete.getTarget(), - domainParameterXref, - queryOptions, - loadQueryInfluencers, - queryParameterBindings, - sessionFactory.getSqlTranslationEngine() - ); - } - - @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final EntityPersister entityDescriptor = sessionFactory.getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( sqmDelete.getTarget().getEntityName() ); - final String hierarchyRootTableName = entityDescriptor.getTableName(); - - final TableGroup deletingTableGroup = converter.getMutatingTableGroup(); - - final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( - deletingTableGroup.getNavigablePath(), - hierarchyRootTableName - ); - assert hierarchyRootTableReference != null; - - // Use the converter to interpret the where-clause. We do this for 2 reasons: - // 1) the resolved Predicate is ultimately the base for applying restriction to the deletes - // 2) we also inspect each ColumnReference that is part of the where-clause to see which - // table it comes from. If all the referenced columns (if any at all) are from the root table - // we can perform all the deletes without using an id-table - final Predicate specifiedRestriction = converter.visitWhereClause( sqmDelete.getWhereClause() ); - - final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); - entityDescriptor.applyBaseRestrictions( - predicateCollector, - deletingTableGroup, - true, - executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), - false, - null, - converter - ); - - converter.pruneTableGroupJoins(); - final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( - hierarchyRootTableReference.getIdentificationVariable() - ); - if ( predicateCollector.getPredicate() != null ) { - predicateCollector.getPredicate().accept( walker ); - } - - // We need an id table if we want to delete from an intermediate table to avoid FK violations - // The intermediate table has a FK to the root table, so we can't delete from the root table first - // Deleting from the intermediate table first also isn't possible, - // because that is the source for deletion in other tables, hence we need an id table - final boolean needsIdTable = !walker.isAllColumnReferencesFromIdentificationVariable() - || entityDescriptor != entityDescriptor.getRootEntityDescriptor(); - - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( - executionContext ); - - if ( needsIdTable ) { - return executeWithIdTable( - predicateCollector.getPredicate(), - converter.getJdbcParamsBySqmParam(), - converter.getSqmParameterMappingModelExpressibleResolutions(), - executionContextAdapter - ); - } - else { - return executeWithoutIdTable( - predicateCollector.getPredicate(), - deletingTableGroup, - converter.getJdbcParamsBySqmParam(), - converter.getSqmParameterMappingModelExpressibleResolutions(), - converter.getSqlExpressionResolver(), - executionContextAdapter - ); - } - } - - private CompletionStage executeWithoutIdTable( - Predicate suppliedPredicate, - TableGroup tableGroup, - Map, List>> restrictionSqmParameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, - SqlExpressionResolver sqlExpressionResolver, - ExecutionContext executionContext) { - assert entityDescriptor == entityDescriptor.getRootEntityDescriptor(); - - final EntityPersister rootEntityPersister = entityDescriptor.getEntityPersister(); - final String rootTableName = rootEntityPersister.getTableName(); - final NamedTableReference rootTableReference = (NamedTableReference) tableGroup.resolveTableReference( - tableGroup.getNavigablePath(), - rootTableName - ); - - final QuerySpec matchingIdSubQuerySpec = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( - tableGroup.getNavigablePath(), - rootTableReference, - suppliedPredicate, - rootEntityPersister, - sqlExpressionResolver, - sessionFactory - ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - SqmUtil.generateJdbcParamsXref( - domainParameterXref, - () -> restrictionSqmParameterResolutions - ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - executionContext.getSession() - ); - - - CompletionStage cleanUpCollectionTablesStage = ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - entityDescriptor, - (tableReference, attributeMapping) -> { - // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup - if ( suppliedPredicate == null ) { - return null; - } - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idSelectFkSubQuery; - // todo (6.0): based on the location of the attribute mapping, we could prune the table group of the subquery - if ( fkDescriptor.getTargetPart().isEntityIdentifierMapping() ) { - idSelectFkSubQuery = matchingIdSubQuerySpec; - } - else { - idSelectFkSubQuery = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( - tableGroup.getNavigablePath(), - rootTableReference, - suppliedPredicate, - rootEntityPersister, - sqlExpressionResolver, - sessionFactory - ); - } - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - sessionFactory - ), - idSelectFkSubQuery, - false - ); - - }, - jdbcParameterBindings, - executionContext - ); - - final CompletionStage[] deleteFromNonRootStages = new CompletionStage[] { voidFuture() }; - if ( rootTableReference instanceof UnionTableReference ) { - final MutableInteger rows = new MutableInteger(); - return cleanUpCollectionTablesStage - .thenCompose( v -> visitUnionTableReferences( - suppliedPredicate, - tableGroup, - sqlExpressionResolver, - executionContext, - matchingIdSubQuerySpec, - jdbcParameterBindings, - deleteFromNonRootStages, - rows - ) ) - .thenApply( o -> rows.get() ); - } - else { - entityDescriptor.visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> { - if ( !tableExpression.equals( rootTableName ) ) { - final NamedTableReference tableReference = (NamedTableReference) tableGroup.getTableReference( - tableGroup.getNavigablePath(), - tableExpression, - true - ); - final QuerySpec idMatchingSubQuerySpec; - // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup - idMatchingSubQuerySpec = suppliedPredicate == null ? null : matchingIdSubQuerySpec; - CompletableFuture future = new CompletableFuture<>(); - deleteFromNonRootStages[0] = deleteFromNonRootStages[0] - .thenCompose( v -> future ); - try { - deleteFromNonRootTableWithoutIdTable( - tableReference, - tableKeyColumnVisitationSupplier, - sqlExpressionResolver, - tableGroup, - idMatchingSubQuerySpec, - jdbcParameterBindings, - executionContext - ) - .thenCompose( CompletionStages::voidFuture ) - .whenComplete( (unused, throwable) -> { - if ( throwable == null ) { - future.complete( unused ); - } - else { - future.completeExceptionally( throwable ); - } - } ); - } - catch (Throwable t) { - future.completeExceptionally( t ); - } - } - } - ); - - return deleteFromNonRootStages[0] - .thenCompose( v -> deleteFromRootTableWithoutIdTable( - rootTableReference, - suppliedPredicate, - jdbcParameterBindings, - executionContext - ) ); - } - } - - private CompletionStage visitUnionTableReferences( - Predicate suppliedPredicate, - TableGroup tableGroup, - SqlExpressionResolver sqlExpressionResolver, - ExecutionContext executionContext, - QuerySpec matchingIdSubQuerySpec, - JdbcParameterBindings jdbcParameterBindings, - CompletionStage[] deleteFromNonRootStages, - MutableInteger rows) { - entityDescriptor.visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> { - final NamedTableReference tableReference = new NamedTableReference( - tableExpression, - tableGroup.getPrimaryTableReference().getIdentificationVariable() - ); - final QuerySpec idMatchingSubQuerySpec; - // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup - idMatchingSubQuerySpec = suppliedPredicate == null ? null : matchingIdSubQuerySpec; - CompletableFuture future = new CompletableFuture<>(); - deleteFromNonRootStages[0] = deleteFromNonRootStages[0] - .thenCompose( v -> future ); - deleteFromNonRootTableWithoutIdTable( - tableReference, - tableKeyColumnVisitationSupplier, - sqlExpressionResolver, - tableGroup, - idMatchingSubQuerySpec, - jdbcParameterBindings, - executionContext - ) - .thenAccept( rows::plus ) - .whenComplete( (unused, throwable) -> { - if ( throwable == null ) { - future.complete( unused ); - } - else { - future.completeExceptionally( throwable ); - } - } ); - } - ); - return deleteFromNonRootStages[0]; - } - - private CompletionStage deleteFromRootTableWithoutIdTable( - NamedTableReference rootTableReference, - Predicate predicate, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - return executeSqlDelete( - new DeleteStatement( rootTableReference, predicate ), - jdbcParameterBindings, - executionContext - ); - } - - private CompletionStage deleteFromNonRootTableWithoutIdTable( - NamedTableReference targetTableReference, - Supplier> tableKeyColumnVisitationSupplier, - SqlExpressionResolver sqlExpressionResolver, - TableGroup rootTableGroup, - QuerySpec matchingIdSubQuerySpec, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - assert targetTableReference != null; - LOG.tracef( "deleteFromNonRootTable - %s", targetTableReference.getTableExpression() ); - - final NamedTableReference deleteTableReference = new NamedTableReference( - targetTableReference.getTableExpression(), - DeleteStatement.DEFAULT_ALIAS, - true - ); - final Predicate tableDeletePredicate; - if ( matchingIdSubQuerySpec == null ) { - tableDeletePredicate = null; - } - else { - /* - * delete from sub_table - * where sub_id in ( - * select root_id from root_table - * where {predicate} - * ) - */ - - /* - * Create the `sub_id` reference as the LHS of the in-subquery predicate - */ - final List deletingTableColumnRefs = new ArrayList<>(); - tableKeyColumnVisitationSupplier.get().accept( - (columnIndex, selection) -> { - assert deleteTableReference.getTableReference( selection.getContainingTableExpression() ) != null; - - final Expression expression = sqlExpressionResolver.resolveSqlExpression( - deleteTableReference, - selection - ); - - deletingTableColumnRefs.add( (ColumnReference) expression ); - } - ); - - final Expression deletingTableColumnRefsExpression = deletingTableColumnRefs.size() == 1 - ? deletingTableColumnRefs.get( 0 ) - : new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() ); - - tableDeletePredicate = new InSubQueryPredicate( - deletingTableColumnRefsExpression, - matchingIdSubQuerySpec, - false - ); - } - - final DeleteStatement sqlAstDelete = new DeleteStatement( deleteTableReference, tableDeletePredicate ); - return executeSqlDelete( - sqlAstDelete, - jdbcParameterBindings, - executionContext - ).thenApply( rows -> { - LOG.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows ); - return rows; - } ); - } - - private static CompletionStage executeSqlDelete( - DeleteStatement sqlAst, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - - final JdbcServices jdbcServices = factory.getJdbcServices(); - - final JdbcOperationQueryMutation jdbcDelete = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqlAst ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcDelete, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContext - ); - } - - private CompletionStage executeWithIdTable( - Predicate predicate, - Map, List>> restrictionSqmParameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, - ExecutionContext executionContext) { - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - SqmUtil.generateJdbcParamsXref( - domainParameterXref, - () -> restrictionSqmParameterResolutions - ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - executionContext.getSession() - ); - - return ReactiveExecuteWithTemporaryTableHelper - .performBeforeTemporaryTableUseActions( idTable, executionContext ) - .thenCompose( v -> executeUsingIdTable( predicate, executionContext, jdbcParameterBindings ) - .handle( CompletionStages::handle ) - .thenCompose( resultHandler -> ReactiveExecuteWithTemporaryTableHelper - .performAfterTemporaryTableUseActions( - idTable, - sessionUidAccess, - afterUseAction, - executionContext - ) - .thenCompose( resultHandler::getResultAsCompletionStage ) - ) - ); - } - - private CompletionStage executeUsingIdTable( - Predicate predicate, - ExecutionContext executionContext, - JdbcParameterBindings jdbcParameterBindings) { - return ReactiveExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable( - converter, - predicate, - idTable, - sessionUidAccess, - jdbcParameterBindings, - executionContext ) - .thenCompose( rows -> { - final QuerySpec idTableIdentifierSubQuery = createIdTableSelectQuerySpec( - idTable, - sessionUidAccess, - entityDescriptor, - executionContext - ); - - return ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - entityDescriptor, - (tableReference, attributeMapping) -> { - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idTableFkSubQuery = fkDescriptor.getTargetPart() - .isEntityIdentifierMapping() - ? idTableIdentifierSubQuery - : createIdTableSelectQuerySpec( idTable, fkDescriptor.getTargetPart(), sessionUidAccess, entityDescriptor, executionContext ); - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - sessionFactory - ), - idTableFkSubQuery, - false - ); - - }, - JdbcParameterBindings.NO_BINDINGS, - executionContext - ).thenCompose( unused -> visitConstraintOrderedTables( idTableIdentifierSubQuery, executionContext ) - .thenApply( v -> rows ) ); - } ); - } - - private CompletionStage visitConstraintOrderedTables( - QuerySpec idTableIdentifierSubQuery, - ExecutionContext executionContext) { - final Completable completable = new Completable<>(); - entityDescriptor - .visitConstraintOrderedTables( (tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable( - tableExpression, - tableKeyColumnVisitationSupplier, - idTableIdentifierSubQuery, - executionContext - ) - .handle( completable::complete ) - ); - return completable.getStage().thenCompose( CompletionStages::voidFuture ); - } - - private CompletionStage deleteFromTableUsingIdTable( - String tableExpression, - Supplier> tableKeyColumnVisitationSupplier, - QuerySpec idTableSubQuery, - ExecutionContext executionContext) { - LOG.tracef( "deleteFromTableUsingIdTable - %s", tableExpression ); - - final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor ); - final NamedTableReference targetTable = new NamedTableReference( - tableExpression, - DeleteStatement.DEFAULT_ALIAS, - true - ); - - tableKeyColumnVisitationSupplier.get().accept( - (columnIndex, selection) -> { - assert selection.getContainingTableExpression().equals( tableExpression ); - assert !selection.isFormula(); - assert selection.getCustomReadExpression() == null; - assert selection.getCustomWriteExpression() == null; - - keyColumnCollector - .apply( new ColumnReference( targetTable, selection ) ); - } - ); - - final InSubQueryPredicate predicate = new InSubQueryPredicate( - keyColumnCollector.buildKeyExpression(), - idTableSubQuery, - false - ); - - return executeSqlDelete( - new DeleteStatement( targetTable, predicate ), - JdbcParameterBindings.NO_BINDINGS, - executionContext - ); - } - -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java index bb9ceff9c..c27fbf502 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java @@ -6,67 +6,147 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.lang.invoke.MethodHandles; +import java.util.UUID; import java.util.concurrent.CompletionStage; import java.util.function.Function; import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.persister.entity.UnionSubclassEntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedDeleteHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; public class ReactiveTableBasedDeleteHandler extends TableBasedDeleteHandler implements ReactiveAbstractMutationHandler { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public interface ReactiveExecutionDelegate extends TableBasedDeleteHandler.ExecutionDelegate { - - @Override - default int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext); - } - public ReactiveTableBasedDeleteHandler( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmDeleteStatement, domainParameterXref, idTable, afterUseAction, sessionUidAccess, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( + sqmDeleteStatement, + domainParameterXref, + idTable, + temporaryTableStrategy, + forceDropAfterUse, + sessionUidAccess, + context, + firstJdbcParameterBindingsConsumer + ); } @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { + public CompletionStage reactiveExecute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Starting multi-table delete execution - %s", - getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() + getSqmStatement().getTarget().getModel().getName() + ); + } + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + final CacheableSqmInterpretation idTableInsert = getIdTableInsert(); + final TemporaryTable idTable = getIdTable(); + final StandardReactiveJdbcMutationExecutor jdbcMutationExecutor = StandardReactiveJdbcMutationExecutor.INSTANCE; + if ( idTableInsert != null ) { + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + idTable, + getTemporaryTableStrategy(), + executionContext + ).thenCompose( unused -> + ReactiveExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( idTableInsert.jdbcOperation(), jdbcParameterBindings, executionContext ) + .thenCompose( rows -> executeDelete( jdbcParameterBindings, rows, executionContext, jdbcMutationExecutor ) ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( idTable, getSessionUidAccess(), getAfterUseAction(), executionContext ) + .thenCompose( v -> handler.getResultAsCompletionStage() ) ) + ); + } + else { + return loop(getCollectionTableDeletes() ,delete -> + reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + ).thenCompose( v -> { + final int[] rows = { 0 }; + return deleteRows( jdbcParameterBindings, jdbcMutationExecutor, executionContext, rows ) + .thenApply( vv -> rows[0] ); + } ); + } + } + + private CompletionStage deleteRows(JdbcParameterBindings jdbcParameterBindings, StandardReactiveJdbcMutationExecutor jdbcMutationExecutor, SqmJdbcExecutionContextAdapter executionContext, int[] rows) { + if ( getEntityDescriptor() instanceof UnionSubclassEntityPersister ) { + return CompletionStages + .loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + .thenApply( tot -> rows[0] += tot ) + ); + } + else { + return CompletionStages + .loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + .thenApply( tot -> rows[0] = tot ) + ); + } + } + + private CompletionStage executeDelete( + JdbcParameterBindings jdbcParameterBindings, + Integer rows, + SqmJdbcExecutionContextAdapter executionContext, + StandardReactiveJdbcMutationExecutor jdbcMutationExecutor) { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( executionContext.getSession() ) ) + ) ); } - return resolveDelegate( executionContext ).reactiveExecute( executionContext ); + return loop( getCollectionTableDeletes(), delete -> + reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + ).thenApply( v -> rows ); } - protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - return new ReactiveRestrictedDeleteExecutionDelegate( - getEntityDescriptor(), - getIdTable(), - getAfterUseAction(), - getSqmDeleteOrUpdateStatement(), - getDomainParameterXref(), - getSessionUidAccess(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - getSessionFactory() + private static CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + JdbcOperationQueryMutation delete, + StandardReactiveJdbcMutationExecutor jdbcMutationExecutor, + SqmJdbcExecutionContextAdapter executionContext) { + return jdbcMutationExecutor.executeReactive( + delete, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java index d697758ed..194b8b598 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java @@ -5,105 +5,398 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; - import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.Generator; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.id.insert.Binder; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedInsertHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.reactive.id.insert.ReactiveInsertGeneratedIdentifierDelegate; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; -import org.hibernate.reactive.query.sqm.mutation.internal.cte.ReactiveInsertExecutionDelegate; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; +import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.insert.ConflictClause; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBinder; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.type.descriptor.ValueBinder; -public class ReactiveTableBasedInsertHandler extends TableBasedInsertHandler implements ReactiveHandler { +import java.lang.invoke.MethodHandles; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.stream.IntStream; - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); +import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; - public interface ReactiveExecutionDelegate extends ExecutionDelegate { - @Override - default int execute(ExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } +public class ReactiveTableBasedInsertHandler extends TableBasedInsertHandler implements ReactiveHandler { - CompletionStage reactiveExecute(ExecutionContext executionContext); - } + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); public ReactiveTableBasedInsertHandler( SqmInsertStatement sqmInsert, DomainParameterXref domainParameterXref, TemporaryTable entityTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmInsert, domainParameterXref, entityTable, afterUseAction, sessionUidAccess, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmInsert, domainParameterXref, entityTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, context, firstJdbcParameterBindingsConsumer ); } @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { + public CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext context) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Starting multi-table insert execution - %s", - getSqmInsertStatement().getTarget().getModel().getName() + getSqmStatement().getTarget().getModel().getName() ); } - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter - .omittingLockingAndPaging( executionContext ); - return resolveDelegate( executionContext ) - .reactiveExecute( executionContextAdapter ); + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + // NOTE: we could get rid of using a temporary table if the expressions in Values are "stable". + // But that is a non-trivial optimization that requires more effort + // as we need to split out individual inserts if we have a non-bulk capable optimizer + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + getEntityTable(), + getTemporaryTableStrategy(), + executionContext + ).thenCompose( createdTable -> + ReactiveExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + getTemporaryTableInsert().jdbcOperation(), + jdbcParameterBindings, + executionContext + ).thenCompose( rows -> { + if ( rows != 0 ) { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( executionContext.getSession() ) ) + ) + ); + } + return insertRootTable( rows, createdTable, sessionUidBindings, executionContext ) + .thenCompose( insertedRows -> CompletionStages + .loop( + getNonRootTableInserts(), nonRootTableInsert -> + insertTable( nonRootTableInsert, sessionUidBindings, executionContext ) + ).thenApply( v -> insertedRows ) + ); + } + return CompletionStages.completedFuture( rows ); + } ) ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( + getEntityTable(), + getSessionUidAccess(), + getAfterUseAction(), + executionContext + ) + .thenCompose( v -> handler.getResultAsCompletionStage() ) + ); } - @Override - protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - return (ReactiveExecutionDelegate) super.resolveDelegate( executionContext ); + private CompletionStage insertTable( + JdbcOperationQueryMutation nonRootTableInsert, + JdbcParameterBindings sessionUidBindings, + ExecutionContext executionContext) { + return StandardReactiveJdbcMutationExecutor.INSTANCE + .executeReactive( + nonRootTableInsert, + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ).thenCompose( unused -> CompletionStages.voidFuture() ); } - @Override - protected ExecutionDelegate buildExecutionDelegate( - SqmInsertStatement sqmInsert, - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable entityTable, - AfterUseAction afterUseAction, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup insertingTableGroup, - Map tableReferenceByAlias, - List assignments, - InsertSelectStatement insertStatement, - ConflictClause conflictClause, - JdbcParameter sessionUidParameter, - DomainQueryExecutionContext executionContext) { - return new ReactiveInsertExecutionDelegate( - sqmConverter, - entityTable, - afterUseAction, - sessionUidAccess, - domainParameterXref, - insertingTableGroup, - tableReferenceByAlias, - assignments, - insertStatement, - conflictClause, - sessionUidParameter, - executionContext - ); + private CompletionStage insertRootTable( + int rows, + boolean rowNumberStartsAtOne, + JdbcParameterBindings sessionUidBindings, + SqmJdbcExecutionContextAdapter executionContext) { + final EntityPersister entityPersister = getEntityDescriptor().getEntityPersister(); + final Generator generator = entityPersister.getGenerator(); + final EntityIdentifierMapping identifierMapping = entityPersister.getIdentifierMapping(); + + final SharedSessionContractImplementor session = executionContext.getSession(); + final RootTableInserter rootTableInserter = getRootTableInserter(); + + if ( rootTableInserter.temporaryTableIdentitySelect() != null ) { + return StandardReactiveSelectExecutor.INSTANCE.list( + rootTableInserter.temporaryTableIdentitySelect(), + sessionUidBindings, + executionContext, + null, + null, + ReactiveListResultsConsumer.UniqueSemantic.NONE, + rows + ).thenApply( list -> { + Map entityTableToRootIdentity = new LinkedHashMap<>( list.size() ); + for ( Object o : list ) { + entityTableToRootIdentity.put( o, null ); + } + return entityTableToRootIdentity; + } ).thenCompose( entityTableToRootIdentity -> insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session + ) ); + } + else { + final Map entityTableToRootIdentity = null; + + if ( rootTableInserter.temporaryTableIdUpdate() != null ) { + final BeforeExecutionGenerator beforeExecutionGenerator = (BeforeExecutionGenerator) generator; + final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 3 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + updateBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( session ) ) + ) + ); + } + final List parameterBinders = rootTableInserter.temporaryTableIdUpdate().getParameterBinders(); + final JdbcParameter rootIdentity = (JdbcParameter) parameterBinders.get( 0 ); + final JdbcParameter rowNumber = (JdbcParameter) parameterBinders.get( 1 ); + final BasicEntityIdentifierMapping basicIdentifierMapping = (BasicEntityIdentifierMapping) identifierMapping; + + if ( !rowNumberStartsAtOne ) { + return ReactiveExecuteWithTemporaryTableHelper.loadInsertedRowNumbers( + rootTableInserter.temporaryTableRowNumberSelectSql(), + getEntityTable(), + getSessionUidAccess(), + rows, + executionContext + ).thenCompose( rowNumbers -> + forEachRow( + rowNumbers, + executionContext, + updateBindings, + rowNumber, + rootIdentity, + basicIdentifierMapping, + beforeExecutionGenerator, + session, + rootTableInserter + ).thenCompose( v -> insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session ) + ) + ); + } + else { + final Integer[] rowNumbers = IntStream.range( 1, rows + 1 ).boxed() + .toArray(Integer[]::new); + return forEachRow( + rowNumbers, + executionContext, + updateBindings, + rowNumber, + rootIdentity, + basicIdentifierMapping, + beforeExecutionGenerator, + session, + rootTableInserter + ).thenCompose( v -> insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session) + ); + } + } + return insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session + ); + } + } + + private static CompletionStage forEachRow( + Integer[] rowNumbers, + SqmJdbcExecutionContextAdapter executionContext, + JdbcParameterBindings updateBindings, + JdbcParameter rowNumber, + JdbcParameter rootIdentity, + BasicEntityIdentifierMapping basicIdentifierMapping, + BeforeExecutionGenerator beforeExecutionGenerator, + SharedSessionContractImplementor session, + RootTableInserter rootTableInserter) { + return loop( rowNumbers, rowNumberValue -> { + updateBindings.addBinding( + rowNumber, + new JdbcParameterBindingImpl( + rowNumber.getExpressionType().getSingleJdbcMapping(), + rowNumberValue + ) + ); + updateBindings.addBinding( + rootIdentity, + new JdbcParameterBindingImpl( + basicIdentifierMapping.getJdbcMapping(), + beforeExecutionGenerator.generate( session, null, null, INSERT ) + ) + ); + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + rootTableInserter.temporaryTableIdUpdate(), + updateBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ).thenApply( updateCount -> { + assert updateCount == 1; + return updateCount; + } ); + } ); } + + private CompletionStage insertRootTable( + JdbcParameterBindings sessionUidBindings, + SqmJdbcExecutionContextAdapter executionContext, + RootTableInserter rootTableInserter, + EntityPersister entityPersister, + EntityIdentifierMapping identifierMapping, + Map entityTableToRootIdentity, + SharedSessionContractImplementor session ) { + if ( rootTableInserter.rootTableInsertWithReturningSql() != null ) { + final GeneratedValuesMutationDelegate insertDelegate = entityPersister.getEntityPersister().getInsertDelegate(); + final BasicEntityIdentifierMapping basicIdentifierMapping = (BasicEntityIdentifierMapping) identifierMapping; + // todo 7.0 : InsertGeneratedIdentifierDelegate will be removed once we're going to handle + // generated values within the jdbc insert operaetion itself + final ReactiveInsertGeneratedIdentifierDelegate identifierDelegate = (ReactiveInsertGeneratedIdentifierDelegate) insertDelegate; + final ValueBinder jdbcValueBinder = basicIdentifierMapping.getJdbcMapping().getJdbcValueBinder(); + return loop(entityTableToRootIdentity.entrySet() , entry -> + identifierDelegate.reactivePerformInsertReturning( + rootTableInserter.rootTableInsertWithReturningSql(), + session, + new Binder() { + @Override + public void bindValues(PreparedStatement ps) throws SQLException { + jdbcValueBinder.bind( ps, entry.getKey(), 1, session ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidParameter.getParameterBinder().bindParameterValue( + ps, + 2, + sessionUidBindings, + executionContext + ); + } + } + + @Override + public Object getEntity() { + return null; + } + } + ).thenAccept( generatedValues -> { + entry.setValue( generatedValues.getGeneratedValue( identifierMapping ) ); + } ) + ).thenCompose( unused -> { + final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 2 ); + + final List parameterBinders = rootTableInserter.temporaryTableIdentityUpdate() + .getParameterBinders(); + final JdbcParameter rootIdentity = (JdbcParameter) parameterBinders.get( 0 ); + final JdbcParameter entityIdentity = (JdbcParameter) parameterBinders.get( 1 ); + return loop(entityTableToRootIdentity.entrySet(), entry -> { + JdbcMapping jdbcMapping = basicIdentifierMapping.getJdbcMapping(); + updateBindings.addBinding( + entityIdentity, + new JdbcParameterBindingImpl( jdbcMapping, entry.getKey() ) + ); + updateBindings.addBinding( + rootIdentity, + new JdbcParameterBindingImpl( jdbcMapping, entry.getValue() ) + ); + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + rootTableInserter.temporaryTableIdentityUpdate(), + updateBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + }).thenApply( v -> entityTableToRootIdentity.size() ); + }); + } + else { + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + rootTableInserter.rootTableInsert(), + sessionUidBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java new file mode 100644 index 000000000..e0edb82e2 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java @@ -0,0 +1,129 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.mutation.internal.temptable; + +import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedSoftDeleteHandler; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import java.lang.invoke.MethodHandles; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +public class ReactiveTableBasedSoftDeleteHandler extends TableBasedSoftDeleteHandler implements ReactiveHandler { + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + public ReactiveTableBasedSoftDeleteHandler( + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + TemporaryTable idTable, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, + Function sessionUidAccess, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( + sqmDelete, + domainParameterXref, + idTable, + temporaryTableStrategy, + forceDropAfterUse, + sessionUidAccess, + context, + firstJdbcParameterBindingsConsumer + ); + } + + @Override + public CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext context) { + if ( LOG.isTraceEnabled() ) { + LOG.tracef( + "Starting multi-table delete execution - %s", + getSqmStatement().getTarget().getModel().getName() + ); + } + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + StandardReactiveJdbcMutationExecutor jdbcMutationExecutor = StandardReactiveJdbcMutationExecutor.INSTANCE; + + final CacheableSqmInterpretation idTableInsert = getIdTableInsert(); + if ( idTableInsert != null ) { + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + getIdTable(), + getTemporaryTableStrategy(), + executionContext + ).thenCompose( unused -> + ReactiveExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + idTableInsert.jdbcOperation(), + jdbcParameterBindings, + executionContext + ).thenCompose( rows -> { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( executionContext.getSession() ) ) + ) + ); + } + return jdbcMutationExecutor.executeReactive( + getSoftDelete(), + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ).thenApply( u -> rows ); + } ) + .handle( CompletionStages::handle) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( + getIdTable(), + getSessionUidAccess(), + getAfterUseAction(), + executionContext) + .thenCompose( v -> handler.getResultAsCompletionStage() ) ) + ); + } + else { + return jdbcMutationExecutor.executeReactive( + getSoftDelete(), + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java index 7379f962e..d3ba6f6f3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java @@ -6,99 +6,117 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.function.Function; +import org.hibernate.AssertionFailure; import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedUpdateHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; public class ReactiveTableBasedUpdateHandler extends TableBasedUpdateHandler implements ReactiveAbstractMutationHandler { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public interface ReactiveExecutionDelegate extends TableBasedUpdateHandler.ExecutionDelegate { - @Override - default int execute(ExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - CompletionStage reactiveExecute(ExecutionContext executionContext); - } - public ReactiveTableBasedUpdateHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmUpdate, domainParameterXref, idTable, afterUseAction, sessionUidAccess, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmUpdate, domainParameterXref, idTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, context, firstJdbcParameterBindingsConsumer ); } @Override - public int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { + public CompletionStage reactiveExecute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Starting multi-table update execution - %s", - getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() + getSqmStatement().getTarget().getModel().getName() ); } - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); - return resolveDelegate( executionContext ).reactiveExecute( executionContextAdapter ); + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + getIdTable(), + getTemporaryTableStrategy(), + executionContext + ).thenCompose( unused -> ReactiveExecuteWithTemporaryTableHelper + .saveIntoTemporaryTable( + getMatchingIdsIntoIdTableInsert().jdbcOperation(), + jdbcParameterBindings, + executionContext + ).thenCompose( rows -> loop(getTableUpdaters(), tableUpdater -> + updateTable( tableUpdater, rows, jdbcParameterBindings, executionContext ) ) + .thenApply(v -> rows)) + .handle( CompletionStages::handle) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( getIdTable(), getSessionUidAccess(), getAfterUseAction(), executionContext ) + .thenCompose( v -> handler.getResultAsCompletionStage() )) + ); } - @Override - protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - return (ReactiveExecutionDelegate) super.resolveDelegate( executionContext ); + private CompletionStage updateTable( + TableUpdater tableUpdater, + int expectedUpdateCount, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + if ( tableUpdater == null ) { + // no assignments for this table - skip it + return voidFuture(); + } + return executeMutation( tableUpdater.jdbcUpdate(), jdbcParameterBindings, executionContext ) + .thenCompose( updateCount -> { + // We are done when the update count matches + if ( updateCount == expectedUpdateCount ) { + return voidFuture(); + } + + // If the table is optional, execute an insert + if ( tableUpdater.jdbcInsert() != null ) { + return executeMutation( tableUpdater.jdbcInsert(), jdbcParameterBindings, executionContext ) + .thenAccept( insertCount -> { + if(insertCount + updateCount != expectedUpdateCount){ + throw new AssertionFailure( "insertCount + updateCount != expectedUpdateCount"); + } + } ); + } + return voidFuture(); + } ); } - @Override - protected ReactiveUpdateExecutionDelegate buildExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable idTable, - AfterUseAction afterUseAction, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup updatingTableGroup, - Map tableReferenceByAlias, - List assignments, - Predicate suppliedPredicate, - DomainQueryExecutionContext executionContext) { - return new ReactiveUpdateExecutionDelegate( - sqmConverter, - idTable, - afterUseAction, - sessionUidAccess, - domainParameterXref, - updatingTableGroup, - tableReferenceByAlias, - assignments, - suppliedPredicate, + private CompletionStage executeMutation(JdbcOperationQueryMutation jdbcUpdate, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + jdbcUpdate, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, executionContext ); } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java index 126c168f8..9dde81ab7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java @@ -23,7 +23,7 @@ import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.util.impl.CompletionStages; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; /** * @see org.hibernate.dialect.temptable.TemporaryTableHelper @@ -38,13 +38,12 @@ public class ReactiveTemporaryTableHelper { * @see org.hibernate.jdbc.Work */ public interface ReactiveWork { - CompletionStage reactiveExecute(ReactiveConnection connection); + CompletionStage reactiveExecute(ReactiveConnection connection); } public static class TemporaryTableCreationWork implements ReactiveWork { private final TemporaryTable temporaryTable; private final TemporaryTableExporter exporter; - private final SessionFactoryImplementor sessionFactory; public TemporaryTableCreationWork( TemporaryTable temporaryTable, @@ -62,23 +61,25 @@ public TemporaryTableCreationWork( SessionFactoryImplementor sessionFactory) { this.temporaryTable = temporaryTable; this.exporter = exporter; - this.sessionFactory = sessionFactory; } @Override - public CompletionStage reactiveExecute(ReactiveConnection connection) { + public CompletionStage reactiveExecute(ReactiveConnection connection) { try { final String creationCommand = exporter.getSqlCreateCommand( temporaryTable ); return connection.executeUnprepared( creationCommand ) .handle( (integer, throwable) -> { + if ( throwable == null ) { + return true; + } logException( "create", creationCommand, temporaryTable, throwable ); - return null; + return false; } ); } catch (Exception e) { logException( "create", null, temporaryTable, e ); - return voidFuture(); + return falseFuture(); } } } @@ -111,19 +112,22 @@ public TemporaryTableDropWork( } @Override - public CompletionStage reactiveExecute(ReactiveConnection connection) { + public CompletionStage reactiveExecute(ReactiveConnection connection) { try { final String dropCommand = exporter.getSqlDropCommand( temporaryTable ); return connection.update( dropCommand ) .handle( (integer, throwable) -> { + if ( throwable == null ) { + return true; + } logException( "drop", dropCommand, temporaryTable, throwable ); - return null; + return false; } ); } catch (Exception e) { logException( "drop", null, temporaryTable, e ); - return voidFuture(); + return falseFuture(); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java deleted file mode 100644 index 927105dfa..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java +++ /dev/null @@ -1,292 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.temptable; - -import java.lang.invoke.MethodHandles; -import java.sql.PreparedStatement; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.SelectableConsumer; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.UpdateExecutionDelegate; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.SqlTuple; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.results.internal.SqlSelectionImpl; - -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions; -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions; -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -public class ReactiveUpdateExecutionDelegate extends UpdateExecutionDelegate implements ReactiveTableBasedUpdateHandler.ReactiveExecutionDelegate { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - public ReactiveUpdateExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable idTable, - AfterUseAction afterUseAction, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup updatingTableGroup, - Map tableReferenceByAlias, - List assignments, - Predicate suppliedPredicate, - DomainQueryExecutionContext executionContext) { - super( - sqmConverter, - idTable, - afterUseAction, - sessionUidAccess, - domainParameterXref, - updatingTableGroup, - tableReferenceByAlias, - assignments, - suppliedPredicate, - executionContext - ); - } - - private static void doNothing(Integer integer, PreparedStatement preparedStatement) { - } - - @Override - public int execute(ExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - @Override - public CompletionStage reactiveExecute(ExecutionContext executionContext) { - return performBeforeTemporaryTableUseActions( - getIdTable(), - executionContext - ) - .thenCompose( v -> saveMatchingIdsIntoIdTable( - getSqmConverter(), - getSuppliedPredicate(), - getIdTable(), - getSessionUidAccess(), - getJdbcParameterBindings(), - executionContext - ) ) - .thenCompose( rows -> { - final QuerySpec idTableSubQuery = createIdTableSelectQuerySpec( - getIdTable(), - getSessionUidAccess(), - getEntityDescriptor(), - executionContext - ); - - final CompletionStage[] resultStage = new CompletionStage[] { voidFuture() }; - getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> resultStage[0] = resultStage[0].thenCompose( - v -> reactiveUpdateTable( - tableExpression, - tableKeyColumnVisitationSupplier, - rows, - idTableSubQuery, - executionContext - ) ) - ); - return resultStage[0].thenApply( v -> rows ); - }) - .handle( CompletionStages::handle ) - .thenCompose( handler -> performAfterTemporaryTableUseActions( - getIdTable(), - getSessionUidAccess(), - getAfterUseAction(), - executionContext - ) - .thenCompose( handler::getResultAsCompletionStage ) - ); - } - - private CompletionStage reactiveUpdateTable( - String tableExpression, - Supplier> tableKeyColumnVisitationSupplier, - int expectedUpdateCount, - QuerySpec idTableSubQuery, - ExecutionContext executionContext) { - - // update `updatingTableReference` - // set ... - // where `keyExpression` in ( `idTableSubQuery` ) - - final TableReference updatingTableReference = getUpdatingTableGroup().getTableReference( - getUpdatingTableGroup().getNavigablePath(), - tableExpression, - true - ); - - final List assignments = getAssignmentsByTable().get( updatingTableReference ); - if ( assignments == null || assignments.isEmpty() ) { - // no assignments for this table - skip it - return voidFuture(); - } - - final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); - final JdbcServices jdbcServices = getSessionFactory().getJdbcServices(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcServices.getJdbcEnvironment().getSqlAstTranslatorFactory(); - - final Expression keyExpression = resolveMutatingTableKeyExpression( tableExpression, tableKeyColumnVisitationSupplier ); - - return executeUpdate( idTableSubQuery, executionContext, assignments, dmlTableReference, sqlAstTranslatorFactory, keyExpression ) - .thenCompose( updateCount -> { - // We are done when the update count matches - if ( updateCount == expectedUpdateCount ) { - return voidFuture(); - } - // If the table is optional, execute an insert - if ( isTableOptional( tableExpression ) ) { - return executeInsert( - tableExpression, - dmlTableReference, - keyExpression, - tableKeyColumnVisitationSupplier, - idTableSubQuery, - assignments, - sqlAstTranslatorFactory, - executionContext - ) - .thenAccept( insertCount -> { - assert insertCount + updateCount == expectedUpdateCount; - } ); - } - return voidFuture(); - } ); - } - - - private CompletionStage executeUpdate(QuerySpec idTableSubQuery, ExecutionContext executionContext, List assignments, NamedTableReference dmlTableReference, SqlAstTranslatorFactory sqlAstTranslatorFactory, Expression keyExpression) { - final UpdateStatement sqlAst = new UpdateStatement( - dmlTableReference, - assignments, - new InSubQueryPredicate( keyExpression, idTableSubQuery, false ) - ); - - final JdbcOperationQueryMutation jdbcUpdate = sqlAstTranslatorFactory - .buildMutationTranslator( getSessionFactory(), sqlAst ) - .translate( getJdbcParameterBindings(), executionContext.getQueryOptions() ); - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcUpdate, - getJdbcParameterBindings(), - executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - ::prepareStatement, - ReactiveUpdateExecutionDelegate::doNothing, - executionContext - ); - } - - private CompletionStage executeInsert( - String targetTableExpression, - NamedTableReference targetTableReference, - Expression targetTableKeyExpression, - Supplier> tableKeyColumnVisitationSupplier, - QuerySpec idTableSubQuery, - List assignments, - SqlAstTranslatorFactory sqlAstTranslatorFactory, - ExecutionContext executionContext) { - - // Execute a query in the form - - // - // insert into (...) - // select ... - // from temptable_ - // where not exists ( - // select 1 - // from dml_ - // where dml_. = temptable_. - // ) - - // Create a new QuerySpec for the "insert source" select query. This - // is mostly a copy of the incoming `idTableSubQuery` along with the - // NOT-EXISTS predicate - final QuerySpec insertSourceSelectQuerySpec = makeInsertSourceSelectQuerySpec( idTableSubQuery ); - - // create the `select 1 ...` sub-query and apply the not-exists predicate - final QuerySpec existsSubQuerySpec = createExistsSubQuerySpec( targetTableExpression, tableKeyColumnVisitationSupplier, idTableSubQuery ); - insertSourceSelectQuerySpec.applyPredicate( - new ExistsPredicate( - existsSubQuerySpec, - true, - getSessionFactory().getTypeConfiguration().getBasicTypeForJavaType( Boolean.class ) - ) - ); - - // Collect the target column references from the key expressions - final List targetColumnReferences = new ArrayList<>(); - if ( targetTableKeyExpression instanceof SqlTuple ) { - //noinspection unchecked - targetColumnReferences.addAll( (Collection) ( (SqlTuple) targetTableKeyExpression ).getExpressions() ); - } - else { - targetColumnReferences.add( (ColumnReference) targetTableKeyExpression ); - } - - // And transform assignments to target column references and selections - for ( Assignment assignment : assignments ) { - targetColumnReferences.addAll( assignment.getAssignable().getColumnReferences() ); - insertSourceSelectQuerySpec.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( assignment.getAssignedValue() ) ); - } - - final InsertSelectStatement insertSqlAst = new InsertSelectStatement( targetTableReference ); - insertSqlAst.addTargetColumnReferences( targetColumnReferences.toArray( new ColumnReference[0] ) ); - insertSqlAst.setSourceSelectStatement( insertSourceSelectQuerySpec ); - - final JdbcOperationQueryMutation jdbcInsert = sqlAstTranslatorFactory - .buildMutationTranslator( getSessionFactory(), insertSqlAst ) - .translate( getJdbcParameterBindings(), executionContext.getQueryOptions() ); - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcInsert, - getJdbcParameterBindings(), - executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - ::prepareStatement, - ReactiveUpdateExecutionDelegate::doNothing, - executionContext - ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java index a4a622537..f3a3b9a42 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java @@ -8,7 +8,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; /** @@ -16,8 +15,6 @@ */ public interface ReactiveAbstractMutationHandler extends ReactiveHandler { - SqmDeleteOrUpdateStatement getSqmDeleteOrUpdateStatement(); - EntityMappingType getEntityDescriptor(); SessionFactoryImplementor getSessionFactory(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java index dc52d893e..93399180d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java @@ -10,10 +10,12 @@ import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; public interface ReactiveSqmMultiTableInsertStrategy extends SqmMultiTableInsertStrategy { @@ -27,8 +29,18 @@ default int executeInsert( throw LOG.nonReactiveMethodCall( "reactiveExecuteInsert" ); } - CompletionStage reactiveExecuteInsert( + /** + * Execute the multi-table insert indicated by the passed SqmInsertStatement + * + * @return The number of rows affected + * @deprecated Uses {@link #buildHandler(SqmInsertStatement, DomainParameterXref, DomainQueryExecutionContext)} instead + */ + @Deprecated(forRemoval = true, since = "3.1") + default CompletionStage reactiveExecuteInsert( SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context){ + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmInsertStatement, domainParameterXref, context ); + return ((ReactiveHandler)buildResult.multiTableHandler()).reactiveExecute( buildResult.firstJdbcParameterBindings(), context ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java index 41b1c6224..b4a4bd077 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java @@ -10,11 +10,14 @@ import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; public interface ReactiveSqmMultiTableMutationStrategy extends SqmMultiTableMutationStrategy { @@ -28,10 +31,21 @@ default int executeUpdate( throw LOG.nonReactiveMethodCall( "reactiveExecuteUpdate" ); } - CompletionStage reactiveExecuteUpdate( + /** + * Execute the multi-table update indicated by the passed SqmUpdateStatement + * + * @return The number of rows affected + * @deprecated Use {@link #buildHandler(SqmDeleteOrUpdateStatement, DomainParameterXref, DomainQueryExecutionContext)} instead + */ + @Deprecated(forRemoval = true, since = "7.1") + default CompletionStage reactiveExecuteUpdate( SqmUpdateStatement sqmUpdateStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context){ + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmUpdateStatement, domainParameterXref, context ); + return ((ReactiveHandler)buildResult.multiTableHandler()).reactiveExecute( buildResult.firstJdbcParameterBindings(), context ); + + } @Override default int executeDelete( @@ -41,8 +55,18 @@ default int executeDelete( throw LOG.nonReactiveMethodCall( "reactiveExecuteDelete" ); } - CompletionStage reactiveExecuteDelete( + /** + * Execute the multi-table update indicated by the passed SqmUpdateStatement + * + * @return The number of rows affected + * @deprecated Use {@link #buildHandler(SqmDeleteOrUpdateStatement, DomainParameterXref, DomainQueryExecutionContext)} instead + */ + @Deprecated(forRemoval = true, since = "3.1") + default CompletionStage reactiveExecuteDelete( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context){ + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmDeleteStatement, domainParameterXref, context ); + return ((ReactiveHandler)buildResult.multiTableHandler()).reactiveExecute( buildResult.firstJdbcParameterBindings(), context ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 795b565ff..7b0bd112a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -109,7 +109,7 @@ import org.hibernate.reactive.query.ReactiveSelectionQuery; import org.hibernate.reactive.query.sql.internal.ReactiveNativeQueryImpl; import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor; -import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; +import org.hibernate.reactive.query.sqm.internal.ReactiveSqmQueryImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.util.impl.CompletionStages; @@ -390,7 +390,7 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) } protected ReactiveQueryImplementor createReactiveCriteriaQuery(SqmStatement criteria, Class resultType) { - final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( criteria, resultType, this ); + final ReactiveSqmQueryImpl query = new ReactiveSqmQueryImpl<>( criteria, resultType, this ); applyQuerySettingsAndHints( query ); return query; } @@ -400,11 +400,11 @@ public ReactiveQuery createReactiveQuery(TypedQueryReference typedQuer checksBeforeQueryCreation(); if ( typedQueryReference instanceof SelectionSpecificationImpl specification ) { final CriteriaQuery query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, specification.getResultType(), this ); } else if ( typedQueryReference instanceof MutationSpecificationImpl specification ) { final CommonAbstractCriteria query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); } else { @SuppressWarnings("unchecked") @@ -433,8 +433,8 @@ public ReactiveQuery createReactiveQuery(String queryString, Class exp try { final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); - final ReactiveQuerySqmImpl query = - new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this ); + final ReactiveSqmQueryImpl query = + new ReactiveSqmQueryImpl<>( queryString, interpretation, expectedResultType, this ); applyQuerySettingsAndHints( query ); query.setComment( queryString ); return query; @@ -611,7 +611,7 @@ protected ReactiveNativeQueryImpl createReactiveNativeQueryImplementor(Cl return (ReactiveNativeQueryImpl) query; } - protected ReactiveQuerySqmImpl createReactiveSqmQueryImplementor(Class resultType, NamedSqmQueryMemento memento) { + protected ReactiveSqmQueryImpl createReactiveSqmQueryImplementor(Class resultType, NamedSqmQueryMemento memento) { final SqmQueryImplementor query = memento.toQuery( this, resultType ); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic query" ); @@ -620,7 +620,7 @@ protected ReactiveQuerySqmImpl createReactiveSqmQueryImplementor(Class if ( memento.getLockOptions() != null ) { query.setLockOptions( memento.getLockOptions() ); } - return (ReactiveQuerySqmImpl) query; + return (ReactiveSqmQueryImpl) query; } private RuntimeException convertNamedQueryException(RuntimeException e) { @@ -649,7 +649,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(String hqlString final QueryImplementor query = createQuery( hqlString ); final SqmStatement sqmStatement = ( (SqmQueryImplementor) query ).getSqmStatement(); checkMutationQuery( hqlString, sqmStatement ); - return new ReactiveQuerySqmImpl<>( sqmStatement, null, this ); + return new ReactiveSqmQueryImpl<>( sqmStatement, null, this ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index d67cc2018..51cf19747 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -68,7 +68,7 @@ import org.hibernate.reactive.query.ReactiveSelectionQuery; import org.hibernate.reactive.query.sql.internal.ReactiveNativeQueryImpl; import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor; -import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; +import org.hibernate.reactive.query.sqm.internal.ReactiveSqmQueryImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; import org.hibernate.reactive.session.ReactiveSqmQueryImplementor; import org.hibernate.reactive.session.ReactiveStatelessSession; @@ -319,11 +319,12 @@ public void whenComplete(BiConsumer consumer) { private CompletionStage recreateCollections(Object entity, Object id, EntityPersister persister) { final Completable stage = new Completable<>(); + final String entityName = persister.getEntityName(); + final EventMonitor eventMonitor = getEventMonitor(); final Loop loop = new Loop(); forEachOwnedCollection( entity, id, persister, (descriptor, collection) -> { - firePreRecreate( collection, descriptor ); - final EventMonitor eventMonitor = getEventMonitor(); + firePreRecreate( collection, descriptor, entityName, entity ); final DiagnosticEvent event = eventMonitor.beginCollectionRecreateEvent(); loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) .reactiveRecreate( collection, id, this ) ) @@ -335,7 +336,7 @@ private CompletionStage recreateCollections(Object entity, Object id, Enti if ( statistics.isStatisticsEnabled() ) { statistics.recreateCollection( descriptor.getRole() ); } - firePostRecreate( collection, descriptor ); + firePostRecreate( collection, id, entityName, descriptor ); } ) ); } @@ -480,29 +481,35 @@ public CompletionStage reactiveDelete(Object entity) { } private CompletionStage removeCollections(Object entity, Object id, EntityPersister persister) { - final Completable stage = new Completable<>(); - final Loop loop = new Loop(); - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> { - firePreRemove( collection, entity, descriptor ); - final EventMonitor eventMonitor = getEventMonitor(); - final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); - loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) - .reactiveRemove( id, this ) ) - .whenComplete( (unused, throwable) -> eventMonitor - .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) - ) - .thenAccept( v -> { - firePostRemove( collection, entity, descriptor ); - final StatisticsImplementor statistics = getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.removeCollection( descriptor.getRole() ); - } - } ) - ); - } ); - loop.whenComplete( stage::complete ); - return stage.getStage(); + if ( persister.hasOwnedCollections() ) { + final Loop loop = new Loop(); + final Completable stage = new Completable<>(); + final String entityName = persister.getEntityName(); + forEachOwnedCollection( + entity, id, persister, + (descriptor, collection) -> { + firePreRemove( collection, id, entityName, entity ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); + loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) + .reactiveRemove( id, this ) ) + .whenComplete( (unused, throwable) -> eventMonitor + .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) + ) + .thenAccept( v -> { + firePostRemove( collection, id, entityName, entity ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.removeCollection( descriptor.getRole() ); + } + } ) + ); + } + ); + loop.whenComplete( stage::complete ); + return stage.getStage(); + } + return voidFuture(); } @Override @@ -561,31 +568,37 @@ private CompletionStage executeReactiveUpdate(Object entity) { } private CompletionStage removeAndRecreateCollections(Object entity, Object id, EntityPersister persister) { - final Completable stage = new Completable<>(); - final Loop loop = new Loop(); - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> { - firePreUpdate( collection, descriptor ); - final EventMonitor eventMonitor = getEventMonitor(); - final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); - ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) persister; - loop.then( () -> supplyStage( () -> reactivePersister - .reactiveRemove( id, this ) - .thenCompose( v -> reactivePersister.reactiveRecreate( collection, id, this ) ) ) - .whenComplete( (unused, throwable) -> eventMonitor - .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) - ) - .thenAccept( v -> { - firePostUpdate( collection, descriptor ); - final StatisticsImplementor statistics = getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.updateCollection( descriptor.getRole() ); - } - } ) - ); - } ); - loop.whenComplete( stage::complete ); - return stage.getStage(); + if ( persister.hasOwnedCollections() ) { + final String entityName = persister.getEntityName(); + final Completable stage = new Completable<>(); + final Loop loop = new Loop(); + forEachOwnedCollection( + entity, id, persister, + (descriptor, collection) -> { + firePreUpdate( collection, id, entityName, entity ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); + ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) persister; + loop.then( () -> supplyStage( () -> reactivePersister + .reactiveRemove( id, this ) + .thenCompose( v -> reactivePersister.reactiveRecreate( collection, id, this ) ) ) + .whenComplete( (unused, throwable) -> eventMonitor + .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) + ) + .thenAccept( v -> { + firePostUpdate( collection, id, entityName, entity); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.updateCollection( descriptor.getRole() ); + } + } ) + ); + } + ); + loop.whenComplete( stage::complete ); + return stage.getStage(); + } + return voidFuture(); } @Override @@ -938,11 +951,11 @@ public ReactiveQuery createReactiveQuery(TypedQueryReference typedQuer checksBeforeQueryCreation(); if ( typedQueryReference instanceof SelectionSpecificationImpl specification ) { final CriteriaQuery query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, specification.getResultType(), this ); } if ( typedQueryReference instanceof MutationSpecificationImpl specification ) { final CommonAbstractCriteria query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); } @SuppressWarnings("unchecked") // this cast is fine because of all our impls of TypedQueryReference return Class @@ -987,7 +1000,7 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) } private ReactiveQuery createReactiveCriteriaQuery(SqmStatement criteria, Class resultType) { - final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( criteria, resultType, this ); + final ReactiveSqmQueryImpl query = new ReactiveSqmQueryImpl<>( criteria, resultType, this ); applyQuerySettingsAndHints( query ); return query; } @@ -1013,8 +1026,8 @@ public ReactiveSqmQueryImplementor createReactiveQuery(String queryString try { final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); - final ReactiveQuerySqmImpl query = - new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this ); + final ReactiveSqmQueryImpl query = + new ReactiveSqmQueryImpl<>( queryString, interpretation, expectedResultType, this ); applyQuerySettingsAndHints( query ); query.setComment( queryString ); @@ -1157,7 +1170,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(String hqlString final QueryImplementor query = createQuery( hqlString ); final SqmStatement sqmStatement = ( (SqmQueryImplementor) query ).getSqmStatement(); checkMutationQuery( hqlString, sqmStatement ); - return new ReactiveQuerySqmImpl<>( sqmStatement, null, this ); + return new ReactiveSqmQueryImpl<>( sqmStatement, null, this ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java index a4b45101f..8b66a3bfb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java @@ -27,7 +27,7 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; /** - * @see org.hibernate.query.results.ResultSetMappingImpl + * @see org.hibernate.query.results.internal.ResultSetMappingImpl */ public class ReactiveResultSetMapping implements ResultSetMapping, ReactiveValuesMappingProducer { @@ -62,6 +62,11 @@ public CompletionStage reactiveResolve( .thenApply( columnCount -> delegate.resolve( jdbcResultsMetadata, loadQueryInfluencers, sessionFactory ) ); } + @Override + public ResultSetMapping cacheKeyInstance() { + return new ReactiveResultSetMapping( delegate.cacheKeyInstance() ); + } + @Override public String getMappingIdentifier() { return delegate.getMappingIdentifier(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java new file mode 100644 index 000000000..db8222838 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java @@ -0,0 +1,42 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableAssembler; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; + +import java.util.concurrent.CompletionStage; + +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; + +/** + * @see org.hibernate.sql.results.graph.embeddable.internal.EmbeddableAssembler + */ +public class ReactiveEmbeddableAssembler extends EmbeddableAssembler implements ReactiveDomainResultsAssembler { + + public ReactiveEmbeddableAssembler(EmbeddableInitializer initializer) { + super( initializer ); + } + + @Override + public CompletionStage reactiveAssemble(ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { + final ReactiveInitializer reactiveInitializer = (ReactiveInitializer) getInitializer(); + final InitializerData data = reactiveInitializer.getData( rowProcessingState ); + final Initializer.State state = data.getState(); + if ( state == Initializer.State.KEY_RESOLVED ) { + return reactiveInitializer + .reactiveResolveInstance( data ) + .thenApply( v -> reactiveInitializer.getResolvedInstance( data ) ); + } + return completedFuture( reactiveInitializer.getResolvedInstance( data ) ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java index 50034533b..685717f35 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java @@ -6,14 +6,20 @@ package org.hibernate.reactive.sql.results.graph.embeddable.internal; import org.hibernate.engine.FetchTiming; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchSelectImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; public class ReactiveEmbeddableFetchImpl extends EmbeddableFetchImpl { @@ -32,9 +38,26 @@ public ReactiveEmbeddableFetchImpl(EmbeddableFetchImpl original) { } @Override - public EmbeddableInitializer createInitializer( - InitializerParent parent, - AssemblerCreationState creationState) { - return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), parent, creationState, true ); + public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), getNullIndicatorResult(), parent, creationState, true ); + } + + @Override + public DomainResultAssembler createAssembler(InitializerParent parent, AssemblerCreationState creationState) { + Initializer initializer = creationState.resolveInitializer( this, parent, this ); + EmbeddableInitializer embeddableInitializer = initializer.asEmbeddableInitializer(); + return new ReactiveEmbeddableAssembler( embeddableInitializer ); + } + + @Override + public Fetch findFetch(Fetchable fetchable) { + Fetch fetch = super.findFetch( fetchable ); + if ( fetch instanceof EntityFetchSelectImpl entityFetchSelect ) { + return new ReactiveEntityFetchSelectImpl( entityFetchSelect ); + } + else if ( fetch instanceof EmbeddableFetchImpl embeddableFetch ) { + return new ReactiveEmbeddableFetchImpl( embeddableFetch ); + } + return fetch; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java index 1e3433320..426345ab5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java @@ -20,7 +20,7 @@ public ReactiveEmbeddableForeignKeyResultImpl(EmbeddableForeignKeyResultImpl @Override public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { return getReferencedModePart() instanceof NonAggregatedIdentifierMapping - ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) - : new ReactiveEmbeddableInitializerImpl( this, null, null, creationState, true ); + ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) + : new ReactiveEmbeddableInitializerImpl( this, null, null, null, creationState, true ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java index a1d4a890e..6d5fbcd5e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -5,12 +5,19 @@ */ package org.hibernate.reactive.sql.results.graph.embeddable.internal; + import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.VirtualModelPart; +import org.hibernate.metamodel.spi.EmbeddableInstantiator; +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerData; import org.hibernate.sql.results.graph.InitializerParent; @@ -19,8 +26,13 @@ import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import static org.hibernate.reactive.util.impl.CompletionStages.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; +import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; +import static org.hibernate.sql.results.graph.embeddable.EmbeddableLoadingLogger.EMBEDDED_LOAD_LOGGER; +import static org.hibernate.sql.results.graph.entity.internal.BatchEntityInsideEmbeddableSelectFetchInitializer.BATCH_PROPERTY; public class ReactiveEmbeddableInitializerImpl extends EmbeddableInitializerImpl implements ReactiveInitializer { @@ -33,6 +45,10 @@ public ReactiveEmbeddableInitializerData( super( initializer, rowProcessingState ); } + public Object[] getRowState(){ + return rowState; + } + @Override public void setState(State state) { super.setState( state ); @@ -51,10 +67,11 @@ public EmbeddableMappingType.ConcreteEmbeddableType getConcreteEmbeddableType() public ReactiveEmbeddableInitializerImpl( EmbeddableResultGraphNode resultDescriptor, BasicFetch discriminatorFetch, + DomainResult nullIndicatorResult, InitializerParent parent, AssemblerCreationState creationState, boolean isResultInitializer) { - super( resultDescriptor, discriminatorFetch, parent, creationState, isResultInitializer ); + super( resultDescriptor, discriminatorFetch, nullIndicatorResult, parent, creationState, isResultInitializer ); } @Override @@ -64,10 +81,128 @@ protected InitializerData createInitializerData(RowProcessingState rowProcessing @Override public CompletionStage reactiveResolveInstance(EmbeddableInitializerData data) { - super.resolveInstance( data ); + if ( data.getState() != State.KEY_RESOLVED ) { + return voidFuture(); + } + + data.setState( State.RESOLVED ); + return extractRowState( (ReactiveEmbeddableInitializerData) data ) + .thenCompose( unused -> prepareCompositeInstance( (ReactiveEmbeddableInitializerData) data ) ); + } + + private CompletionStage extractRowState(ReactiveEmbeddableInitializerData data) { + final DomainResultAssembler[] subAssemblers = assemblers[data.getSubclassId()]; + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object[] rowState = data.getRowState(); + final boolean[] stateAllNull = {true}; + final int[] index = {0}; + final boolean[] forceExit = { false }; + return whileLoop( + () -> index[0] < subAssemblers.length && !forceExit[0], + () -> { + final int i = index[0]++; + final DomainResultAssembler assembler = subAssemblers[i]; + if ( assembler instanceof ReactiveDomainResultsAssembler reactiveAssembler ) { + return reactiveAssembler.reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + .thenAccept( contributorValue -> setContributorValue( + contributorValue, + i, + rowState, + stateAllNull, + forceExit + ) ); + } + else { + setContributorValue( + assembler == null ? null : assembler.assemble( rowProcessingState ), + i, + rowState, + stateAllNull, + forceExit + ); + return voidFuture(); + } + }) + .whenComplete( + (unused, throwable) -> { + if ( stateAllNull[0] ) { + data.setState( State.MISSING ); + } + } + ); + } + + private void setContributorValue( + Object contributorValue, + int index, + Object[] rowState, + boolean[] stateAllNull, + boolean[] forceExit) { + if ( contributorValue == BATCH_PROPERTY ) { + rowState[index] = null; + } + else { + rowState[index] = contributorValue; + } + if ( contributorValue != null ) { + stateAllNull[0] = false; + } + else if ( isPartOfKey() ) { + // If this is a foreign key and there is a null part, the whole thing has to be turned into null + stateAllNull[0] = true; + forceExit[0] = true; + } + } + + private CompletionStage prepareCompositeInstance(ReactiveEmbeddableInitializerData data) { + // Virtual model parts use the owning entity as container which the fetch parent access provides. + // For an identifier or foreign key this is called during the resolveKey phase of the fetch parent, + // so we can't use the fetch parent access in that case. + final ReactiveInitializer parent = (ReactiveInitializer) getParent(); + if ( parent != null && getInitializedPart() instanceof VirtualModelPart && !isPartOfKey() && data.getState() != State.MISSING ) { + final ReactiveEmbeddableInitializerData subData = parent.getData( data.getRowProcessingState() ); + return parent + .reactiveResolveInstance( subData ) + .thenCompose( + unused -> { + data.setInstance( parent.getResolvedInstance( subData ) ); + if ( data.getState() == State.INITIALIZED ) { + return voidFuture(); + } + return doCreateCompositeInstance( data ) + .thenAccept( v -> EMBEDDED_LOAD_LOGGER.debugf( + "Created composite instance [%s]", + getNavigablePath() + ) ); + } ); + } + + return doCreateCompositeInstance( data ) + .thenAccept( v -> EMBEDDED_LOAD_LOGGER.debugf( "Created composite instance [%s]", getNavigablePath() ) ); + + } + + private CompletionStage doCreateCompositeInstance(ReactiveEmbeddableInitializerData data) { + if ( data.getInstance() == null ) { + return createCompositeInstance( data ) + .thenAccept( data::setInstance ); + } return voidFuture(); } + private CompletionStage createCompositeInstance(ReactiveEmbeddableInitializerData data) { + if ( data.getState() == State.MISSING ) { + return nullFuture(); + } + + final EmbeddableInstantiator instantiator = data.getConcreteEmbeddableType() == null + ? getInitializedPart().getEmbeddableTypeDescriptor().getRepresentationStrategy().getInstantiator() + : data.getConcreteEmbeddableType().getInstantiator(); + final Object instance = instantiator.instantiate( data ); + data.setState( State.RESOLVED ); + return completedFuture( instance ); + } + @Override public CompletionStage reactiveInitializeInstance(EmbeddableInitializerData data) { super.initializeInstance( data ); @@ -81,16 +216,21 @@ public CompletionStage forEachReactiveSubInitializer( final ReactiveEmbeddableInitializerData embeddableInitializerData = (ReactiveEmbeddableInitializerData) data; final RowProcessingState rowProcessingState = embeddableInitializerData.getRowProcessingState(); if ( embeddableInitializerData.getConcreteEmbeddableType() == null ) { - return loop( subInitializers, subInitializer -> loop( subInitializer, initializer -> consumer - .apply( (ReactiveInitializer) initializer, rowProcessingState ) - ) ); + return loop( subInitializers, subInitializer -> + loop( subInitializer, initializer -> + initializer != null + ? consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ) + : voidFuture() + ) + ); } else { Initializer[] initializers = subInitializers[embeddableInitializerData.getSubclassId()]; - return loop( 0, initializers.length, i -> { - ReactiveInitializer reactiveInitializer = (ReactiveInitializer) initializers[i]; - return consumer.apply( reactiveInitializer, rowProcessingState ); - } ); + return loop(0, initializers.length, i -> + initializers[i] != null + ? consumer.apply( (ReactiveInitializer) initializers[i], rowProcessingState ) + : voidFuture() + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java index a7342275d..c6ee36363 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -26,6 +26,7 @@ import org.hibernate.proxy.map.MapProxy; import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; @@ -254,16 +255,21 @@ public CompletionStage reactiveResolveInstance(EntityInitializerData origi final RowProcessingState rowProcessingState = data.getRowProcessingState(); data.setState( State.RESOLVED ); if ( data.getEntityKey() == null ) { - assert getIdentifierAssembler() != null; - final Object id = getIdentifierAssembler().assemble( rowProcessingState ); - if ( id == null ) { - setMissing( data ); - return voidFuture(); - } - resolveEntityKey( data, id ); + return assembleId( rowProcessingState ) + .thenCompose( id -> { + if ( id == null ) { + setMissing( data ); + return voidFuture(); + } + resolveEntityKey( data, id ); + return postAssembleId( rowProcessingState, data ); + } ); } - final PersistenceContext persistenceContext = rowProcessingState.getSession() - .getPersistenceContextInternal(); + return postAssembleId( rowProcessingState, data ); + } + + private CompletionStage postAssembleId(RowProcessingState rowProcessingState, ReactiveEntityInitializerData data) { + final PersistenceContext persistenceContext = rowProcessingState.getSession().getPersistenceContextInternal(); data.setEntityHolder( persistenceContext.claimEntityHolderIfPossible( data.getEntityKey(), null, @@ -274,29 +280,37 @@ public CompletionStage reactiveResolveInstance(EntityInitializerData origi if ( useEmbeddedIdentifierInstanceAsEntity( data ) ) { data.setEntityInstanceForNotify( rowProcessingState.getEntityId() ); data.setInstance( data.getEntityInstanceForNotify() ); + postResolveInstance( data ); + return voidFuture(); } - else { - return reactiveResolveEntityInstance1( data ) - .thenAccept( v -> { - if ( data.getUniqueKeyAttributePath() != null ) { - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); - final EntityUniqueKey euk = new EntityUniqueKey( - concreteDescriptor.getEntityName(), - data.getUniqueKeyAttributePath(), - rowProcessingState.getEntityUniqueKey(), - data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], - session.getFactory() - ); - session.getPersistenceContextInternal().addEntity( euk, data.getInstance() ); - } - postResolveInstance( data ); - } ); - } - postResolveInstance( data ); - return voidFuture(); + + return reactiveResolveEntityInstance1( data ) + .thenAccept( v -> { + if ( data.getUniqueKeyAttributePath() != null ) { + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); + final EntityUniqueKey euk = new EntityUniqueKey( + concreteDescriptor.getEntityName(), + data.getUniqueKeyAttributePath(), + rowProcessingState.getEntityUniqueKey(), + data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], + session.getFactory() + ); + session.getPersistenceContextInternal().addEntity( euk, data.getInstance() ); + } + postResolveInstance( data ); + } ); + } + + private CompletionStage assembleId(RowProcessingState rowProcessingState) { + final DomainResultAssembler identifierAssembler = getIdentifierAssembler(); + assert identifierAssembler != null; + return identifierAssembler instanceof ReactiveDomainResultsAssembler reactiveAssembler + ? reactiveAssembler.reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + : completedFuture( identifierAssembler.assemble( rowProcessingState ) ); } + // We could move this method in ORM private void postResolveInstance(ReactiveEntityInitializerData data) { if ( data.getInstance() != null ) { upgradeLockMode( data ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java index 3f88bcffb..8de288c0e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java @@ -7,13 +7,15 @@ import java.lang.invoke.MethodHandles; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; +import org.hibernate.JDBCException; import org.hibernate.LockOptions; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -22,6 +24,7 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.pool.ReactiveConnection; +import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.util.impl.CompletionStages; @@ -63,6 +66,17 @@ public ReactiveDeferredResultSetAccess( this.sqlStatementLogger = executionContext.getSession().getJdbcServices().getSqlStatementLogger(); } + @Override + public JdbcServices getJdbcServices() { + return getFactory().getJdbcServices(); + } + + @Override + public JDBCException convertSqlException(SQLException e, String message) { + return getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper() + .convert( e, message ); + } + /** * Reactive version of {@link org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess#registerAfterLoadAction(ExecutionContext, LockOptions)} * calling {@link ReactiveSession#reactiveLock(String, Object, LockOptions)} @@ -139,20 +153,6 @@ private JdbcValuesMetadata convertToMetadata(ResultSet resultSet) { return (JdbcValuesMetadata) resultSet; } - @Override - public CompletionStage getReactiveMetadata() { - return getReactiveResultSet().thenApply( this::reactiveMetadata ); - } - - private ResultSetMetaData reactiveMetadata(ResultSet resultSet) { - try { - return resultSet.getMetaData(); - } - catch (SQLException e) { - throw new RuntimeException( e ); - } - } - private static int columnCount(ResultSet resultSet) { try { return resultSet.getMetaData().getColumnCount(); @@ -172,6 +172,11 @@ private CompletionStage executeQuery() { return completedFuture( logicalConnection ) .thenCompose( lg -> { LOG.tracef( "Executing query to retrieve ResultSet : %s", getFinalSql() ); + + Dialect dialect = executionContext.getSession().getJdbcServices().getDialect(); + // This must happen at the very last minute in order to process parameters + // added in org.hibernate.dialect.pagination.OffsetFetchLimitHandler.processSql + final String sql = Parameters.instance( dialect ).process( getFinalSql() ); Object[] parameters = PreparedStatementAdaptor.bind( super::bindParameters ); final SessionEventListenerManager eventListenerManager = executionContext @@ -181,7 +186,7 @@ private CompletionStage executeQuery() { eventListenerManager.jdbcExecuteStatementStart(); return connection() - .selectJdbc( getFinalSql(), parameters ) + .selectJdbc( sql, parameters ) .thenCompose( this::validateResultSet ) .whenComplete( (resultSet, throwable) -> { // FIXME: I don't know if this event makes sense for Vert.x diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java index 2412f3941..80d3217aa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java @@ -8,9 +8,11 @@ import java.lang.invoke.MethodHandles; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; +import java.sql.SQLException; import java.util.concurrent.CompletionStage; +import org.hibernate.JDBCException; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.reactive.logging.impl.Log; @@ -40,8 +42,13 @@ public ReactiveDirectResultSetAccess( } @Override - public SessionFactoryImplementor getFactory() { - return getPersistenceContext().getFactory(); + public JdbcServices getJdbcServices() { + return getFactory().getJdbcServices(); + } + + @Override + public JDBCException convertSqlException(SQLException e, String message) { + return getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( e, message ); } @Override @@ -73,11 +80,6 @@ public CompletionStage getReactiveResultSet() { return completedFuture( resultSet ); } - @Override - public CompletionStage getReactiveMetadata() { - return completedFuture( getMetaData() ); - } - @Override public CompletionStage getReactiveColumnCount() { return completedFuture( getColumnCount() ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java index 75a265ed5..ad9be5a6a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java @@ -10,9 +10,9 @@ import java.sql.SQLException; import java.util.concurrent.CompletionStage; +import org.hibernate.JDBCException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; @@ -27,14 +27,14 @@ */ public interface ReactiveResultSetAccess extends JdbcValuesMetadata { CompletionStage getReactiveResultSet(); - CompletionStage getReactiveMetadata(); CompletionStage getReactiveColumnCount(); CompletionStage resolveJdbcValueMetadata(); ResultSet getResultSet(); - SessionFactoryImplementor getFactory(); + JdbcServices getJdbcServices(); + void release(); /** @@ -51,43 +51,35 @@ default int getColumnCount() { return getResultSet().getMetaData().getColumnCount(); } catch (SQLException e) { - throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( - e, - "Unable to access ResultSet column count" - ); + throw convertSqlException( e, "Unable to access ResultSet column count" ); } } + JDBCException convertSqlException(SQLException e, String message); + default int resolveColumnPosition(String columnName) { try { return getResultSet().findColumn( columnName ); } catch (SQLException e) { - throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( - e, - "Unable to find column position by name" - ); + throw convertSqlException( e, "Unable to find column position by name" ); } } default String resolveColumnName(int position) { try { - return getFactory().getJdbcServices().getJdbcEnvironment() + return getJdbcServices().getJdbcEnvironment() .getDialect() .getColumnAliasExtractor() .extractColumnAlias( getResultSet().getMetaData(), position ); } catch (SQLException e) { - throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( - e, - "Unable to find column name by position" - ); + throw convertSqlException( e, "Unable to find column name by position" ); } } @Override default BasicType resolveType(int position, JavaType explicitJavaType, TypeConfiguration typeConfiguration) { - final JdbcServices jdbcServices = getFactory().getJdbcServices(); try { final ResultSetMetaData metaData = getResultSet().getMetaData(); final String columnTypeName = metaData.getColumnTypeName( position ); @@ -95,7 +87,7 @@ default BasicType resolveType(int position, JavaType explicitJavaType, final int scale = metaData.getScale( position ); final int precision = metaData.getPrecision( position ); final int displaySize = metaData.getColumnDisplaySize( position ); - final Dialect dialect = jdbcServices.getDialect(); + final Dialect dialect = getJdbcServices().getDialect(); final int length = dialect.resolveSqlTypeLength( columnTypeName, columnType, @@ -104,13 +96,7 @@ default BasicType resolveType(int position, JavaType explicitJavaType, displaySize ); final JdbcType resolvedJdbcType = dialect - .resolveSqlTypeDescriptor( - columnTypeName, - columnType, - length, - scale, - typeConfiguration.getJdbcTypeRegistry() - ); + .resolveSqlTypeDescriptor( columnTypeName, columnType, length, scale, typeConfiguration.getJdbcTypeRegistry() ); final JavaType javaType; final JdbcType jdbcType; // If there is an explicit JavaType, then prefer its recommended JDBC type @@ -145,26 +131,19 @@ public EnumType getEnumeratedType() { @Override public Dialect getDialect() { - return getFactory().getJdbcServices().getDialect(); + return getJdbcServices().getDialect(); } } ); } else { jdbcType = resolvedJdbcType; - javaType = jdbcType.getJdbcRecommendedJavaTypeMapping( - length, - scale, - typeConfiguration - ); + javaType = jdbcType.getJdbcRecommendedJavaTypeMapping( length, scale, typeConfiguration ); } return typeConfiguration.getBasicTypeRegistry().resolve( javaType, jdbcType ); } catch (SQLException e) { - throw jdbcServices.getSqlExceptionHelper().convert( - e, - "Unable to determine JDBC type code for ResultSet position " + position - ); + throw convertSqlException( e, "Unable to determine JDBC type code for ResultSet position " + position ); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java index 2e20e7781..14e227358 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java @@ -8,8 +8,11 @@ import java.util.function.Function; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.generator.Generator; import org.hibernate.generator.GeneratorCreationContext; +import org.hibernate.id.CompositeNestedGeneratedValueGenerator; import org.hibernate.id.Configurable; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.SelectGenerator; @@ -18,18 +21,23 @@ import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.id.enhanced.TableGenerator; import org.hibernate.id.enhanced.TableStructure; -import org.hibernate.mapping.GeneratorCreator; +import org.hibernate.mapping.GeneratorSettings; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SimpleValue; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.id.impl.EmulatedSequenceReactiveIdentifierGenerator; +import org.hibernate.reactive.id.impl.ReactiveCompositeNestedGeneratedValueGenerator; import org.hibernate.reactive.id.impl.ReactiveGeneratorWrapper; import org.hibernate.reactive.id.impl.ReactiveSequenceIdentifierGenerator; import org.hibernate.reactive.id.impl.TableReactiveIdentifierGenerator; import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.service.ServiceRegistry; import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.type.Type; import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; @@ -67,20 +75,22 @@ private static Generator buildIdGenerator( return existing; } else { - SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier(); - GeneratorCreator customIdGeneratorCreator = identifier.getCustomIdGeneratorCreator(); - identifier.setCustomIdGeneratorCreator( context -> { - Generator generator = customIdGeneratorCreator.createGenerator( context ); - return augmentWithReactiveGenerator( generator, context, creationContext ); - } ); - final Generator idgenerator = identifier - // returns the cached Generator if it was already created - .createGenerator( + final SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier(); + final Generator idgenerator = augmentWithReactiveGenerator( + identifier.createGenerator( creationContext.getDialect(), persistentClass.getRootClass(), persistentClass.getIdentifierProperty(), creationContext.getGeneratorSettings() - ); + ), + new IdGeneratorCreationContext( + persistentClass.getRootClass(), + persistentClass.getIdentifierProperty(), + creationContext.getGeneratorSettings(), + identifier, + creationContext + ), + creationContext ); creationContext.getGenerators().put( rootName, idgenerator ); return idgenerator; } @@ -90,8 +100,8 @@ public static Generator augmentWithReactiveGenerator( Generator generator, GeneratorCreationContext creationContext, RuntimeModelCreationContext runtimeModelCreationContext) { - if ( generator instanceof SequenceStyleGenerator ) { - final DatabaseStructure structure = ( (SequenceStyleGenerator) generator ).getDatabaseStructure(); + if ( generator instanceof SequenceStyleGenerator sequenceStyleGenerator) { + final DatabaseStructure structure = sequenceStyleGenerator.getDatabaseStructure(); if ( structure instanceof TableStructure ) { return initialize( (IdentifierGenerator) generator, new EmulatedSequenceReactiveIdentifierGenerator( (TableStructure) structure, runtimeModelCreationContext ), creationContext ); } @@ -100,16 +110,28 @@ public static Generator augmentWithReactiveGenerator( } throw LOG.unknownStructureType(); } - if ( generator instanceof TableGenerator ) { + if ( generator instanceof TableGenerator tableGenerator ) { return initialize( (IdentifierGenerator) generator, - new TableReactiveIdentifierGenerator( (TableGenerator) generator, runtimeModelCreationContext ), + new TableReactiveIdentifierGenerator( tableGenerator, runtimeModelCreationContext ), creationContext ); } if ( generator instanceof SelectGenerator ) { throw LOG.selectGeneratorIsNotSupportedInHibernateReactive(); } + if ( generator instanceof CompositeNestedGeneratedValueGenerator compositeNestedGeneratedValueGenerator ) { + final ReactiveCompositeNestedGeneratedValueGenerator reactiveCompositeNestedGeneratedValueGenerator = new ReactiveCompositeNestedGeneratedValueGenerator( + compositeNestedGeneratedValueGenerator, + creationContext, + runtimeModelCreationContext + ); + return initialize( + (IdentifierGenerator) generator, + reactiveCompositeNestedGeneratedValueGenerator, + creationContext + ); + } //nothing to do return generator; } @@ -121,4 +143,57 @@ private static Generator initialize( ( (Configurable) reactiveIdGenerator ).initialize( creationContext.getSqlStringGenerationContext() ); return new ReactiveGeneratorWrapper( reactiveIdGenerator, idGenerator ); } + + private record IdGeneratorCreationContext( + RootClass rootClass, + Property property, + GeneratorSettings defaults, + SimpleValue identifier, + RuntimeModelCreationContext buildingContext) implements GeneratorCreationContext { + + @Override + public Database getDatabase() { + return buildingContext.getBootModel().getDatabase(); + } + + @Override + public ServiceRegistry getServiceRegistry() { + return buildingContext.getBootstrapContext().getServiceRegistry(); + } + + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return defaults.getSqlStringGenerationContext(); + } + + @Override + public String getDefaultCatalog() { + return defaults.getDefaultCatalog(); + } + + @Override + public String getDefaultSchema() { + return defaults.getDefaultSchema(); + } + + @Override + public RootClass getRootClass() { + return rootClass; + } + + @Override + public PersistentClass getPersistentClass() { + return rootClass; + } + + @Override + public Property getProperty() { + return property; + } + + @Override + public Type getType() { + return identifier.getType(); + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java index 81af936ba..ab84952ef 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java @@ -5,26 +5,27 @@ */ package org.hibernate.reactive.type.descriptor.jdbc; -import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JsonHelper; -import org.hibernate.type.descriptor.jdbc.JsonJdbcType; +import org.hibernate.type.descriptor.jdbc.spi.JsonGeneratingVisitor; +import org.hibernate.type.format.StringJsonDocumentWriter; import io.vertx.core.json.JsonArray; -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; /** * @see org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType @@ -65,21 +66,24 @@ protected X fromString(String string, JavaType javaType, WrapperOptions o if ( string == null ) { return null; } - - return JsonHelper.arrayFromString( javaType, getElementJdbcType(), string, options ); + if ( ((BasicPluralJavaType) javaType).getElementJavaType() instanceof UnknownBasicJavaType ) { + return options.getJsonFormatMapper().fromString( string, javaType, options ); + } + else { + return JsonHelper.arrayFromString( javaType, this.getElementJdbcType(), string, options ); + } } protected String toString(X value, JavaType javaType, WrapperOptions options) { - final JdbcType elementJdbcType = getElementJdbcType(); - final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); - if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { - final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); - return JsonHelper.arrayToString( embeddableMappingType, domainObjects, options ); + final JavaType elementJavaType = ( (BasicPluralJavaType) javaType ).getElementJavaType(); + if ( elementJavaType instanceof UnknownBasicJavaType ) { + return options.getJsonFormatMapper().toString( value, javaType, options); } else { - assert !( elementJdbcType instanceof AggregateJdbcType ); - final JavaType elementJavaType = ( (BasicPluralJavaType) javaType ).getElementJavaType(); - return JsonHelper.arrayToString( elementJavaType, elementJdbcType, domainObjects, options ); + final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); + final StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); + JsonGeneratingVisitor.INSTANCE.visitArray( elementJavaType, getElementJdbcType(), domainObjects, options, writer ); + return writer.getJson(); } } @@ -121,12 +125,8 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o } private X getObject(Object array, WrapperOptions options) throws SQLException { - if ( array == null ) { - return null; - } - - return ( (ReactiveJsonArrayJdbcType) getJdbcType() ) - .fromString( ( (JsonArray) array ).encode(), getJavaType(), options ); + final String json = array == null ? null : ( (JsonArray) array ).encode(); + return ( (ReactiveJsonArrayJdbcType) getJdbcType() ).fromString( json, getJavaType(), options ); } }; } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java index 07a79379b..508625bcb 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; @@ -29,6 +28,7 @@ import org.hibernate.reactive.provider.service.ReactiveGenerationTarget; import org.hibernate.reactive.stage.Stage; import org.hibernate.reactive.testing.SessionFactoryManager; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.tool.schema.spi.SchemaManagementTool; import org.junit.jupiter.api.AfterAll; @@ -41,7 +41,6 @@ import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.smallrye.mutiny.Uni; -import io.vertx.core.Promise; import io.vertx.core.VertxOptions; import io.vertx.junit5.RunTestOnContext; import io.vertx.junit5.Timeout; @@ -75,7 +74,7 @@ public abstract class BaseReactiveTest { * Configure Vertx JUnit5 test context */ @RegisterExtension - static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions() ); + static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions(), false ); private static VertxOptions vertxOptions() { Metrics.addRegistry( new SimpleMeterRegistry() ); @@ -217,33 +216,19 @@ protected CompletionStage setupSessionFactory(Configuration configuration) * @return a {@link CompletionStage} void that succeeds when the factory is ready. */ protected CompletionStage setupSessionFactory(Supplier confSupplier) { - CompletableFuture future = new CompletableFuture<>(); - testOnContext.vertx() + return testOnContext.vertx() .executeBlocking( // schema generation is a blocking operation and so it causes an // exception when run on the Vert.x event loop. So call it using // Vertx.executeBlocking() - promise -> startFactoryManager( promise, confSupplier ), - event -> { - if ( event.succeeded() ) { - future.complete( null ); - } - else { - future.completeExceptionally( event.cause() ); - } - } - ); - return future; - } - - private void startFactoryManager(Promise p, Supplier confSupplier) { - try { - factoryManager.start( () -> createHibernateSessionFactory( confSupplier.get() ) ); - p.complete(); - } - catch (Throwable e) { - p.fail( e ); - } + () -> startFactoryManager( confSupplier ), + false + ).toCompletionStage().thenCompose( CompletionStages::voidFuture ); + } + + private Object startFactoryManager(Supplier confSupplier) { + factoryManager.start( () -> createHibernateSessionFactory( confSupplier.get() ) ); + return null; } private SessionFactory createHibernateSessionFactory(Configuration configuration) { diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java new file mode 100644 index 000000000..0192bbbf7 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java @@ -0,0 +1,107 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.SequenceGenerator; + +import java.util.Collection; +import java.util.List; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 10, timeUnit = MINUTES) + +public class CompositeIdWithGeneratedValuesTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( Product.class ); + } + + @Test + public void testCompositeIdWithGeneratedValues(VertxTestContext context) { + final Product product = new Product( "name", 1150L ); + test( + context, + getMutinySessionFactory().withTransaction( session -> session.persist( product ) ) + .invoke( () -> assertThat( product.id ).isNotNull() ) + .chain( () -> getMutinySessionFactory().withTransaction( session -> session.find( + Product.class, + new ProductId( product.version, product.id ) + ) ) ) + .invoke( found -> { + assertThat( found ).hasFieldOrPropertyWithValue( "id", product.id ); + assertThat( found ).hasFieldOrPropertyWithValue( "version", product.version ); + assertThat( found ).hasFieldOrPropertyWithValue( "name", product.name ); + } ) + ); + } + + @Entity + @IdClass(ProductId.class) + public static class Product { + @Id + private Long version; + + @Id + @GeneratedValue + @SequenceGenerator(name = "product_seq", sequenceName = "product_seq") + private Long id; + + private String name; + + public Product() { + } + + public Product(String name, Long version) { + this.name = name; + this.version = version; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "(" + id + "," + version + "):" + name; + } + } + + public static class ProductId { + private Long version; + private Long id; + + private ProductId() { + } + + public ProductId(Long version, Long id) { + this.version = version; + this.id = id; + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java new file mode 100644 index 000000000..f5cbf5fd9 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java @@ -0,0 +1,131 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EmbeddedIdWithOneToOneTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( FooEntity.class, BarEntity.class ); + } + + @Test + public void test(VertxTestContext context) { + BarEntity barEntity = new BarEntity( "1" ); + FooId fooId = new FooId( barEntity ); + FooEntity entity = new FooEntity( fooId ); + + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.persistAll( barEntity, entity ) ) + .chain( () -> getMutinySessionFactory() + .withTransaction( s -> s.find( FooEntity.class, fooId ) ) + ) + .invoke( result -> { + assertThat( result.getId() ).isEqualTo( fooId ); + assertThat( result.getId().getIdEntity() ).isEqualTo( fooId.getIdEntity() ); + assertThat( result.getId().getIdEntity().getId() ).isEqualTo( fooId.getIdEntity().getId() ); + } ) + ); + } + + @Entity(name = "bar") + public static class BarEntity { + + @Id + private String id; + + public BarEntity() { + } + + public BarEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + @Entity(name = "foo") + public static class FooEntity { + + @EmbeddedId + private FooId id; + + public FooEntity() { + } + + public FooEntity(FooId id) { + this.id = id; + } + + public FooId getId() { + return id; + } + + public void setId(FooId id) { + this.id = id; + } + } + + @Embeddable + public static class FooId { + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "id", nullable = false) + private BarEntity barEntity; + + public FooId() { + } + + public FooId(BarEntity barEntity) { + this.barEntity = barEntity; + } + + public BarEntity getIdEntity() { + return barEntity; + } + + public void setIdEntity(BarEntity barEntity) { + this.barEntity = barEntity; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + FooId fooId = (FooId) o; + return Objects.equals( barEntity, fooId.barEntity ); + } + + @Override + public int hashCode() { + return Objects.hashCode( barEntity ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java index e48d3ec69..77331fe2b 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java @@ -5,6 +5,7 @@ */ package org.hibernate.reactive; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -17,32 +18,41 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import static jakarta.persistence.CascadeType.PERSIST; +import static jakarta.persistence.FetchType.LAZY; import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; @Timeout(value = 10, timeUnit = MINUTES) - public class HQLQueryTest extends BaseReactiveTest { Flour spelt = new Flour( 1, "Spelt", "An ancient grain, is a hexaploid species of wheat.", "Wheat flour" ); Flour rye = new Flour( 2, "Rye", "Used to bake the traditional sourdough breads of Germany.", "Wheat flour" ); Flour almond = new Flour( 3, "Almond", "made from ground almonds.", "Gluten free" ); + Author miller = new Author( "Madeline Miller"); + Author camilleri = new Author( "Andrea Camilleri"); + Book circe = new Book( "9780316556347", "Circe", miller ); + Book shapeOfWater = new Book( "0-330-49286-1 ", "The Shape of Water", camilleri ); + Book spider = new Book( "978-0-14-311203-7", "The Patience of the Spider", camilleri ); + @Override protected Collection> annotatedEntities() { - return List.of( Flour.class ); + return List.of( Flour.class, Book.class, Author.class ); } @BeforeEach public void populateDb(VertxTestContext context) { - test( context, getMutinySessionFactory() - .withTransaction( (session, transaction) -> session.persistAll( spelt, rye, almond ) ) ); + test( context, getMutinySessionFactory().withTransaction( session -> session + .persistAll( spelt, rye, almond, miller, camilleri, circe, shapeOfWater, spider ) ) + ); } @Test @@ -69,7 +79,7 @@ public void testAutoFlushOnResultList(VertxTestContext context) { public void testSelectScalarString(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "SELECT 'Prova' FROM Flour WHERE id = " + rye.getId(), String.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( found -> assertEquals( "Prova", found ) ) ); } @@ -78,7 +88,7 @@ public void testSelectScalarString(VertxTestContext context) { public void testSelectScalarCount(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "SELECT count(*) FROM Flour", Long.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( found -> assertEquals( 3L, found ) ) ); } @@ -86,14 +96,17 @@ public void testSelectScalarCount(VertxTestContext context) { @Test public void testSelectWithMultipleScalarValues(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { - Stage.SelectionQuery qr = s.createSelectionQuery( "SELECT 'Prova', f.id FROM Flour f WHERE f.id = " + rye.getId(), Object[].class ); - assertNotNull( qr ); - return qr.getSingleResult(); - } ).thenAccept( found -> { - assertTrue( found instanceof Object[] ); - assertEquals( "Prova", ( (Object[]) found )[0] ); - assertEquals( rye.getId(), ( (Object[]) found )[1] ); - } ) + Stage.SelectionQuery qr = s.createSelectionQuery( + "SELECT 'Prova', f.id FROM Flour f WHERE f.id = " + rye.getId(), + Object[].class + ); + assertThat( qr ).isNotNull(); + return qr.getSingleResult(); + } ).thenAccept( found -> { + assertThat( found ).isInstanceOf( Object[].class ); + assertEquals( "Prova", ( (Object[]) found )[0] ); + assertEquals( rye.getId(), ( (Object[]) found )[1] ); + } ) ); } @@ -101,7 +114,7 @@ public void testSelectWithMultipleScalarValues(VertxTestContext context) { public void testSingleResultQueryOnId(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "FROM Flour WHERE id = 1", Flour.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( flour -> assertEquals( spelt, flour ) ) ); @@ -111,7 +124,7 @@ public void testSingleResultQueryOnId(VertxTestContext context) { public void testSingleResultQueryOnName(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "FROM Flour WHERE name = 'Almond'", Flour.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( flour -> assertEquals( almond, flour ) ) ); @@ -122,13 +135,28 @@ public void testFromQuery(VertxTestContext context) { test( context, getSessionFactory() .withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "FROM Flour ORDER BY name", Flour.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getResultList(); } ) .thenAccept( results -> assertThat( results ).containsExactly( almond, rye, spelt ) ) ); } + @Test + public void testSelectNewConstructor(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( session -> session + .createQuery( "SELECT NEW Book(b.title, b.author) FROM Book b ORDER BY b.title DESC", Book.class ) + .getResultList() + ) + .invoke( books -> assertThat( books ).containsExactly( + new Book( shapeOfWater.title, camilleri ), + new Book( spider.title, camilleri ), + new Book( circe.title, miller ) + ) ) + ); + } + @Entity(name = "Flour") @Table(name = "Flour") public static class Flour { @@ -204,4 +232,122 @@ public int hashCode() { return Objects.hash( name, description, type ); } } + + @Entity(name = "Book") + @Table(name = "Book_HQL") + public static class Book { + @Id + @GeneratedValue + private Integer id; + + private String isbn; + + private String title; + + @ManyToOne(fetch = LAZY) + private Author author; + + public Book() { + } + + public Book(String title, Author author) { + this.title = title; + this.author = author; + } + + public Book(String isbn, String title, Author author) { + this.isbn = isbn; + this.title = title; + this.author = author; + author.books.add( this ); + } + + public Integer getId() { + return id; + } + + public String getIsbn() { + return isbn; + } + + public String getTitle() { + return title; + } + + public Author getAuthor() { + return author; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book) o; + return Objects.equals( isbn, book.isbn ) && Objects.equals( + title, + book.title + ) && Objects.equals( author, book.author ); + } + + @Override + public int hashCode() { + return Objects.hash( isbn, title, author ); + } + + @Override + public String toString() { + return id + ":" + isbn + ":" + title + ":" + author; + } + } + + @Entity(name = "Author") + @Table(name = "Author_HQL") + public static class Author { + @Id @GeneratedValue + private Integer id; + + private String name; + + @OneToMany(mappedBy = "author", cascade = PERSIST) + private List books = new ArrayList<>(); + + public Author() { + } + + public Author(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public List getBooks() { + return books; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Author author = (Author) o; + return Objects.equals( name, author.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + + @Override + public String toString() { + return id + ":" + name; + } + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java new file mode 100644 index 000000000..f1c08d4ce --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java @@ -0,0 +1,225 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletionStage; + +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.SoftDelete; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Tuple; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.common.Identifier.id; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +@Timeout(value = 10, timeUnit = MINUTES) +public class ImplicitSoftDeleteTests extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( ImplicitEntity.class ); + } + + @Override + protected CompletionStage cleanDb() { + return voidFuture(); + } + + @BeforeEach + void createTestData(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createNativeQuery( "delete from implicit_entities" ).executeUpdate() ) + .call( () -> getMutinySessionFactory().withTransaction( s -> s + .persistAll( new ImplicitEntity( 1, "first" ), new ImplicitEntity( 2, "second" ), new ImplicitEntity( 3, "third" ) ) + ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity first = s.getReference( ImplicitEntity.class, 1 ); + return s.remove( first ).call( s::flush ); + } ) ) + .call( () -> getMutinySessionFactory() + .withTransaction( s -> s.createNativeQuery( "select * from implicit_entities e order by id", Tuple.class ).getResultList() ) + .invoke( tuples -> assertThat( tuples ).hasSize( 3 ) ) + ) + ); + } + + @Test + void testSelectionQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createQuery( "from ImplicitEntity", ImplicitEntity.class ).getResultList() ) + .invoke( list -> assertThat( list ).hasSize( 2 ) ) + ); + } + + @Test + void testLoading(VertxTestContext context) { + test( + context, getMutinySessionFactory() + // Load + .withTransaction( s -> s + .find( ImplicitEntity.class, 1 ).invoke( entity -> assertThat( entity ).isNull() ) + .call( () -> s.find( ImplicitEntity.class, 2 ).invoke( entity -> assertThat( entity ).isNotNull() ) ) + .call( () -> s.find( ImplicitEntity.class, 3 ).invoke( entity -> assertThat( entity ).isNotNull() ) ) + ) + // Proxy + // We deleted the entity, so we expect an ObjectNotFoundException + .chain( () -> assertThrown( ObjectNotFoundException.class, getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 1 ); + return s.fetch( reference ).map( ImplicitEntity::getName ); + } ) ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 2 ); + return s.fetch( reference ).map( ImplicitEntity::getName ); + } ) ) + .invoke( name -> assertThat( name ).isEqualTo( "second" ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 3 ); + return s.fetch( reference ).map( ImplicitEntity::getName ); + } ) ) + .invoke( name -> assertThat( name ).isEqualTo( "third" ) ) + ); + } + + @Test + void testMultiLoading(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( ImplicitEntity.class, 1, 2, 3 ) ) + .invoke( list -> assertThat( list ) + .containsExactly( null, new ImplicitEntity( null, "second" ), new ImplicitEntity( null, "third" ) ) ) + ); + } + + @Test + void testNaturalIdLoading(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( ImplicitEntity.class, id( "name", "first" ) ) ) + .invoke( entity -> assertThat( entity ).isNull() ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s.find( ImplicitEntity.class, id( "name", "second" ) ) ) ) + .invoke( entity -> assertThat( entity ).extracting( ImplicitEntity::getId ).isEqualTo( 2 ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s.find( ImplicitEntity.class, id( "name", "third" ) ) ) ) + .invoke( entity -> assertThat( entity ).extracting( ImplicitEntity::getId ).isEqualTo( 3 ) ) + ); + } + + @Test + void testDeletion(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 2 ); + return s.remove( reference ).call( s::flush ) + .call( () -> s.createSelectionQuery( "from ImplicitEntity", ImplicitEntity.class ).getResultList() + // #1 was "deleted" up front and we just "deleted" #2... only #3 should be active + .invoke( list -> { + assertThat( list ).extracting( ImplicitEntity::getId ).containsExactly( 3 ); + assertThat( list ).extracting( ImplicitEntity::getName ).containsExactly( "third" ); + } ) + ); + } ) + ); + } + + @Test + void testFullUpdateMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "update ImplicitEntity set name = null" ).executeUpdate() ) + .invoke( affected -> assertThat( affected ).isEqualTo( 2 ) ) + ); + } + + @Test + void testRestrictedUpdateMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "update ImplicitEntity set name = null where name = 'second'" ).executeUpdate() ) + .invoke( affected -> assertThat( affected ).isEqualTo( 1 ) ) + ); + } + + @Test + void testFullDeleteMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "delete ImplicitEntity" ).executeUpdate() ) + // only #2 and #3 + .invoke( affected -> assertThat( affected ).isEqualTo( 2 ) ) + ); + } + + @Test + void testRestrictedDeleteMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "delete ImplicitEntity where name = 'second'" ).executeUpdate() ) + // only #2 + .invoke( affected -> assertThat( affected ).isEqualTo( 1 ) ) + ); + } + + + @Entity(name = "ImplicitEntity") + @Table(name = "implicit_entities") + @SoftDelete + public static class ImplicitEntity { + @Id + private Integer id; + @NaturalId + private String name; + + public ImplicitEntity() { + } + + public ImplicitEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object object) { + if ( object == null || getClass() != object.getClass() ) { + return false; + } + ImplicitEntity that = (ImplicitEntity) object; + return Objects.equals( name, that.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java index ab64e1925..432535b51 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java @@ -5,6 +5,8 @@ */ package org.hibernate.reactive; +import org.hibernate.reactive.annotations.DisabledFor; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -28,6 +30,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL; @Timeout(value = 10, timeUnit = MINUTES) @@ -185,6 +188,30 @@ public void testQueryUpdateWithParameters(VertxTestContext context) { ); } + @Test + @DisabledFor(value = POSTGRESQL, reason = "https://github.com/hibernate/hibernate-reactive/issues/2412") + public void testHqlInsertWithTransaction(VertxTestContext context) { + final Integer id = 1; + final String title = "Spell Book: A Comprehensive Guide to Magic Spells and Incantations"; + test( context, getMutinySessionFactory().withTransaction( session -> session + .createMutationQuery( "insert into SpellBook (id, title, forbidden) values (:id, :title, :forbidden)" ) + .setParameter( "id", id ) + .setParameter( "title", title ) + .setParameter( "forbidden", true ) + .executeUpdate() ) + .call( () -> getMutinySessionFactory().withTransaction( session -> session + .createSelectionQuery( "from SpellBook g where g.id = :id ", SpellBook.class ) + .setParameter( "id", id ) + .getSingleResult() + .invoke( spellBook -> { + assertThat( spellBook.getTitle() ).isEqualTo( title ); + assertThat( spellBook.forbidden ).isTrue(); + } + ) + ) ) + ); + } + @Entity(name="SpellBook") @Table(name = "SpellBookJS") @DiscriminatorValue("S") diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java index 3d22d0e71..73233b5e9 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java @@ -146,14 +146,14 @@ private String getIsbn() { private Author author; public Book(String isbn, String title, Author author) { - super( "Book", 1, singleton( "isbn" ), null ); + super( new EntityRelatedState( "Book", singleton( "isbn" ) ), 1, null ); this.title = title; this.isbn = isbn; this.author = author; } public Book() { - super( "Book", 1, singleton( "isbn" ), null ); + super( new EntityRelatedState( "Book", singleton( "isbn" ) ), 1, null ); } @Override diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LockOnLoadTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LockOnLoadTest.java new file mode 100644 index 000000000..fe0c7e20e --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LockOnLoadTest.java @@ -0,0 +1,70 @@ +/* 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 org.hibernate.LockMode; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@Timeout(value = 10, timeUnit = MINUTES) +public class LockOnLoadTest extends BaseReactiveTest{ + @Override + protected Collection> annotatedEntities() { + return List.of( Person.class ); + } + + @Test + public void testLockOnLoad(VertxTestContext context) { + Person person = new Person( 1L, "Davide" ); + + test( context, getMutinySessionFactory() + .withTransaction( session -> session.persist( person ) ) + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( Person.class, person.getId() ) + // the issue occurred when trying to find the same entity but upgrading the lock mode + .chain( p -> session.find( Person.class, person.getId(), LockMode.PESSIMISTIC_WRITE ) ) + .invoke( p -> assertThat( p ).isNotNull() ) + ) ) + ); + } + + @Entity(name = "Person") + @Table(name = "LockOnLoadTest.Person") + public static class Person { + @Id + private Long id; + + private String name; + + public Person() { + } + + public Person(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java new file mode 100644 index 000000000..314c69b48 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java @@ -0,0 +1,189 @@ +/* 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.hibernate.annotations.EmbeddedColumnNaming; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jakarta.persistence.OneToMany; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ManyToOneMapsIdAndEmbeddedIdTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( Link.class, GPSPoint.class, NetPoint.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + NetPointKey startKey = new NetPointKey( 1, NetPointType.STOP_POINT ); + NetPointKey endKey = new NetPointKey( 2, NetPointType.STOP_POINT ); + LinkKey linkKey = new LinkKey( startKey, endKey, "123" ); + + final NetPoint start = new NetPoint(); + fillWithBogusValues( start ); + start.key = startKey; + + final NetPoint end = new NetPoint(); + fillWithBogusValues( end ); + end.key = endKey; + + final Link link = new Link(); + link.key = linkKey; + link.addPoint( new GPSPoint( new GPSPointKey( linkKey, 0 ), link, 1, 1, 1 ) ); + link.addPoint( new GPSPoint( new GPSPointKey( linkKey, 1 ), link, 1, 1, 1 ) ); + link.addPoint( new GPSPoint( new GPSPointKey( linkKey, 2 ), link, 1, 1, 1 ) ); + + test( + context, getMutinySessionFactory().withTransaction( session -> session + .persistAll( start, end, link ) ) + ); + } + + @Test + public void test(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( session -> session + .createQuery( "from Link", Link.class ).getResultList() ) + .invoke( links -> assertThat( links ).hasSize( 1 ) ) + ); + } + + void fillWithBogusValues(NetPoint start) { + start.gpsLatitude = 1; + start.gpsLongitude = 1; + start.operatingDepartmentId = "123"; + start.operatingDepartmentShortName = "123 - 123"; + } + + @Entity(name = "GPSPoint") + public static class GPSPoint { + + @EmbeddedId + public GPSPointKey key; + + @ManyToOne + @MapsId("link") + public Link link; + + @Column(nullable = false) + public Integer latitude; + + @Column(nullable = false) + public Integer longitude; + + @Column(nullable = false) + public Integer distance; + + public GPSPoint() { + } + + public GPSPoint(GPSPointKey key, Link link, Integer latitude, Integer longitude, Integer distance) { + this.key = key; + this.link = link; + this.latitude = latitude; + this.longitude = longitude; + this.distance = distance; + } + } + + @Embeddable + public record GPSPointKey( + @Embedded + LinkKey link, + + Integer position + ) { + + } + + @Entity(name = "Link") + public static class Link { + + @EmbeddedId + public LinkKey key; + + @OneToMany(mappedBy = "link", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + public List gpsPoints = new ArrayList<>(); + + public void addPoint(GPSPoint point) { + gpsPoints.add( point ); + point.link = this; + } + } + + @Embeddable + public record LinkKey( + @Embedded + @EmbeddedColumnNaming("start_%s") + NetPointKey start, + + @Embedded + @EmbeddedColumnNaming("end_%s") + NetPointKey end, + + String operatingDepartmentId + ) { + + } + + @Embeddable + public record NetPointKey( + Integer id, + + @Enumerated(EnumType.ORDINAL) + NetPointType type + ) { + + } + + @Entity(name = "NetPoint") + public static class NetPoint { + + @EmbeddedId + public NetPointKey key; + + @Column(nullable = false) + public String operatingDepartmentId; + + @Column(nullable = false) + public String operatingDepartmentShortName; + + @Column(nullable = false) + public Integer gpsLatitude; + + @Column(nullable = false) + public Integer gpsLongitude; + } + + public enum NetPointType { + @Deprecated UNSPECIFIED, + STOP_POINT, + DEPOT_POINT, + BEACON, + SECTION_POINT + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java index bf46f835a..31bcf2380 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java @@ -27,7 +27,13 @@ import jakarta.persistence.metamodel.EntityType; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; @Timeout(value = 10, timeUnit = MINUTES) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java index c65c79ee4..1011c0b19 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java @@ -43,6 +43,7 @@ import static jakarta.persistence.FetchType.LAZY; import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; import static org.hibernate.reactive.QueryTest.Author.AUTHOR_TABLE; import static org.hibernate.reactive.QueryTest.Author.HQL_NAMED_QUERY; import static org.hibernate.reactive.QueryTest.Author.SQL_NAMED_QUERY; @@ -395,6 +396,42 @@ public void testNativeEntityQueryWithParam(VertxTestContext context) { ); } + // https://github.com/hibernate/hibernate-reactive/issues/2314 + @Test + public void testNativeEntityQueryWithLimit(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + Book book1 = new Book( "1-85723-235-6", "Feersum Endjinn", author1 ); + Book book2 = new Book( "0-380-97346-4", "Cryptonomicon", author2 ); + Book book3 = new Book( "0-553-08853-X", "Snow Crash", author2 ); + author1.books.add( book1 ); + author2.books.add( book2 ); + author2.books.add( book3 ); + + test( + context, + openSession() + .thenCompose( session -> session.persist( author1, author2 ) + .thenCompose( v -> session.flush() ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( session -> session.createNativeQuery( + "select * from " + BOOK_TABLE + " order by isbn", + Book.class + ) + .setMaxResults( 2 ) + .getResultList() ) + .thenAccept( books -> { + assertThat( books ) + .extracting( b -> b.id, b -> b.title, b -> b.isbn ) + .containsExactly( + tuple( book2.id, book2.title, book2.isbn ), + tuple( book3.id, book3.title, book3.isbn ) + ); + } ) + ); + } + @Test public void testNativeEntityQueryWithNamedParam(VertxTestContext context) { Author author1 = new Author( "Iain M. Banks" ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TenantDependentPool.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TenantDependentPool.java index 70b3a1ba6..4dd18b30e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TenantDependentPool.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/TenantDependentPool.java @@ -7,16 +7,12 @@ import java.net.URI; import java.util.Map; -import java.util.function.Function; import java.util.stream.Collectors; import org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant; import org.hibernate.reactive.pool.impl.DefaultSqlClientPool; -import io.vertx.core.AsyncResult; -import io.vertx.core.Context; import io.vertx.core.Future; -import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.sqlclient.Pool; import io.vertx.sqlclient.PoolOptions; @@ -58,8 +54,18 @@ protected Pool createPool(URI uri, SqlConnectOptions connectOptions, PoolOptions return pools; } - private Pool createPool(URI uri, SqlConnectOptions connectOptions, PoolOptions poolOptions, Vertx vertx, Tenant tenant) { - return super.createPool( changeDbName( uri, tenant ), changeDbName( connectOptions, tenant ), poolOptions, vertx ); + private Pool createPool( + URI uri, + SqlConnectOptions connectOptions, + PoolOptions poolOptions, + Vertx vertx, + Tenant tenant) { + return super.createPool( + changeDbName( uri, tenant ), + changeDbName( connectOptions, tenant ), + poolOptions, + vertx + ); } /** @@ -100,11 +106,6 @@ public Pool getTenantPool(Tenant tenantId) { return poolMap.get( tenantId ); } - @Override - public void getConnection(Handler> handler) { - poolMap.get( defaultTenantId ).getConnection( handler ); - } - @Override public Future getConnection() { return poolMap.get( defaultTenantId ).getConnection(); @@ -125,21 +126,6 @@ public PreparedQuery> preparedQuery(String sql, PrepareOptions optio return poolMap.get( defaultTenantId ).preparedQuery( sql, options ); } - @Override - public void close(Handler> handler) { - poolMap.forEach( (tenant, pool) -> pool.close( handler ) ); - } - - @Override - public Pool connectHandler(Handler handler) { - return poolMap.get( defaultTenantId ).connectHandler( handler ); - } - - @Override - public Pool connectionProvider(Function> provider) { - return poolMap.get( defaultTenantId ).connectionProvider( provider ); - } - @Override public int size() { return poolMap.get( defaultTenantId ).size(); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java index 8dcc07a6a..8cf8e6931 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java @@ -144,14 +144,10 @@ private void assertQueries() { } private boolean hasMergeOperator() { - switch ( dbType() ) { - case SQLSERVER: - case ORACLE: - case POSTGRESQL: - return true; - default: - return false; - } + return switch ( dbType() ) { + case SQLSERVER, ORACLE, POSTGRESQL, DB2 -> true; + default -> false; + }; } @Entity(name = "Record") diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java index e1a0928d1..172ace2bc 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java @@ -12,7 +12,7 @@ import org.testcontainers.containers.CockroachContainer; import org.testcontainers.containers.Container; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; class CockroachDBDatabase extends PostgreSQLDatabase { @@ -25,7 +25,7 @@ class CockroachDBDatabase extends PostgreSQLDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final CockroachContainer cockroachDb = new CockroachContainer( imageName( "cockroachdb/cockroach", "v24.3.13" ) ) + public static final CockroachContainer cockroachDb = new CockroachContainer( fromDockerfile( "cockroachdb" ) ) // Username, password and database are not supported by test container at the moment // Testcontainers will use a database named 'postgres' and the 'root' user .withReuse( true ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java index dfeeaf15c..c2b60e3d6 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java @@ -28,7 +28,7 @@ import org.testcontainers.containers.Db2Container; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; class DB2Database implements TestableDatabase { @@ -87,7 +87,7 @@ class DB2Database implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - static final Db2Container db2 = new Db2Container( imageName( "icr.io", "db2_community/db2", "12.1.0.0" ) ) + static final Db2Container db2 = new Db2Container( fromDockerfile( "db2" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java index 1b2f3f6a7..e8f40f34d 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java @@ -5,10 +5,17 @@ */ package org.hibernate.reactive.containers; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + import org.testcontainers.utility.DockerImageName; + /** - * A utility class with methods to generate {@link DockerImageName} for testcontainers. + * A utility class with methods to generate a {@link DockerImageName} for Testcontainers. *

* Testcontainers might not work if the image required is available in multiple different registries (for example when * using podman instead of docker). @@ -17,10 +24,28 @@ */ public final class DockerImage { - public static final String DEFAULT_REGISTRY = "docker.io"; + /** + * The absolute path of the project root that we have set in Gradle. + */ + private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" ); + + /** + * The path to the directory containing all the Dockerfile files + */ + private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" ); - public static DockerImageName imageName(String image, String version) { - return imageName( DEFAULT_REGISTRY, image, version ); + /** + * Extract the image name and version from the first FROM instruction in the Dockerfile. + * Note that everything else is ignored. + */ + public static DockerImageName fromDockerfile(String databaseName) { + try { + final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() ); + return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } } public static DockerImageName imageName(String registry, String image, String version) { @@ -28,4 +53,74 @@ public static DockerImageName imageName(String registry, String image, String ve .parse( registry + "/" + image + ":" + version ) .asCompatibleSubstituteFor( image ); } + + private static class ImageInformation { + private final String registry; + private final String image; + private final String version; + + public ImageInformation(String fullImageInfo) { + // FullImageInfo pattern: /: + // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13" + final int registryEndPos = fullImageInfo.indexOf( '/' ); + final int imageEndPos = fullImageInfo.lastIndexOf( ':' ); + this.registry = fullImageInfo.substring( 0, registryEndPos ); + this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos ); + this.version = fullImageInfo.substring( imageEndPos + 1 ); + } + + public String getRegistry() { + return registry; + } + + public String getImage() { + return image; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return registry + "/" + image + ":" + version; + } + } + + private static Path dockerFilePath(String database) { + // Get project root from system property set by Gradle, with fallback + return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" ); + } + + private static ImageInformation readFromInstruction(String database) throws IOException { + return readFromInstruction( dockerFilePath( database ) ); + } + + /** + * Read a Dockerfile and extract the first FROM instruction. + * + * @param dockerfilePath path to the Dockerfile + * @return the first FROM instruction found, or empty if none found + * @throws IOException if the file cannot be read + */ + private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException { + if ( !Files.exists( dockerfilePath ) ) { + throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath ); + } + + List lines = Files.readAllLines( dockerfilePath ); + for ( String line : lines ) { + // Skip comments and empty lines + String trimmedLine = line.trim(); + if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) { + continue; + } + + if ( trimmedLine.startsWith( "FROM " ) ) { + return new ImageInformation( trimmedLine.substring( "FROM ".length() ) ); + } + } + + throw new IOException( " Missing FROM instruction in " + dockerfilePath ); + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java index aeb1b8fb0..eaab2e8fb 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java @@ -28,7 +28,7 @@ import org.testcontainers.containers.MSSQLServerContainer; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; /** * The JDBC driver syntax is: @@ -96,7 +96,7 @@ class MSSQLServerDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MSSQLServerContainer mssqlserver = new MSSQLServerContainer<>( imageName( "mcr.microsoft.com", "mssql/server", "2022-latest" ) ) + public static final MSSQLServerContainer mssqlserver = new MSSQLServerContainer<>( fromDockerfile( "sqlserver" ) ) .acceptLicense() .withPassword( PASSWORD ) .withReuse( true ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java index 16a5f97ff..14d60c264 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java @@ -13,7 +13,7 @@ import org.testcontainers.containers.MariaDBContainer; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; class MariaDatabase extends MySQLDatabase { @@ -36,7 +36,7 @@ class MariaDatabase extends MySQLDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MariaDBContainer maria = new MariaDBContainer<>( imageName( "mariadb", "11.7.2" ) ) + public static final MariaDBContainer maria = new MariaDBContainer<>( fromDockerfile( "maria" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java index e0aa9ef3a..5b5ad5d2c 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java @@ -5,7 +5,7 @@ */ package org.hibernate.reactive.containers; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; import java.io.Serializable; import java.math.BigDecimal; @@ -87,7 +87,7 @@ class MySQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MySQLContainer mysql = new MySQLContainer<>( imageName( "mysql", "9.2.0") ) + public static final MySQLContainer mysql = new MySQLContainer<>( fromDockerfile( "mysql" ).asCompatibleSubstituteFor( "mysql" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java index d57f9103a..bbde95e6c 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java @@ -29,7 +29,7 @@ import org.testcontainers.containers.OracleContainer; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; /** * Connection string for Oracle thin should be something like: @@ -88,9 +88,7 @@ class OracleDatabase implements TestableDatabase { } } - public static final OracleContainer oracle = new OracleContainer( - imageName( "gvenzl/oracle-free", "23-slim-faststart" ) - .asCompatibleSubstituteFor( "gvenzl/oracle-xe" ) ) + public static final OracleContainer oracle = new OracleContainer( fromDockerfile( "oracle" ).asCompatibleSubstituteFor( "gvenzl/oracle-xe" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java index 56ef4f878..f6a974ba2 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java @@ -5,8 +5,6 @@ */ package org.hibernate.reactive.containers; -import static org.hibernate.reactive.containers.DockerImage.imageName; - import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; @@ -30,9 +28,11 @@ import org.testcontainers.containers.PostgreSQLContainer; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; + class PostgreSQLDatabase implements TestableDatabase { - public static PostgreSQLDatabase INSTANCE = new PostgreSQLDatabase(); + public static final PostgreSQLDatabase INSTANCE = new PostgreSQLDatabase(); private static Map, String> expectedDBTypeForClass = new HashMap<>(); @@ -87,7 +87,7 @@ class PostgreSQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( imageName( "postgres", "17.5" ) ) + public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( fromDockerfile( "postgresql" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java index 3132b8498..56def4ef9 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java @@ -34,6 +34,7 @@ import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MYSQL; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -126,13 +127,8 @@ context, setupFactory( strategy, type ) validateConf.addAnnotatedClass( BasicTypesTestEntity.class ); // The table mapping this entity shouldn't be in the db validateConf.addAnnotatedClass( Extra.class ); - return setupSessionFactory( validateConf ) - .handle( (unused, throwable) -> { - assertThat( throwable ) - .isInstanceOf( SchemaManagementException.class ) - .hasMessage( errorMessage ); - return null; - } ); + return assertThrown( SchemaManagementException.class, setupSessionFactory( validateConf ) ) + .thenAccept( throwable -> assertThat( throwable ).hasMessage( errorMessage ) ); } ) ); } diff --git a/integration-tests/bytecode-enhancements-it/build.gradle b/integration-tests/bytecode-enhancements-it/build.gradle index 17ffdf10e..aaf1f5d8a 100644 --- a/integration-tests/bytecode-enhancements-it/build.gradle +++ b/integration-tests/bytecode-enhancements-it/build.gradle @@ -10,107 +10,39 @@ buildscript { } plugins { - id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}" + id "hr-java-library" + id "hr-test-containers" + + alias(libs.plugins.org.hibernate.orm) } description = 'Bytecode enhancements integration tests' -ext { - log4jVersion = '2.20.0' - assertjVersion = '3.27.3' -} - dependencies { implementation project(':hibernate-reactive-core') // JPA metamodel generation for criteria queries (optional) - annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}" + annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen) // Testing on one database should be enough - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly(libs.com.ongres.scram.scram.client) // logging - runtimeOnly "org.apache.logging.log4j:log4j-core:${log4jVersion}" + runtimeOnly(libs.org.apache.logging.log4j.log4j.core) // Testcontainers - testImplementation "org.testcontainers:postgresql:${testcontainersVersion}" + testImplementation(libs.org.testcontainers.postgresql) // Testing - testImplementation "org.assertj:assertj-core:${assertjVersion}" - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" -} - -// Optional: enable the bytecode enhancements -hibernate { enhancement } - -// Print a summary of the results of the tests (number of failures, successes and skipped) -// This is the same as the one in hibernate-reactive-core -def loggingSummary(db, result, desc) { - if ( !desc.parent ) { // will match the outermost suite - def output = "${db} results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)" - def repeatLength = output.length() + 1 - logger.lifecycle '\n' + ('-' * repeatLength) + '\n' + output + '\n' + ('-' * repeatLength) - } + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) } -// Example: -// gradle test -Pdb=MySQL -test { - def selectedDb = project.hasProperty( 'db' ) - ? project.properties['db'] - : 'PostgreSQL' - doFirst { - systemProperty 'db', selectedDb - } - afterSuite { desc, result -> - loggingSummary( selectedDb, result, desc ) +hibernate { + enhancement { + // We want everything enabled for the tests } } - -// Configuration for the tests -tasks.withType( Test ).configureEach { - defaultCharacterEncoding = "UTF-8" - useJUnitPlatform() - testLogging { - displayGranularity 1 - showStandardStreams = project.hasProperty('showStandardOutput') - showStackTraces = true - exceptionFormat = 'full' - events 'PASSED', 'FAILED', 'SKIPPED' - } - systemProperty 'docker', project.hasProperty( 'docker' ) ? 'true' : 'false' - systemProperty 'org.hibernate.reactive.common.InternalStateAssertions.ENFORCE', 'true' - - if ( project.hasProperty( 'includeTests' ) ) { - // Example: ./gradlew testAll -PincludeTests=DefaultPortTest - filter { - includeTestsMatching project.properties['includeTests'] ?: '*' as String - } - } -} - -// Rule to recognize calls to testDb -// and run the tests on the selected db -// Example: -// gradle testDbMySQL testDbDB2 -tasks.addRule( "Pattern testDb" ) { String taskName -> - if ( taskName.startsWith( "testDb" ) ) { - tasks.register( taskName, Test ) { - def dbName = taskName.substring( "testDb".length() ) - description = "Run tests for ${dbName}" - - // We only want to test this on Postgres - onlyIf { dbName.toLowerCase().startsWith( 'p' ) } - doFirst() { - systemProperty 'db', dbName - } - afterSuite { desc, result -> - loggingSummary( dbName, result, desc ) - } - - } - } -} \ No newline at end of file diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java index 2388cb421..261fa380d 100644 --- a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -20,6 +19,7 @@ import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.stage.Stage; +import org.hibernate.reactive.util.impl.CompletionStages; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -29,7 +29,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.smallrye.mutiny.Uni; -import io.vertx.core.Promise; import io.vertx.core.VertxOptions; import io.vertx.junit5.RunTestOnContext; import io.vertx.junit5.VertxExtension; @@ -52,9 +51,7 @@ public abstract class BaseReactiveIT { // These properties are in DatabaseConfiguration in core public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); - public static final DockerImageName IMAGE_NAME = DockerImageName - .parse( "docker.io/postgres:17.5" ) - .asCompatibleSubstituteFor( "postgres" ); + public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" ); public static final String USERNAME = "hreact"; public static final String PASSWORD = "hreact"; @@ -73,7 +70,7 @@ public abstract class BaseReactiveIT { * Configure Vertx JUnit5 test context */ @RegisterExtension - static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions() ); + static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions(), false ); private static VertxOptions vertxOptions() { return new VertxOptions() @@ -202,33 +199,19 @@ protected CompletionStage setupSessionFactory(Configuration configuration) * @return a {@link CompletionStage} void that succeeds when the factory is ready. */ protected CompletionStage setupSessionFactory(Supplier confSupplier) { - CompletableFuture future = new CompletableFuture<>(); - testOnContext.vertx() + return testOnContext.vertx() .executeBlocking( // schema generation is a blocking operation and so it causes an // exception when run on the Vert.x event loop. So call it using // Vertx.executeBlocking() - promise -> startFactoryManager( promise, confSupplier ), - event -> { - if ( event.succeeded() ) { - future.complete( null ); - } - else { - future.completeExceptionally( event.cause() ); - } - } - ); - return future; + () -> startFactoryManager( confSupplier ), + true + ).toCompletionStage().thenCompose( CompletionStages::voidFuture ); } - private void startFactoryManager(Promise p, Supplier confSupplier) { - try { - ormSessionFactory = createHibernateSessionFactory( confSupplier.get() ); - p.complete(); - } - catch (Throwable e) { - p.fail( e ); - } + private Object startFactoryManager(Supplier confSupplier) { + ormSessionFactory = createHibernateSessionFactory( confSupplier.get() ); + return null; } private SessionFactory createHibernateSessionFactory(Configuration configuration) { diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java new file mode 100644 index 000000000..b5621249d --- /dev/null +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java @@ -0,0 +1,125 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.it; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.testcontainers.utility.DockerImageName; + +/** + * A utility class with methods to generate a {@link DockerImageName} for Testcontainers. + *

+ * Testcontainers might not work if the image required is available in multiple different registries (for example when + * using podman instead of docker). + * These methods make sure to pick a registry as default. + *

+ */ +public final class DockerImage { + + /** + * The absolute path of the project root that we have set in Gradle. + */ + private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" ); + + /** + * The path to the directory containing all the Dockerfile files + */ + private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" ); + + /** + * Extract the image name and version from the first FROM instruction in the Dockerfile. + * Note that everything else is ignored. + */ + public static DockerImageName fromDockerfile(String databaseName) { + try { + final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() ); + return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + + public static DockerImageName imageName(String registry, String image, String version) { + return DockerImageName + .parse( registry + "/" + image + ":" + version ) + .asCompatibleSubstituteFor( image ); + } + + private static class ImageInformation { + private final String registry; + private final String image; + private final String version; + + public ImageInformation(String fullImageInfo) { + // FullImageInfo pattern: /: + // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13" + final int registryEndPos = fullImageInfo.indexOf( '/' ); + final int imageEndPos = fullImageInfo.lastIndexOf( ':' ); + this.registry = fullImageInfo.substring( 0, registryEndPos ); + this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos ); + this.version = fullImageInfo.substring( imageEndPos + 1 ); + } + + public String getRegistry() { + return registry; + } + + public String getImage() { + return image; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return registry + "/" + image + ":" + version; + } + } + + private static Path dockerFilePath(String database) { + // Get project root from system property set by Gradle, with fallback + return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" ); + } + + private static ImageInformation readFromInstruction(String database) throws IOException { + return readFromInstruction( dockerFilePath( database ) ); + } + + /** + * Read a Dockerfile and extract the first FROM instruction. + * + * @param dockerfilePath path to the Dockerfile + * @return the first FROM instruction found, or empty if none found + * @throws IOException if the file cannot be read + */ + private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException { + if ( !Files.exists( dockerfilePath ) ) { + throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath ); + } + + List lines = Files.readAllLines( dockerfilePath ); + for ( String line : lines ) { + // Skip comments and empty lines + String trimmedLine = line.trim(); + if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) { + continue; + } + + if ( trimmedLine.startsWith( "FROM " ) ) { + return new ImageInformation( trimmedLine.substring( "FROM ".length() ) ); + } + } + + throw new IOException( " Missing FROM instruction in " + dockerfilePath ); + } +} diff --git a/integration-tests/hibernate-validator-postgres-it/build.gradle b/integration-tests/hibernate-validator-postgres-it/build.gradle index 27689b24d..aae2818e6 100644 --- a/integration-tests/hibernate-validator-postgres-it/build.gradle +++ b/integration-tests/hibernate-validator-postgres-it/build.gradle @@ -10,110 +10,44 @@ buildscript { } plugins { - id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}" -} + id "hr-java-library" + id "hr-test-containers" -description = 'Quarkus QE integration tests' + alias(libs.plugins.org.hibernate.orm) +} -ext { - log4jVersion = '2.20.0' - assertjVersion = '3.27.3' +test { + def db = project.properties['db'] + enabled = db == null || db.equals( 'PostgreSQL' ) } +description = 'Quarkus QE integration tests' + dependencies { implementation project(':hibernate-reactive-core') - implementation "org.hibernate.validator:hibernate-validator:8.0.2.Final" - runtimeOnly 'org.glassfish.expressly:expressly:5.0.0' + implementation(libs.org.hibernate.validator.hibernate.validator) + runtimeOnly(libs.org.glassfish.expressly.expressly) // JPA metamodel generation for criteria queries (optional) - annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}" + annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen) // Testing on one database should be enough - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly(libs.com.ongres.scram.scram.client) // logging - runtimeOnly "org.apache.logging.log4j:log4j-core:${log4jVersion}" + runtimeOnly(libs.org.apache.logging.log4j.log4j.core) // Testcontainers - testImplementation "org.testcontainers:postgresql:${testcontainersVersion}" + testImplementation(libs.org.testcontainers.postgresql) // Testing - testImplementation "org.assertj:assertj-core:${assertjVersion}" - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) } // Optional: enable the bytecode enhancements //hibernate { enhancement } -// Print a summary of the results of the tests (number of failures, successes and skipped) -// This is the same as the one in hibernate-reactive-core -def loggingSummary(db, result, desc) { - if ( !desc.parent ) { // will match the outermost suite - def output = "${db} results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)" - def repeatLength = output.length() + 1 - logger.lifecycle '\n' + ('-' * repeatLength) + '\n' + output + '\n' + ('-' * repeatLength) - } -} - -// Example: -// gradle test -Pdb=MySQL -test { - def selectedDb = project.hasProperty( 'db' ) - ? project.properties['db'] - : 'PostgreSQL' - doFirst { - systemProperty 'db', selectedDb - } - afterSuite { desc, result -> - loggingSummary( selectedDb, result, desc ) - } -} - -// Configuration for the tests -tasks.withType( Test ).configureEach { - defaultCharacterEncoding = "UTF-8" - useJUnitPlatform() - testLogging { - displayGranularity 1 - showStandardStreams = project.hasProperty('showStandardOutput') - showStackTraces = true - exceptionFormat = 'full' - events 'PASSED', 'FAILED', 'SKIPPED' - } - systemProperty 'docker', project.hasProperty( 'docker' ) ? 'true' : 'false' - systemProperty 'org.hibernate.reactive.common.InternalStateAssertions.ENFORCE', 'true' - - if ( project.hasProperty( 'includeTests' ) ) { - // Example: ./gradlew testAll -PincludeTests=DefaultPortTest - filter { - includeTestsMatching project.properties['includeTests'] ?: '*' as String - } - } -} - - -// Rule to recognize calls to testDb -// and run the tests on the selected db -// Example: -// gradle testDbMySQL testDbDB2 -tasks.addRule( "Pattern testDb" ) { String taskName -> - if ( taskName.startsWith( "testDb" ) ) { - tasks.register( taskName, Test ) { - def dbName = taskName.substring( "testDb".length() ) - description = "Run tests for ${dbName}" - - // We only want to test this on Postgres - onlyIf { dbName.toLowerCase().startsWith( 'p' ) } - doFirst() { - systemProperty 'db', dbName - } - afterSuite { desc, result -> - loggingSummary( dbName, result, desc ) - } - - } - } -} \ No newline at end of file diff --git a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java index 7debdbb2c..cb94e0547 100644 --- a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java +++ b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java @@ -7,7 +7,6 @@ import java.util.Collection; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -20,6 +19,7 @@ import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.stage.Stage; +import org.hibernate.reactive.util.impl.CompletionStages; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -29,7 +29,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.smallrye.mutiny.Uni; -import io.vertx.core.Promise; import io.vertx.core.VertxOptions; import io.vertx.junit5.RunTestOnContext; import io.vertx.junit5.VertxExtension; @@ -52,9 +51,7 @@ public abstract class BaseReactiveIT { // These properties are in DatabaseConfiguration in core public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); - public static final DockerImageName IMAGE_NAME = DockerImageName - .parse( "docker.io/postgres:17.5" ) - .asCompatibleSubstituteFor( "postgres" ); + public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" ); public static final String USERNAME = "hreact"; public static final String PASSWORD = "hreact"; @@ -73,7 +70,7 @@ public abstract class BaseReactiveIT { * Configure Vertx JUnit5 test context */ @RegisterExtension - static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions() ); + static RunTestOnContext testOnContext = new RunTestOnContext( vertxOptions(), false ); private static VertxOptions vertxOptions() { return new VertxOptions() @@ -202,33 +199,19 @@ protected CompletionStage setupSessionFactory(Configuration configuration) * @return a {@link CompletionStage} void that succeeds when the factory is ready. */ protected CompletionStage setupSessionFactory(Supplier confSupplier) { - CompletableFuture future = new CompletableFuture<>(); - testOnContext.vertx() + return testOnContext.vertx() .executeBlocking( // schema generation is a blocking operation and so it causes an // exception when run on the Vert.x event loop. So call it using // Vertx.executeBlocking() - promise -> startFactoryManager( promise, confSupplier ), - event -> { - if ( event.succeeded() ) { - future.complete( null ); - } - else { - future.completeExceptionally( event.cause() ); - } - } - ); - return future; + () -> startFactoryManager( confSupplier ), + true + ) + .toCompletionStage().thenCompose( CompletionStages::voidFuture ); } - private void startFactoryManager(Promise p, Supplier confSupplier) { - try { - ormSessionFactory = createHibernateSessionFactory( confSupplier.get() ); - p.complete(); - } - catch (Throwable e) { - p.fail( e ); - } + private Object startFactoryManager(Supplier confSupplier) { + return ormSessionFactory = createHibernateSessionFactory( confSupplier.get() ); } private SessionFactory createHibernateSessionFactory(Configuration configuration) { diff --git a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java new file mode 100644 index 000000000..7770da76c --- /dev/null +++ b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java @@ -0,0 +1,125 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.it.quarkus.qe.database; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.testcontainers.utility.DockerImageName; + +/** + * A utility class with methods to generate a {@link DockerImageName} for Testcontainers. + *

+ * Testcontainers might not work if the image required is available in multiple different registries (for example when + * using podman instead of docker). + * These methods make sure to pick a registry as default. + *

+ */ +public final class DockerImage { + + /** + * The absolute path of the project root that we have set in Gradle. + */ + private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" ); + + /** + * The path to the directory containing all the Dockerfile files + */ + private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" ); + + /** + * Extract the image name and version from the first FROM instruction in the Dockerfile. + * Note that everything else is ignored. + */ + public static DockerImageName fromDockerfile(String databaseName) { + try { + final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() ); + return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + + public static DockerImageName imageName(String registry, String image, String version) { + return DockerImageName + .parse( registry + "/" + image + ":" + version ) + .asCompatibleSubstituteFor( image ); + } + + private static class ImageInformation { + private final String registry; + private final String image; + private final String version; + + public ImageInformation(String fullImageInfo) { + // FullImageInfo pattern: /: + // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13" + final int registryEndPos = fullImageInfo.indexOf( '/' ); + final int imageEndPos = fullImageInfo.lastIndexOf( ':' ); + this.registry = fullImageInfo.substring( 0, registryEndPos ); + this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos ); + this.version = fullImageInfo.substring( imageEndPos + 1 ); + } + + public String getRegistry() { + return registry; + } + + public String getImage() { + return image; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return registry + "/" + image + ":" + version; + } + } + + private static Path dockerFilePath(String database) { + // Get project root from system property set by Gradle, with fallback + return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" ); + } + + private static ImageInformation readFromInstruction(String database) throws IOException { + return readFromInstruction( dockerFilePath( database ) ); + } + + /** + * Read a Dockerfile and extract the first FROM instruction. + * + * @param dockerfilePath path to the Dockerfile + * @return the first FROM instruction found, or empty if none found + * @throws IOException if the file cannot be read + */ + private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException { + if ( !Files.exists( dockerfilePath ) ) { + throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath ); + } + + List lines = Files.readAllLines( dockerfilePath ); + for ( String line : lines ) { + // Skip comments and empty lines + String trimmedLine = line.trim(); + if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) { + continue; + } + + if ( trimmedLine.startsWith( "FROM " ) ) { + return new ImageInformation( trimmedLine.substring( "FROM ".length() ) ); + } + } + + throw new IOException( " Missing FROM instruction in " + dockerfilePath ); + } +} diff --git a/integration-tests/techempower-postgres-it/build.gradle b/integration-tests/techempower-postgres-it/build.gradle index 5c4ecda47..b44b3f7a6 100644 --- a/integration-tests/techempower-postgres-it/build.gradle +++ b/integration-tests/techempower-postgres-it/build.gradle @@ -9,84 +9,37 @@ buildscript { } } -description = 'TechEmpower integration tests' +plugins { + id "hr-java-library" + id "hr-test-containers" +} -ext { - jacksonDatabindVersion = '2.15.2' - jbossLoggingVersion = '3.5.0.Final' - assertjVersion = '3.27.3' - vertxWebVersion = project.hasProperty( 'vertxWebVersion' ) - ? project.property( 'vertxWebVersion' ) - : vertxSqlClientVersion - vertxWebClientVersion = project.hasProperty( 'vertxWebClientVersion' ) - ? project.property( 'vertxWebClientVersion' ) - : vertxSqlClientVersion +test { + def db = project.properties['db'] + enabled = db == null || db.equals( 'PostgreSQL' ) } +description = 'TechEmpower integration tests' + dependencies { implementation project( ':hibernate-reactive-core' ) - implementation "io.vertx:vertx-web:${vertxWebVersion}" - implementation "io.vertx:vertx-web-client:${vertxWebClientVersion}" + implementation(libs.io.vertx.vertx.web) + implementation(libs.io.vertx.vertx.web.client) - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // The Pg client requires this dependency - runtimeOnly "com.ongres.scram:client:2.1" - runtimeOnly "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}" + runtimeOnly(libs.com.ongres.scram.scram.client) + runtimeOnly(libs.com.fasterxml.jackson.core.jackson.databind) // logging - implementation "org.jboss.logging:jboss-logging:${jbossLoggingVersion}" + implementation(libs.org.jboss.logging.jboss.logging) // Testcontainers - implementation "org.testcontainers:postgresql:${testcontainersVersion}" + implementation(libs.org.testcontainers.postgresql) // Testing - testImplementation "org.assertj:assertj-core:${assertjVersion}" - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" -} - -// Configuration for the tests -tasks.withType( Test ).configureEach { - defaultCharacterEncoding = "UTF-8" - useJUnitPlatform() - testLogging { - showStandardStreams = project.hasProperty( 'showStandardOutput' ) - showStackTraces = true - exceptionFormat = 'full' - displayGranularity = 1 - events = ['PASSED', 'FAILED', 'SKIPPED'] - } - - // We need a to use an instance of PostgreSQL with a specific configuration. - // So, for this particular integration-test module, we default to true unless docker is disabled. - systemProperty 'docker', project.hasProperty('docker') - systemProperty 'org.hibernate.reactive.common.InternalStateAssertions.ENFORCE', 'true' - - if ( project.hasProperty( 'includeTests' ) ) { - // Example: ./gradlew testAll -PincludeTests=DefaultPortTest - filter { - includeTestsMatching project.properties['includeTests'] ?: '*' as String - } - } -} - -// Print a summary of the results of the tests (number of failures, successes and skipped) -// This is the same as the one in hibernate-reactive-core -def loggingSummary(db, result, desc) { - if ( !desc.parent ) { // will match the outermost suite - def output = "${db} results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)" - def repeatLength = output.length() + 1 - logger.lifecycle '\n' + ('-' * repeatLength) + '\n' + output + '\n' + ('-' * repeatLength) - } -} - -test { - doFirst { - systemProperty 'db', 'PostgreSQL' - } - // We only want to test this on Postgres - afterSuite { desc, result -> - loggingSummary( 'PostgreSQL', result, desc ) - } + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) } def javaMainClass = "org.hibernate.reactive.it.techempower.VertxServer" diff --git a/integration-tests/techempower-postgres-it/src/main/java/org/hibernate/reactive/it/techempower/WorldVerticle.java b/integration-tests/techempower-postgres-it/src/main/java/org/hibernate/reactive/it/techempower/WorldVerticle.java index 4a12c216f..8b89e2d9d 100644 --- a/integration-tests/techempower-postgres-it/src/main/java/org/hibernate/reactive/it/techempower/WorldVerticle.java +++ b/integration-tests/techempower-postgres-it/src/main/java/org/hibernate/reactive/it/techempower/WorldVerticle.java @@ -45,19 +45,14 @@ public WorldVerticle(Supplier emfSupplier) { this.emfSupplier = emfSupplier; } - private void startHibernate(Promise p) { - try { - this.emf = emfSupplier.get(); - p.complete(); - } - catch (Throwable t) { - p.fail( t ); - } + private Object startHibernate() { + this.emf = emfSupplier.get(); + return null; } @Override public void start(Promise startPromise) { - final Future startHibernate = vertx.executeBlocking( this::startHibernate ) + final Future startHibernate = vertx.executeBlocking( this::startHibernate, true ) .onSuccess( s -> LOG.infof( "✅ Hibernate Reactive is ready" ) ); Router router = Router.router( vertx ); diff --git a/integration-tests/techempower-postgres-it/src/test/java/org/hibernate/reactive/techempower/TechEmpowerTest.java b/integration-tests/techempower-postgres-it/src/test/java/org/hibernate/reactive/techempower/TechEmpowerTest.java index 1f28b2bb2..ac628337d 100644 --- a/integration-tests/techempower-postgres-it/src/test/java/org/hibernate/reactive/techempower/TechEmpowerTest.java +++ b/integration-tests/techempower-postgres-it/src/test/java/org/hibernate/reactive/techempower/TechEmpowerTest.java @@ -73,7 +73,7 @@ public void testWorldRepository(VertxTestContext context) { .compose( this::updates ) .onSuccess( res -> context.completeNow() ) .onFailure( context::failNow ) - .eventually( unused -> vertx.close() ); + .eventually( vertx::close ); } /** diff --git a/integration-tests/verticle-postgres-it/build.gradle b/integration-tests/verticle-postgres-it/build.gradle index 0be234620..f8622ab95 100644 --- a/integration-tests/verticle-postgres-it/build.gradle +++ b/integration-tests/verticle-postgres-it/build.gradle @@ -9,108 +9,37 @@ buildscript { } } -description = 'Bytecode enhancements integration tests' +plugins { + id 'hr-java-library' + id 'hr-test-containers' +} -ext { - jacksonDatabindVersion = '2.15.2' - jbossLoggingVersion = '3.5.0.Final' - assertjVersion = '3.27.3' - vertxWebVersion = project.hasProperty( 'vertxWebVersion' ) - ? project.property( 'vertxWebVersion' ) - : vertxSqlClientVersion - vertxWebClientVersion = project.hasProperty( 'vertxWebClientVersion' ) - ? project.property( 'vertxWebClientVersion' ) - : vertxSqlClientVersion +test { + def db = project.properties['db'] + enabled = db == null || db.equals( 'PostgreSQL' ) } +description = 'Bytecode enhancements integration tests' + dependencies { implementation project(':hibernate-reactive-core') - implementation "io.vertx:vertx-web:${vertxWebVersion}" - implementation "io.vertx:vertx-web-client:${vertxWebClientVersion}" + implementation(libs.io.vertx.vertx.web) + implementation(libs.io.vertx.vertx.web.client) - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // The Pg client requires this dependency - runtimeOnly "com.ongres.scram:client:2.1" - runtimeOnly "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}" + runtimeOnly(libs.com.ongres.scram.scram.client) + runtimeOnly(libs.com.fasterxml.jackson.core.jackson.databind) // logging - implementation "org.jboss.logging:jboss-logging:${jbossLoggingVersion}" + implementation(libs.org.jboss.logging.jboss.logging) // Testcontainers - implementation "org.testcontainers:postgresql:${testcontainersVersion}" + implementation(libs.org.testcontainers.postgresql) // Testing - testImplementation "org.assertj:assertj-core:${assertjVersion}" - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" -} - -// Configuration for the tests -tasks.withType( Test ).configureEach { - defaultCharacterEncoding = "UTF-8" - useJUnitPlatform() - testLogging { - showStandardStreams = project.hasProperty( 'showStandardOutput' ) - showStackTraces = true - exceptionFormat = 'full' - displayGranularity = 1 - events = ['PASSED', 'FAILED', 'SKIPPED'] - } - systemProperty 'docker', project.hasProperty( 'docker' ) - systemProperty 'org.hibernate.reactive.common.InternalStateAssertions.ENFORCE', 'true' - - if ( project.hasProperty( 'includeTests' ) ) { - // Example: ./gradlew testAll -PincludeTests=DefaultPortTest - filter { - includeTestsMatching project.getProperty( 'includeTests' ) ?: '*' - } - } -} - -// Print a summary of the results of the tests (number of failures, successes and skipped) -// This is the same as the one in hibernate-reactive-core -def loggingSummary(db, result, desc) { - if ( !desc.parent ) { // will match the outermost suite - def output = "${db} results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)" - def repeatLength = output.length() + 1 - logger.lifecycle '\n' + ('-' * repeatLength) + '\n' + output + '\n' + ('-' * repeatLength) - } -} - -// Example: -// gradle test -Pdb=MySQL -test { - def selectedDb = project.hasProperty( 'db' ) - ? project.properties['db'] - : 'PostgreSQL' - doFirst { - systemProperty 'db', selectedDb - } - afterSuite { desc, result -> - loggingSummary( selectedDb, result, desc ) - } -} - -// Rule to recognize calls to testDb -// and run the tests on the selected db -// Example: -// gradle testDbMySQL testDbDB2 -tasks.addRule( "Pattern testDb" ) { String taskName -> - if ( taskName.startsWith( "testDb" ) ) { - tasks.register( taskName, Test ) { - def dbName = taskName.substring( "testDb".length() ) - description = "Run tests for ${dbName}" - - // We only want to test this on Postgres - onlyIf { dbName.toLowerCase().startsWith( 'p' ) } - doFirst() { - systemProperty 'db', dbName - } - afterSuite { desc, result -> - loggingSummary( dbName, result, desc ) - } - - } - } + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) } tasks.register( "startVertx", JavaExec ) { diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java new file mode 100644 index 000000000..10026d020 --- /dev/null +++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java @@ -0,0 +1,125 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.it.verticle; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.testcontainers.utility.DockerImageName; + +/** + * A utility class with methods to generate a {@link DockerImageName} for Testcontainers. + *

+ * Testcontainers might not work if the image required is available in multiple different registries (for example when + * using podman instead of docker). + * These methods make sure to pick a registry as default. + *

+ */ +public final class DockerImage { + + /** + * The absolute path of the project root that we have set in Gradle. + */ + private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" ); + + /** + * The path to the directory containing all the Dockerfile files + */ + private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" ); + + /** + * Extract the image name and version from the first FROM instruction in the Dockerfile. + * Note that everything else is ignored. + */ + public static DockerImageName fromDockerfile(String databaseName) { + try { + final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() ); + return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + + public static DockerImageName imageName(String registry, String image, String version) { + return DockerImageName + .parse( registry + "/" + image + ":" + version ) + .asCompatibleSubstituteFor( image ); + } + + private static class ImageInformation { + private final String registry; + private final String image; + private final String version; + + public ImageInformation(String fullImageInfo) { + // FullImageInfo pattern: /: + // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13" + final int registryEndPos = fullImageInfo.indexOf( '/' ); + final int imageEndPos = fullImageInfo.lastIndexOf( ':' ); + this.registry = fullImageInfo.substring( 0, registryEndPos ); + this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos ); + this.version = fullImageInfo.substring( imageEndPos + 1 ); + } + + public String getRegistry() { + return registry; + } + + public String getImage() { + return image; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return registry + "/" + image + ":" + version; + } + } + + private static Path dockerFilePath(String database) { + // Get project root from system property set by Gradle, with fallback + return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" ); + } + + private static ImageInformation readFromInstruction(String database) throws IOException { + return readFromInstruction( dockerFilePath( database ) ); + } + + /** + * Read a Dockerfile and extract the first FROM instruction. + * + * @param dockerfilePath path to the Dockerfile + * @return the first FROM instruction found, or empty if none found + * @throws IOException if the file cannot be read + */ + private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException { + if ( !Files.exists( dockerfilePath ) ) { + throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath ); + } + + List lines = Files.readAllLines( dockerfilePath ); + for ( String line : lines ) { + // Skip comments and empty lines + String trimmedLine = line.trim(); + if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) { + continue; + } + + if ( trimmedLine.startsWith( "FROM " ) ) { + return new ImageInformation( trimmedLine.substring( "FROM ".length() ) ); + } + } + + throw new IOException( " Missing FROM instruction in " + dockerfilePath ); + } +} diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java index 882231ad1..91d886110 100644 --- a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java +++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java @@ -40,19 +40,14 @@ public ProductVerticle(Supplier emfSupplier) { this.emfSupplier = emfSupplier; } - private void startHibernate(Promise p) { - try { - this.emf = emfSupplier.get(); - p.complete(); - } - catch (Throwable t) { - p.fail( t ); - } + private Object startHibernate() { + this.emf = emfSupplier.get(); + return null; } @Override public void start(Promise startPromise) { - final Future startHibernate = vertx.executeBlocking( this::startHibernate ) + final Future startHibernate = vertx.executeBlocking( this::startHibernate, true ) .onSuccess( s -> LOG.infof( "✅ Hibernate Reactive is ready" ) ); Router router = Router.router( vertx ); diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java index 990fef754..f11254c6d 100644 --- a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java +++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java @@ -21,6 +21,7 @@ import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; @@ -36,7 +37,8 @@ public class VertxServer { // These properties are in DatabaseConfiguration in core public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); - public static final String IMAGE_NAME = "postgres:17.5"; + public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" ); + public static final String USERNAME = "hreact"; public static final String PASSWORD = "hreact"; public static final String DB_NAME = "hreact"; diff --git a/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java b/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java index 0c819a393..eef39ba37 100644 --- a/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java +++ b/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java @@ -31,7 +31,6 @@ import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; -import static io.vertx.core.CompositeFuture.all; import static io.vertx.core.Future.all; import static io.vertx.core.Future.failedFuture; import static io.vertx.core.Future.succeededFuture; @@ -50,7 +49,7 @@ * that's been close ahead of time by someone else. * Theoretically, everything could happen in the right order because of chance, * but it's unlikely and at the moment I don't have a better solution. - * See the the related issue + * See the related issue * for more details. *

*/ @@ -88,7 +87,7 @@ public void testProductsGeneration(VertxTestContext context) { .compose( this::findProducts ) .onSuccess( res -> context.completeNow() ) .onFailure( context::failNow ) - .eventually( unused -> vertx.close() ); + .eventually( vertx::close ); } /** @@ -97,7 +96,7 @@ public void testProductsGeneration(VertxTestContext context) { * @see #REQUEST_NUMBER */ private Future createProducts(WebClient webClient) { - List postRequests = new ArrayList<>(); + List> postRequests = new ArrayList<>(); for ( int i = 0; i < REQUEST_NUMBER; i++ ) { final Product product = new Product( i + 1 ); diff --git a/local-build-plugins/build.gradle b/local-build-plugins/build.gradle index 7cc4db2f9..2389343d6 100644 --- a/local-build-plugins/build.gradle +++ b/local-build-plugins/build.gradle @@ -1,5 +1,6 @@ plugins { id "java-gradle-plugin" + id 'groovy-gradle-plugin' } repositories { diff --git a/local-build-plugins/src/main/groovy/hr-java-library.gradle b/local-build-plugins/src/main/groovy/hr-java-library.gradle new file mode 100644 index 000000000..4f372fe21 --- /dev/null +++ b/local-build-plugins/src/main/groovy/hr-java-library.gradle @@ -0,0 +1,110 @@ +plugins{ + id 'java-library' + id 'com.diffplug.spotless' +} + +group = rootProject.group +version = rootProject.version + +spotless { + //Don't fail during the check: rather than enforcing guidelines, we use this plugin to fix mistakes automatically. + enforceCheck false + java { + licenseHeaderFile rootProject.file('spotless.license.java') + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + } +} + +tasks.compileJava.dependsOn(spotlessApply) + +repositories { + // Example: ./gradlew build -PenableMavenLocalRepo + if ( project.hasProperty('enableMavenLocalRepo') ) { + // Useful for local development, it should be disabled otherwise + mavenLocal() + } + // Example: ./gradlew build -PenableCentralSonatypeSnapshotsRep + if ( project.hasProperty('enableCentralSonatypeSnapshotsRep') ) { + maven { url 'https://central.sonatype.com/repository/maven-snapshots/' } + } + + mavenCentral() +} + +ext.publishScript = rootProject.rootDir.absolutePath + '/publish.gradle' + +tasks.withType( JavaCompile ).configureEach { + options.encoding = 'UTF-8' +} + +// Configure test tasks for all subprojects +tasks.withType( Test ).configureEach { + // Set the project root for finding Docker files - available to all modules + systemProperty 'hibernate.reactive.project.root', rootProject.projectDir.absolutePath +} + +if ( !gradle.ext.javaToolchainEnabled ) { + sourceCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion ) + targetCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion ) +} +else { + // Configure generated bytecode + // "sourceCompatibility" is not supported with toolchains. We have to work around that limitation. + tasks.compileJava.configure { + options.release = gradle.ext.javaVersions.main.release.asInt() + } + tasks.compileTestJava.configure { + options.release = gradle.ext.javaVersions.test.release.asInt() + } + + // Configure version of Java tools + java { + toolchain { + languageVersion = gradle.ext.javaVersions.main.compiler + } + } + tasks.compileTestJava { + javaCompiler = javaToolchains.compilerFor { + languageVersion = gradle.ext.javaVersions.test.compiler + } + } + tasks.test { + javaLauncher = javaToolchains.launcherFor { + languageVersion = gradle.ext.javaVersions.test.launcher + } + } + + // Configure JVM Options + tasks.withType( JavaCompile ).configureEach { + options.forkOptions.jvmArgs.addAll( getProperty( 'toolchain.compiler.jvmargs' ).toString().split( ' ' ) ) + } + tasks.withType( Javadoc ).configureEach { + options.setJFlags( getProperty( 'toolchain.javadoc.jvmargs' ).toString().split( ' ' ).toList().findAll( { !it.isEmpty() } ) ) + } + tasks.test { + // Configure JVM Options + jvmArgs(getProperty('toolchain.launcher.jvmargs').toString().split(' ')) + if ( project.hasProperty( 'test.jdk.launcher.args' ) ) { + jvmArgs( project.getProperty( 'test.jdk.launcher.args' ).toString().split( ' ' ) ) + } + } + + // Display version of Java tools + tasks.withType( JavaCompile ).configureEach { + doFirst { + logger.lifecycle "Compiling with '${javaCompiler.get().metadata.installationPath}'" + } + } + tasks.withType( Javadoc ).configureEach { + doFirst { + logger.lifecycle "Generating javadoc with '${javadocTool.get().metadata.installationPath}'" + } + } + tasks.test { + doFirst { + logger.lifecycle "Testing with '${javaLauncher.get().metadata.installationPath}'" + } + } +} diff --git a/local-build-plugins/src/main/groovy/hr-print-resolved-version.gradle b/local-build-plugins/src/main/groovy/hr-print-resolved-version.gradle new file mode 100644 index 000000000..9dea09b82 --- /dev/null +++ b/local-build-plugins/src/main/groovy/hr-print-resolved-version.gradle @@ -0,0 +1,31 @@ +// Task to print the resolved versions of Hibernate ORM and Vert.x +tasks.register( "printResolvedVersions" ) { + description = "Print the resolved hibernate-orm-core and vert.x versions" + doLast { + def hibernateCoreVersion = "n/a" + def vertxVersion = "n/a" + + // Resolve Hibernate Core and Vert.x versions from compile classpath + configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact -> + if (artifact.moduleVersion.id.name == 'hibernate-core') { + hibernateCoreVersion = artifact.moduleVersion.id.version + } + if (artifact.moduleVersion.id.group == 'io.vertx' && artifact.moduleVersion.id.name == 'vertx-sql-client') { + vertxVersion = artifact.moduleVersion.id.version + } + } + + // Print the resolved versions + println "Resolved Hibernate ORM Core Version: ${hibernateCoreVersion}" + println "Resolved Vert.x SQL client Version: ${vertxVersion}" + } +} + +// Make the version printing task run before tests and JavaExec tasks +tasks.withType( Test ).configureEach { + dependsOn printResolvedVersions +} + +tasks.withType( JavaExec ).configureEach { + dependsOn printResolvedVersions +} \ No newline at end of file diff --git a/local-build-plugins/src/main/groovy/hr-test-containers.gradle b/local-build-plugins/src/main/groovy/hr-test-containers.gradle new file mode 100644 index 000000000..fad66398f --- /dev/null +++ b/local-build-plugins/src/main/groovy/hr-test-containers.gradle @@ -0,0 +1,79 @@ +// Print a summary of the results of the tests (number of failures, successes and skipped) +def loggingSummary(db, result, desc) { + if ( !desc.parent ) { // will match the outermost suite + def output = "${db} results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)" + def repeatLength = output.length() + 1 + logger.lifecycle '\n' + ('-' * repeatLength) + '\n' + output + '\n' + ('-' * repeatLength) + } +} + +// Example: +// gradle test -Pdb=MySQL +test { + def selectedDb + + doFirst { + selectedDb = project.hasProperty( 'db' ) + ? project.properties['db'] + : 'PostgreSQL' + systemProperty 'db', selectedDb + } + afterSuite { desc, result -> + loggingSummary( selectedDb, result, desc ) + } +} + +// Configuration for the tests +tasks.withType( Test ).configureEach { + defaultCharacterEncoding = "UTF-8" + useJUnitPlatform() + testLogging { + showStandardStreams = project.hasProperty('showStandardOutput') + showStackTraces = true + exceptionFormat = 'full' + displayGranularity = 1 + events = ['PASSED', 'FAILED', 'SKIPPED'] + } + systemProperty 'docker', project.hasProperty( 'docker' ) ? 'true' : 'false' + systemProperty 'org.hibernate.reactive.common.InternalStateAssertions.ENFORCE', 'true' + + if ( project.hasProperty( 'includeTests' ) ) { + // Example: ./gradlew testAll -PincludeTests=DefaultPortTest + filter { + includeTestsMatching project.properties['includeTests'] ?: '*' as String + } + } +} + +def createTestDbTask(dbName) { + tasks.register( "testDb${dbName}", Test ) { + description = "Run tests for ${dbName}" + + doFirst() { + systemProperty 'db', dbName + } + afterSuite { desc, result -> + loggingSummary( dbName, result, desc ) + } + } +} + +// Rule to recognize calls to testDb +// and run the tests on the selected db +// Example: +// gradle testDbMySQL testDbDB2 +tasks.addRule( "Pattern testDb" ) { String taskName -> + if ( taskName.startsWith( "testDb" ) ) { + def dbName = taskName.substring( "testDb".length() ) + createTestDbTask( dbName ) + } +} + +// The dbs we want to test when running testAll +def dbs = ['MariaDB', 'MySQL', 'PostgreSQL', 'DB2', 'CockroachDB', 'MSSQLServer', 'Oracle'] +dbs.forEach { createTestDbTask it } + +tasks.register( "testAll", Test ) { + description = "Run tests for ${dbs}" + dependsOn = dbs.collect( [] as HashSet ) { db -> "testDb${db}" } +} diff --git a/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java index 36b5ae10a..883317d4a 100644 --- a/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java +++ b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java @@ -28,6 +28,7 @@ public class VersionsPlugin implements Plugin { public static final String SKIP_ORM_VERSION_PARSING = "skipOrmVersionParsing"; public static final String RELATIVE_FILE = "gradle/version.properties"; + public static final String RELATIVE_CATALOG = "gradle/libs.versions.toml"; @Override public void apply(Project project) { @@ -57,12 +58,13 @@ public void apply(Project project) { project.getLogger().lifecycle( "Development version: n/a" ); } - final String ormVersionString = determineOrmVersion( project ); + final VersionsTomlParser tomlParser = new VersionsTomlParser( project.getRootProject().file( RELATIVE_CATALOG ) ); + final String ormVersionString = determineOrmVersion( project, tomlParser ); final Object ormVersion = resolveOrmVersion( ormVersionString, project ); project.getLogger().lifecycle( "ORM version: {}", ormVersion ); project.getExtensions().add( ORM_VERSION, ormVersion ); - final Object ormPluginVersion = determineOrmPluginVersion( ormVersion, project ); + final Object ormPluginVersion = determineOrmPluginVersion( ormVersion, project, tomlParser ); project.getLogger().lifecycle( "ORM Gradle plugin version: {}", ormPluginVersion ); project.getExtensions().add( ORM_PLUGIN_VERSION, ormPluginVersion ); } @@ -123,10 +125,17 @@ private static void withInputStream(File file, Consumer action) { } } - private String determineOrmVersion(Project project) { + private String determineOrmVersion(Project project, VersionsTomlParser parser) { + // Check if it has been set in the project if ( project.hasProperty( ORM_VERSION ) ) { return (String) project.property( ORM_VERSION ); } + + // Check in the catalog + final String version = parser.read( ORM_VERSION ); + if ( version != null ) { + return version; + } throw new IllegalStateException( "Hibernate ORM version not specified on project" ); } @@ -138,10 +147,18 @@ private Object resolveOrmVersion(String stringForm, Project project) { return new ProjectVersion( stringForm ); } - private Object determineOrmPluginVersion(Object ormVersion, Project project) { + private Object determineOrmPluginVersion(Object ormVersion, Project project, VersionsTomlParser parser) { + // Check if it has been set in the project if ( project.hasProperty( ORM_PLUGIN_VERSION ) ) { return project.property( ORM_PLUGIN_VERSION ); } - return ormVersion; + + // Check in the catalog + final String version = parser.read( ORM_PLUGIN_VERSION ); + if ( version != null ) { + return version; + } + + throw new IllegalStateException( "Hibernate ORM Gradle plugin version not specified on project" ); } } diff --git a/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsTomlParser.java b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsTomlParser.java new file mode 100644 index 000000000..8833de6a6 --- /dev/null +++ b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsTomlParser.java @@ -0,0 +1,70 @@ +package org.hibernate.reactive.env; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Read the versions section of the library catalog + */ +public class VersionsTomlParser { + + private final Map data = new HashMap<>(); + + public VersionsTomlParser(File filePath) { + parse( filePath ); + } + + private void parse(File filePath) { + + try ( BufferedReader reader = new BufferedReader( new FileReader( filePath ) ) ) { + String line; + String currentSection = null; + while ( ( line = reader.readLine() ) != null ) { + line = line.trim(); + + // Skip comments and blank lines + if ( line.isEmpty() || line.startsWith( "#" ) ) { + continue; + } + + // Handle [section] + if ( line.startsWith( "[" ) && line.endsWith( "]" ) ) { + currentSection = line.substring( 1, line.length() - 1 ).trim(); + continue; + } + + if ( "versions".equalsIgnoreCase( currentSection ) ) { + // Handle key = value + int equalsIndex = line.indexOf( '=' ); + if ( equalsIndex == -1 ) { + continue; + } + + String key = line.substring( 0, equalsIndex ).trim(); + String value = line.substring( equalsIndex + 1 ).trim(); + + // Remove optional quotes around string values + if ( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) { + value = value.substring( 1, value.length() - 1 ); + } + + data.put( key, value ); + } + } + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + + /** + * Read the value of the property in the versions section of a toml file + */ + public String read(String property) { + return data.get( property ); + } +} diff --git a/podman.md b/podman.md index bc4d96ba7..9b9a71af2 100644 --- a/podman.md +++ b/podman.md @@ -158,7 +158,7 @@ and schema to run the tests: podman run --rm -e LICENSE=accept --privileged=true --group-add keep-groups \ --name HibernateTestingDB2 -e DBNAME=hreact -e DB2INSTANCE=hreact \ -e DB2INST1_PASSWORD=hreact -e PERSISTENT_HOME=false -e ARCHIVE_LOGS=false \ - -e AUTOCONFIG=false -p 50000:50000 docker.io/icr.io/db2_community/db2:12.1.0.0 + -e AUTOCONFIG=false -p 50000:50000 icr.io/db2_community/db2:12.1.2.0 ``` When the database has started, you can run the tests on Db2 with: diff --git a/publish.gradle b/publish.gradle index f77b26959..6d05724fa 100644 --- a/publish.gradle +++ b/publish.gradle @@ -21,7 +21,7 @@ jar { 'Implementation-Version': project.version, 'Implementation-Vendor': 'Hibernate.org', 'Implementation-Vendor-Id': 'org.hibernate', - 'Implementation-Url': 'http://hibernate.org/reactive', + 'Implementation-Url': 'https://hibernate.org/reactive', ) } } @@ -50,7 +50,7 @@ publishing { license { name = 'Apache License Version 2.0' url = 'https://opensource.org/licenses/Apache-2.0' - comments = 'See discussion at http://hibernate.org/community/license/ for more details.' + comments = 'See discussion at https://hibernate.org/community/license/ for more details.' distribution = 'repo' } } @@ -81,7 +81,7 @@ publishing { } maven { name = 'snapshots' - url = "https://oss.sonatype.org/content/repositories/snapshots/" + url = "https://central.sonatype.com/repository/maven-snapshots/" // So that Gradle uses the `ORG_GRADLE_PROJECT_snapshotsPassword` / `ORG_GRADLE_PROJECT_snapshotsUsername` // env variables to read the username/password for the `snapshots` repository publishing: credentials(PasswordCredentials) diff --git a/release/build.gradle b/release/build.gradle index 690534750..4e5321f9a 100644 --- a/release/build.gradle +++ b/release/build.gradle @@ -1,5 +1,8 @@ import java.nio.charset.StandardCharsets +plugins { + id "hr-java-library" +} ext { // Select which repository to use for publishing the documentation // Example: diff --git a/settings.gradle b/settings.gradle index e997f3e73..f1c542d37 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,6 +19,44 @@ gradle.ext.baselineJavaVersion = JavaLanguageVersion.of( 17 ) // You can't use bytecode higher than what Gradle supports, even with toolchains. def GRADLE_MAX_SUPPORTED_BYTECODE_VERSION = 23 +// This overrides the default version catalog in gradle/libs.versions.toml, which can be +// useful to monitor compatibility for upcoming versions on CI: +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + // ./gradlew build -PhibernateOrmVersion=7.1.0.Final + def hibernateOrmVersion = settings.ext.find("hibernateOrmVersion") ?: "" + if ( hibernateOrmVersion != "" ) { + version("hibernateOrmVersion", hibernateOrmVersion) + } + + // ./gradlew build -PhibernateOrmGradlePluginVersion=7.1.0.Final + def hibernateOrmGradlePluginVersion = settings.ext.find("hibernateOrmGradlePluginVersion") ?: "" + if ( hibernateOrmGradlePluginVersion ) { + version("hibernateOrmGradlePluginVersion", hibernateOrmGradlePluginVersion) + } + + // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT + def vertxSqlClientVersion = settings.ext.find("vertxSqlClientVersion") ?: "" + if ( vertxSqlClientVersion ) { + version("vertxSqlClientVersion", vertxSqlClientVersion) + } + + // ./gradlew build -PvertxWebVersion=4.0.0-SNAPSHOT + def vertxWebVersion = settings.ext.find("vertxWebVersion") ?: vertxSqlClientVersion + if ( vertxWebVersion ) { + version("vertxWebVersion", vertxWebVersion) + } + + // ./gradlew build -PvertxWebClientVersion=4.0.0-SNAPSHOT + def vertxWebClientVersion = settings.ext.find("vertxWebClientVersion") ?: vertxSqlClientVersion + if ( vertxWebClientVersion ) { + version("vertxWebClientVersion", vertxWebClientVersion) + } + } + } +} + // If either 'main.jdk.version' or 'test.jdk.version' is set, enable the toolchain and use the selected jdk. // If only one property is set, the other defaults to the baseline Java version (8). // Note that when toolchain is enabled, you also need to specify diff --git a/tooling/docker/README.md b/tooling/docker/README.md new file mode 100644 index 000000000..4de2451e0 --- /dev/null +++ b/tooling/docker/README.md @@ -0,0 +1,6 @@ +Our test suite will only read the first FROM instruction from each Dockerfile to extract the base image and the version +of the container to run. It will ignore everything else. + +The reason we have these files is that we want to automate the upgrade of the containers using dependabot. + +See the class `DockerImage`. diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile new file mode 100644 index 000000000..3783d7f28 --- /dev/null +++ b/tooling/docker/cockroachdb.Dockerfile @@ -0,0 +1,3 @@ +# CockroachDB +# See https://hub.docker.com/r/cockroachdb/cockroach +FROM docker.io/cockroachdb/cockroach:v25.2.4 diff --git a/tooling/docker/db2.Dockerfile b/tooling/docker/db2.Dockerfile new file mode 100644 index 000000000..b70ff8a80 --- /dev/null +++ b/tooling/docker/db2.Dockerfile @@ -0,0 +1,3 @@ +# IBM DB2 +# See https://hub.docker.com/r/ibmcom/db2 +FROM icr.io/db2_community/db2:12.1.2.0 diff --git a/tooling/docker/maria.Dockerfile b/tooling/docker/maria.Dockerfile new file mode 100644 index 000000000..fc954df6e --- /dev/null +++ b/tooling/docker/maria.Dockerfile @@ -0,0 +1,3 @@ +# MariaDB +# See https://hub.docker.com/_/mariadb +FROM docker.io/mariadb:11.8.2 diff --git a/tooling/docker/mysql.Dockerfile b/tooling/docker/mysql.Dockerfile new file mode 100644 index 000000000..897fbd5ee --- /dev/null +++ b/tooling/docker/mysql.Dockerfile @@ -0,0 +1,3 @@ +# MySQL +# See https://hub.docker.com/_/mysql +FROM container-registry.oracle.com/mysql/community-server:9.4.0 diff --git a/tooling/docker/oracle.Dockerfile b/tooling/docker/oracle.Dockerfile new file mode 100644 index 000000000..7dfc6447d --- /dev/null +++ b/tooling/docker/oracle.Dockerfile @@ -0,0 +1,3 @@ +# Oracle Database Free +# See https://hub.docker.com/r/gvenzl/oracle-free +FROM docker.io/gvenzl/oracle-free:23-slim-faststart diff --git a/tooling/docker/postgresql.Dockerfile b/tooling/docker/postgresql.Dockerfile new file mode 100644 index 000000000..fb36f48e8 --- /dev/null +++ b/tooling/docker/postgresql.Dockerfile @@ -0,0 +1,3 @@ +# PostgreSQL +# See https://hub.docker.com/_/postgres +FROM docker.io/postgres:17.5 diff --git a/tooling/docker/sqlserver.Dockerfile b/tooling/docker/sqlserver.Dockerfile new file mode 100644 index 000000000..94fae1429 --- /dev/null +++ b/tooling/docker/sqlserver.Dockerfile @@ -0,0 +1,3 @@ +# Microsoft SQL Server +# See https://hub.docker.com/_/microsoft-mssql-server +FROM mcr.microsoft.com/mssql/server:2025-latest diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 174bf6858..8a002701f 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -5,12 +5,12 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:5.0.2} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.2} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:cockroachdb:1.21.0 +//DEPS org.testcontainers:cockroachdb:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute index 0bb807c65..4d39e5ee3 100755 --- a/tooling/jbang/Db2ReactiveTest.java.qute +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -5,12 +5,12 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} +//DEPS io.vertx:vertx-db2-client:$\{vertx.version:5.0.2} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.2} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:db2:1.21.0 +//DEPS org.testcontainers:db2:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 import jakarta.persistence.Entity; diff --git a/tooling/jbang/Example.java b/tooling/jbang/Example.java index 3a287c0b5..145f1a625 100644 --- a/tooling/jbang/Example.java +++ b/tooling/jbang/Example.java @@ -5,11 +5,11 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.15} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.15} -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.15} -//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:3.0.0.Beta1} +//DEPS com.ongres.scram:scram-client:3.1 +//DEPS io.vertx:vertx-pg-client:${vertx.version:5.0.2} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:5.0.2} +//DEPS io.vertx:vertx-db2-client:${vertx.version:5.0.2} +//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:4.0.0.Final} //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index ea4e87521..658d93f5d 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -5,12 +5,12 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:5.0.2} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.2} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:mariadb:1.21.0 +//DEPS org.testcontainers:mariadb:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index 51e028782..f4c2c35d1 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -5,12 +5,12 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:5.0.2} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.2} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:mysql:1.21.0 +//DEPS org.testcontainers:mysql:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container @@ -72,7 +72,7 @@ public class {baseName} { } @ClassRule - public final static MySQLContainer database = new MySQLContainer<>( imageName( "docker.io", "mysql", "9.2.0" ) ); + public final static MySQLContainer database = new MySQLContainer<>( imageName( "container-registry.oracle.com", "mysql/community-server", "9.3.0" ).asCompatibleSubstituteFor( "mysql" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index e75486fff..5762e7e6b 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -5,12 +5,12 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} -//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Beta1} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:5.0.2} +//DEPS io.vertx:vertx-unit:$\{vertx.version:5.0.2} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:4.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:postgresql:1.21.0 +//DEPS org.testcontainers:postgresql:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: //DEPS com.ongres.scram:client:2.1 diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 9d3759fce..9e931e52e 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -5,19 +5,19 @@ */ ///usr/bin/env jbang "$0" "$@" ; exit $? -//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.15} -//DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.15} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:${vertx.version:4.5.15} -//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:3.0.0.Beta1} +//DEPS io.vertx:vertx-pg-client:${vertx.version:5.0.2} +//DEPS com.ongres.scram:scram-client:3.1 +//DEPS io.vertx:vertx-db2-client:${vertx.version:5.0.2} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:5.0.2} +//DEPS io.vertx:vertx-unit:${vertx.version:5.0.2} +//DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:4.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:postgresql:1.21.0 -//DEPS org.testcontainers:mysql:1.21.0 -//DEPS org.testcontainers:db2:1.21.0 -//DEPS org.testcontainers:mariadb:1.21.0 -//DEPS org.testcontainers:cockroachdb:1.21.0 +//DEPS org.testcontainers:postgresql:1.21.3 +//DEPS org.testcontainers:mysql:1.21.3 +//DEPS org.testcontainers:db2:1.21.3 +//DEPS org.testcontainers:mariadb:1.21.3 +//DEPS org.testcontainers:cockroachdb:1.21.3 // //// Testcontainer needs the JDBC drivers to start the containers //// Hibernate Reactive doesn't use them @@ -229,7 +229,7 @@ public String toString() { */ enum Database { POSTGRESQL( () -> new PostgreSQLContainer( "postgres:17.5" ) ), - MYSQL( () -> new MySQLContainer( "mysql:9.2.0" ) ), + MYSQL( () -> new MySQLContainer( "container-registry.oracle.com/mysql/community-server:9.3.0" ) ), DB2( () -> new Db2Container( "docker.io/icr.io/db2_community/db2:12.1.0.0" ).acceptLicense() ), MARIADB( () -> new MariaDBContainer( "mariadb:11.7.2" ) ), COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v24.3.13" ) );