diff --git a/.github/workflows/atlas.yml b/.github/workflows/atlas.yml index ca283219955f..fcda22d3914e 100644 --- a/.github/workflows/atlas.yml +++ b/.github/workflows/atlas.yml @@ -7,9 +7,6 @@ name: Hibernate ORM build-Atlas on: - push: - branches: - - '6.2' pull_request: branches: - '6.2' @@ -52,7 +49,7 @@ jobs: RUNID: ${{ github.run_number }} run: ci/database-start.sh - name: Set up Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' diff --git a/.github/workflows/contributor-build.yml b/.github/workflows/contributor-build.yml index ae825e8efe21..5a527fd2ee16 100644 --- a/.github/workflows/contributor-build.yml +++ b/.github/workflows/contributor-build.yml @@ -7,9 +7,6 @@ name: Hibernate ORM build on: - push: - branches: - - '6.2' pull_request: branches: - '6.2' @@ -61,7 +58,7 @@ jobs: RDBMS: ${{ matrix.rdbms }} run: ci/database-start.sh - name: Set up Java 11 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '11' diff --git a/Jenkinsfile b/Jenkinsfile index 1d7fb66f8637..77d4d3116c23 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -63,6 +63,12 @@ if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { currentBuild.result = 'NOT_BUILT' return } +// This is a limited maintenance branch, so don't run this on pushes to the branch, only on PRs +if ( !env.CHANGE_ID ) { + print "INFO: Build skipped because this job should only run for pull request, not for branch pushes" + currentBuild.result = 'NOT_BUILT' + return +} stage('Build') { Map executions = [:] diff --git a/ci/jpa-3.1-tck.Jenkinsfile b/ci/jpa-3.1-tck.Jenkinsfile index 42c111fbe675..b7977967fb09 100644 --- a/ci/jpa-3.1-tck.Jenkinsfile +++ b/ci/jpa-3.1-tck.Jenkinsfile @@ -6,6 +6,12 @@ if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { currentBuild.result = 'NOT_BUILT' return } +// This is a limited maintenance branch, so don't run this on pushes to the branch, only on PRs +if ( !env.CHANGE_ID ) { + print "INFO: Build skipped because this job should only run for pull request, not for branch pushes" + currentBuild.result = 'NOT_BUILT' + return +} pipeline { agent { diff --git a/ci/quarkus.Jenkinsfile b/ci/quarkus.Jenkinsfile index 9b4ddfba04b7..ae7abb549df0 100644 --- a/ci/quarkus.Jenkinsfile +++ b/ci/quarkus.Jenkinsfile @@ -6,6 +6,12 @@ if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { currentBuild.result = 'NOT_BUILT' return } +// This is a limited maintenance branch, so don't run this on pushes to the branch, only on PRs +if ( !env.CHANGE_ID ) { + print "INFO: Build skipped because this job should only run for pull request, not for branch pushes" + currentBuild.result = 'NOT_BUILT' + return +} pipeline { agent { diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile deleted file mode 100644 index be02d00d2ee7..000000000000 --- a/ci/snapshot-publish.Jenkinsfile +++ /dev/null @@ -1,60 +0,0 @@ -/* - * See https://github.com/hibernate/hibernate-jenkins-pipeline-helpers - */ -@Library('hibernate-jenkins-pipeline-helpers@1.5') _ - -// Avoid running the pipeline on branch indexing -if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { - print "INFO: Build skipped due to trigger being Branch Indexing" - currentBuild.result = 'NOT_BUILT' - return -} - -pipeline { - agent { - label 'Fedora' - } - tools { - jdk 'OpenJDK 11 Latest' - } - options { - rateLimitBuilds(throttle: [count: 1, durationName: 'hour', userBoost: true]) - buildDiscarder(logRotator(numToKeepStr: '3', artifactNumToKeepStr: '3')) - disableConcurrentBuilds(abortPrevious: true) - } - stages { - stage('Checkout') { - steps { - checkout scm - } - } - stage('Publish') { - steps { - withCredentials([ - usernamePassword(credentialsId: 'ossrh.sonatype.org', usernameVariable: 'hibernatePublishUsername', passwordVariable: 'hibernatePublishPassword'), - usernamePassword(credentialsId: 'plugins.gradle.org', usernameVariable: 'hibernatePluginPortalUsername', passwordVariable: 'hibernatePluginPortalPassword'), - string(credentialsId: 'ge.hibernate.org-access-key', variable: 'GRADLE_ENTERPRISE_ACCESS_KEY'), - string(credentialsId: 'release.gpg.passphrase', variable: 'SIGNING_PASS'), - file(credentialsId: 'release.gpg.private-key', variable: 'SIGNING_KEYRING') - ]) { - sh '''./gradlew clean publish \ - -PhibernatePublishUsername=$hibernatePublishUsername \ - -PhibernatePublishPassword=$hibernatePublishPassword \ - -Pgradle.publish.key=$hibernatePluginPortalUsername \ - -Pgradle.publish.secret=$hibernatePluginPortalPassword \ - --no-scan \ - -DsigningPassword=$SIGNING_PASS \ - -DsigningKeyFile=$SIGNING_KEYRING \ - ''' - } - } - } - } - post { - always { - configFileProvider([configFile(fileId: 'job-configuration.yaml', variable: 'JOB_CONFIGURATION_FILE')]) { - notifyBuildResult maintainers: (String) readYaml(file: env.JOB_CONFIGURATION_FILE).notification?.email?.recipients - } - } - } -} \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java index d3ec9eb4c10e..688193959229 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java @@ -36,6 +36,7 @@ import org.hibernate.metamodel.RepresentationMode; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.MappingModelHelper; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.model.domain.internal.AttributeContainer; import org.hibernate.metamodel.model.domain.internal.DomainModelHelper; import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; @@ -206,19 +207,45 @@ private boolean isCompatible(PersistentAttribute attribute1, PersistentAtt return true; } final MappingMetamodel runtimeMetamodels = jpaMetamodel().getMappingMetamodel(); - final EntityMappingType entity1 = runtimeMetamodels.getEntityDescriptor( - attribute1.getDeclaringType().getTypeName() + final ModelPart modelPart1 = getEntityAttributeModelPart( + attribute1, + attribute1.getDeclaringType(), + runtimeMetamodels ); - final EntityMappingType entity2 = runtimeMetamodels.getEntityDescriptor( - attribute2.getDeclaringType().getTypeName() + final ModelPart modelPart2 = getEntityAttributeModelPart( + attribute2, + attribute2.getDeclaringType(), + runtimeMetamodels ); - - return entity1 != null && entity2 != null && MappingModelHelper.isCompatibleModelPart( - entity1.findSubPart( attribute1.getName() ), - entity2.findSubPart( attribute2.getName() ) + return modelPart1 != null && modelPart2 != null && MappingModelHelper.isCompatibleModelPart( + modelPart1, + modelPart2 ); } + private static ModelPart getEntityAttributeModelPart( + PersistentAttribute attribute, + ManagedDomainType domainType, + MappingMetamodel mappingMetamodel) { + if ( domainType instanceof EntityDomainType ) { + final EntityMappingType entity = mappingMetamodel.getEntityDescriptor( domainType.getTypeName() ); + return entity.findSubPart( attribute.getName() ); + } + else { + ModelPart candidate = null; + for ( ManagedDomainType subType : domainType.getSubTypes() ) { + final ModelPart modelPart = getEntityAttributeModelPart( attribute, subType, mappingMetamodel ); + if ( modelPart != null ) { + if ( candidate != null && !MappingModelHelper.isCompatibleModelPart( candidate, modelPart ) ) { + return null; + } + candidate = modelPart; + } + } + return candidate; + } + } + @Override public PersistentAttribute findAttributeInSuperTypes(String name) { final PersistentAttribute local = findDeclaredAttribute( name ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/MappedSuperclassAttributeInMultipleSubtypesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/MappedSuperclassAttributeInMultipleSubtypesTest.java new file mode 100644 index 000000000000..db4108957408 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/MappedSuperclassAttributeInMultipleSubtypesTest.java @@ -0,0 +1,170 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.query; + +import java.util.List; + +import org.hibernate.testing.orm.domain.gambit.BasicEntity; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * @author Marco Belladelli + */ +@DomainModel( annotatedClasses = { + BasicEntity.class, + MappedSuperclassAttributeInMultipleSubtypesTest.BaseEntity.class, + MappedSuperclassAttributeInMultipleSubtypesTest.MappedSuper.class, + MappedSuperclassAttributeInMultipleSubtypesTest.ChildOne.class, + MappedSuperclassAttributeInMultipleSubtypesTest.ChildTwo.class, +} ) +@SessionFactory +@Jira( "https://hibernate.atlassian.net/browse/HHH-17491" ) +public class MappedSuperclassAttributeInMultipleSubtypesTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final BasicEntity basicEntity = new BasicEntity( 1, "basic" ); + session.persist( basicEntity ); + session.persist( new ChildOne( 1L, "test", 1, basicEntity ) ); + session.persist( new ChildTwo( 2L, "test", 1D, basicEntity ) ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from BaseEntity" ).executeUpdate(); + session.createMutationQuery( "delete from BasicEntity" ).executeUpdate(); + } ); + } + + @Test + public void testSameTypeAttribute(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final List resultList = session.createQuery( + "from BaseEntity e where e.stringProp = 'test'", + BaseEntity.class + ).getResultList(); + assertThat( resultList ).hasSize( 2 ); + assertThat( resultList.stream().map( BaseEntity::getId ) ).contains( 1L, 2L ); + } ); + } + + @Test + public void testDifferentTypeAttribute(SessionFactoryScope scope) { + scope.inTransaction( session -> { + try { + session.createQuery( + "from BaseEntity e where e.otherProp = 1", + BaseEntity.class + ).getResultList(); + fail( "This shouldn't work since the attribute is defined with different types" ); + } + catch (Exception e) { + final Throwable cause = e.getCause().getCause(); + assertThat( cause ).isInstanceOf( IllegalArgumentException.class ); + assertThat( cause.getMessage() ).contains( "Could not resolve attribute 'otherProp'" ); + } + } ); + } + + @Test + public void testToOneAttribute(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final BasicEntity basicEntity = session.find( BasicEntity.class, 1 ); + final List resultList = session.createQuery( + "from BaseEntity e where e.toOneProp = :be", + BaseEntity.class + ).setParameter( "be", basicEntity ).getResultList(); + assertThat( resultList ).hasSize( 2 ); + assertThat( resultList.stream().map( BaseEntity::getId ) ).contains( 1L, 2L ); + } ); + } + + @Entity( name = "BaseEntity" ) + public static class BaseEntity { + @Id + private Long id; + + public BaseEntity() { + } + + public BaseEntity(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + } + + @MappedSuperclass + @SuppressWarnings( "unused" ) + public static abstract class MappedSuper extends BaseEntity { + private String stringProp; + + private Integer otherProp; + + @ManyToOne + private BasicEntity toOneProp; + + public MappedSuper() { + } + + public MappedSuper(Long id, String stringProp, Integer otherProp, BasicEntity toOneProp) { + super( id ); + this.stringProp = stringProp; + this.otherProp = otherProp; + this.toOneProp = toOneProp; + } + } + + @Entity( name = "ChildOne" ) + public static class ChildOne extends MappedSuper { + public ChildOne() { + } + + public ChildOne(Long id, String stringProp, Integer otherProp, BasicEntity toOneProp) { + super( id, stringProp, otherProp, toOneProp ); + } + } + + @Entity( name = "ChildTwo" ) + @SuppressWarnings( "unused" ) + public static class ChildTwo extends BaseEntity { + private String stringProp; + + private Double otherProp; + + @ManyToOne + private BasicEntity toOneProp; + + public ChildTwo() { + } + + public ChildTwo(Long id, String stringProp, Double otherProp, BasicEntity toOneProp) { + super( id ); + this.stringProp = stringProp; + this.otherProp = otherProp; + this.toOneProp = toOneProp; + } + } +}