From 6ebfb9f69ed3a6ceb71f4465ed3cd9753b4882f1 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Mon, 19 May 2025 23:25:59 +0000 Subject: [PATCH 01/56] Post-steps for release : `7.0.0.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 4c241f1ad969..36545aa38b6d 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=7.0.0.Final \ No newline at end of file +hibernateVersion=7.0.1-SNAPSHOT \ No newline at end of file From 4122964722521d3fb0b7748e1103465dd4dbd3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20NO=C3=8BL?= Date: Mon, 19 May 2025 10:33:47 +0200 Subject: [PATCH 02/56] HHH-19472: native queries can return Object[] --- .../jpa/spi/NativeQueryArrayTransformer.java | 22 +++ .../query/sql/internal/NativeQueryImpl.java | 6 +- .../hql/SingleSelectionArrayResultTest.java | 173 ++++++++++++------ 3 files changed, 145 insertions(+), 56 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryArrayTransformer.java diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryArrayTransformer.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryArrayTransformer.java new file mode 100644 index 000000000000..461c100d6ff0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryArrayTransformer.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.jpa.spi; + +import org.hibernate.query.TupleTransformer; + +/** + * A {@link TupleTransformer} for handling {@code Object[]} results from native queries. + * + * @since 7.0 + */ +public class NativeQueryArrayTransformer implements TupleTransformer { + + public static final NativeQueryArrayTransformer INSTANCE = new NativeQueryArrayTransformer(); + + @Override + public Object[] transformTuple(Object[] tuple, String[] aliases) { + return tuple; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index 0c7ab0380387..07e3535da8ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -25,6 +25,7 @@ import org.hibernate.FlushMode; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.spi.NativeQueryArrayTransformer; import org.hibernate.jpa.spi.NativeQueryConstructorTransformer; import org.hibernate.jpa.spi.NativeQueryListTransformer; import org.hibernate.jpa.spi.NativeQueryMapTransformer; @@ -377,7 +378,10 @@ else if ( Map.class.equals( resultClass ) ) { else if ( List.class.equals( resultClass ) ) { return NativeQueryListTransformer.INSTANCE; } - else if ( resultClass != Object.class && resultClass != Object[].class ) { + else if ( Object[].class.equals( resultClass )) { + return NativeQueryArrayTransformer.INSTANCE; + } + else if ( resultClass != Object.class ) { // TODO: this is extremely fragile and probably a bug if ( isClass( resultClass ) && !hasJavaTypeDescriptor( resultClass ) ) { // not a basic type, so something we can attempt diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/SingleSelectionArrayResultTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/SingleSelectionArrayResultTest.java index 0a3577b076e1..e99c9b54d8b6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/hql/SingleSelectionArrayResultTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/SingleSelectionArrayResultTest.java @@ -4,81 +4,144 @@ */ package org.hibernate.orm.test.hql; +import java.util.stream.Stream; + +import org.hibernate.dialect.H2Dialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.query.NativeQuery; +import org.hibernate.query.Query; +import org.hibernate.query.SelectionQuery; + 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.RequiresDialect; +import org.hibernate.testing.orm.junit.RequiresDialects; 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 org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import static org.assertj.core.api.Assertions.assertThat; /** * @author Marco Belladelli */ -@DomainModel( annotatedClasses = BasicEntity.class ) +@DomainModel(annotatedClasses = BasicEntity.class) @SessionFactory -@Jira( "https://hibernate.atlassian.net/browse/HHH-18450" ) +@Jira("https://hibernate.atlassian.net/browse/HHH-18450") +@Jira("https://hibernate.atlassian.net/browse/HHH-19472") +@RequiresDialects({@RequiresDialect(H2Dialect.class), @RequiresDialect(PostgreSQLDialect.class)}) public class SingleSelectionArrayResultTest { - @Test - public void testArrayResult(SessionFactoryScope scope) { + + static class TestArguments implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + Arguments.of( "select 1", null, null ), + Arguments.of( "select cast(1 as integer)", null, null ), + Arguments.of( "select id from BasicEntity", null, null ), + Arguments.of( "select cast(id as integer) from BasicEntity", null, null ), + Arguments.of( "select ?1", 1, 1 ), + Arguments.of( "select :p1", "p1", 1 ), + Arguments.of( "select cast(?1 as integer)", 1, 1 ), + Arguments.of( "select cast(:p1 as integer)", "p1", 1 ) + ); + } + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testQueryObjectResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { + scope.inTransaction( session -> { + Query query = session.createQuery( ql, Object.class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); + } ); + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testNativeQueryObjectResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { + scope.inTransaction( session -> { + NativeQuery query = session.createNativeQuery( ql, Object.class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); + } ); + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testSelectionQueryObjectResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { + scope.inTransaction( session -> { + SelectionQuery query = session.createSelectionQuery( ql, Object.class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); + } ); + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testQueryArrayResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { + scope.inTransaction( session -> { + Query query = session.createQuery( ql, Object[].class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).containsExactly( 1 ); + } ); + } + + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testNativeQueryArrayResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery( - "select 1", - Object[].class - ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createQuery( - "select cast(1 as integer)", - Object[].class - ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createSelectionQuery( - "select id from BasicEntity", - Object[].class - ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createSelectionQuery( - "select cast(id as integer) from BasicEntity", - Object[].class - ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createSelectionQuery( - "select ?1", - Object[].class - ).setParameter( 1, 1 ).getSingleResult() ).containsExactly( 1 ); - assertThat( session.createQuery( - "select cast(:p1 as integer)", - Object[].class - ).setParameter( "p1", 1 ).getSingleResult() ).containsExactly( 1 ); + NativeQuery query = session.createNativeQuery( ql, Object[].class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).containsExactly( 1 ); } ); } - @Test - public void testNormalResult(SessionFactoryScope scope) { + @ParameterizedTest + @ArgumentsSource(TestArguments.class) + public void testSelectionQueryArrayResult(String ql, Object arg1, Object arg2, SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery( - "select 1", - Object.class - ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createQuery( - "select cast(1 as integer)", - Object.class - ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createSelectionQuery( - "select id from BasicEntity", - Object.class - ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createSelectionQuery( - "select cast(id as integer) from BasicEntity", - Object.class - ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createSelectionQuery( - "select ?1", - Object.class - ).setParameter( 1, 1 ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); - assertThat( session.createQuery( - "select cast(:p1 as integer)", - Object.class - ).setParameter( "p1", 1 ).getSingleResult() ).isInstanceOf( Integer.class ).isEqualTo( 1 ); + SelectionQuery query = session.createSelectionQuery( ql, Object[].class ); + if ( arg1 instanceof Integer ) { + query.setParameter( (Integer) arg1, arg2 ); + } + if ( arg1 instanceof String ) { + query.setParameter( (String) arg1, arg2 ); + } + assertThat( query.getSingleResult() ).containsExactly( 1 ); } ); } From 28bcb12e5de97bab441d7494bf443a1d62d4b520 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Tue, 20 May 2025 08:46:16 +0200 Subject: [PATCH 03/56] Update ORM version in the changelog --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index 24a7e4e2f678..b47823ee7033 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,4 @@ -Hibernate 6 Changelog +Hibernate 7 Changelog ======================= Note: Please refer to JIRA to learn more about each issue. From 0613466463b5a5d7c567ce20450bf3ffd47d9285 Mon Sep 17 00:00:00 2001 From: Peter Bambazek Date: Sat, 17 May 2025 19:28:54 +0200 Subject: [PATCH 04/56] HHH-18813: Fix of generated Insert-Query in CteUpdateHandler --- .../internal/cte/CteUpdateHandler.java | 6 +- .../orm/test/secondarytable/HHH18813Test.java | 120 ++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/secondarytable/HHH18813Test.java diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java index 28c82a7b5384..749706d28ec4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteUpdateHandler.java @@ -153,8 +153,8 @@ protected void addDmlCtes( tableExpression, true ); - final List assignmentList = assignmentsByTable.get( updatingTableReference ); - if ( assignmentList == null ) { + final List assignmentsForInsert = assignmentsByTable.get( updatingTableReference ); + if ( assignmentsForInsert == null ) { continue; } final String insertCteTableName = getInsertCteTableName( tableExpression ); @@ -229,7 +229,7 @@ protected void addDmlCtes( // Collect the target column references from the key expressions final List targetColumnReferences = new ArrayList<>( existsKeyColumns ); // And transform assignments to target column references and selections - for ( Assignment assignment : assignments ) { + for ( Assignment assignment : assignmentsForInsert ) { targetColumnReferences.addAll( assignment.getAssignable().getColumnReferences() ); querySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/secondarytable/HHH18813Test.java b/hibernate-core/src/test/java/org/hibernate/orm/test/secondarytable/HHH18813Test.java new file mode 100644 index 000000000000..741bd8bf3199 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/secondarytable/HHH18813Test.java @@ -0,0 +1,120 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.secondarytable; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.SecondaryTable; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +/** + * Test for a Bugfix described in HHH-18813. + * The CteUpdateHandler generated an Insert-Query, + * which contained columns that do not exist in the target table. + * + * @author Peter Bambazek + */ +@JiraKey(value = "HHH-18813") +@DomainModel( + annotatedClasses = {HHH18813Test.SecondaryTableEntitySub.class, HHH18813Test.SecondaryTableEntityBase.class}) +@SessionFactory +class HHH18813Test { + + @Test + void hhh18813Test(SessionFactoryScope scope) { + + // prepare + scope.inTransaction( session -> { + SecondaryTableEntitySub entitySub = new SecondaryTableEntitySub(); + entitySub.setB( 111L ); + entitySub.setC( 222L ); + session.persist( entitySub ); + } ); + + // asset before + scope.inTransaction( session -> { + SecondaryTableEntitySub entitySub = session.createQuery( + "select s from SecondaryTableEntitySub s", + SecondaryTableEntitySub.class ).getSingleResult(); + assertNotNull( entitySub ); + assertEquals( 111L, entitySub.getB() ); + assertEquals( 222L, entitySub.getC() ); + } ); + + // update + scope.inTransaction( session -> { + session.createMutationQuery( "update SecondaryTableEntitySub e set e.b=:b, e.c=:c" ) + .setParameter( "b", 333L ) + .setParameter( "c", 444L ) + .executeUpdate(); + } ); + + // asset after + scope.inTransaction( session -> { + SecondaryTableEntitySub entitySub = session.createQuery( "select s from SecondaryTableEntitySub s", + SecondaryTableEntitySub.class ).getSingleResult(); + assertNotNull( entitySub ); + assertEquals( 333L, entitySub.getB() ); + assertEquals( 444L, entitySub.getC() ); + } ); + } + + @Entity(name = "SecondaryTableEntitySub") + @Inheritance(strategy = InheritanceType.JOINED) + @SecondaryTable(name = "test") + public static class SecondaryTableEntitySub extends SecondaryTableEntityBase { + + @Column + private Long b; + + @Column(table = "test") + private Long c; + + public Long getB() { + return b; + } + + public void setB(Long b) { + this.b = b; + } + + public Long getC() { + return c; + } + + public void setC(Long c) { + this.c = c; + } + } + + @Entity + @Inheritance(strategy = InheritanceType.JOINED) + public static class SecondaryTableEntityBase { + + @Id + @GeneratedValue + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } +} From 0be538a5af82f3930339e120803843559a219f6e Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 29 Apr 2025 17:40:54 +0200 Subject: [PATCH 05/56] HHH-19394 disable transient reference checking for readonly entities --- .../event/internal/AbstractFlushingEventListener.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index c92b4850c2a9..926bd5095d0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -147,7 +147,7 @@ void checkForTransientReferences(EventSource session, PersistenceContext persist // into Nullability, instead of abusing the Cascade infrastructure) for ( Map.Entry me : persistenceContext.reentrantSafeEntityEntries() ) { final EntityEntry entry = me.getValue(); - if ( flushable( entry ) ) { + if ( checkable( entry ) ) { Cascade.cascade( CascadingActions.CHECK_ON_FLUSH, CascadePoint.BEFORE_FLUSH, @@ -167,6 +167,12 @@ private static boolean flushable(EntityEntry entry) { || status == Status.READ_ONLY; // debatable, see HHH-19398 } + private static boolean checkable(EntityEntry entry) { + final Status status = entry.getStatus(); + return status == Status.MANAGED + || status == Status.SAVING; + } + private void cascadeOnFlush(EventSource session, EntityPersister persister, Object object, PersistContext anything) throws HibernateException { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); From 7f72e572268a6ab7c523cce091a56d28d14e4cde Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 29 Apr 2025 17:45:01 +0200 Subject: [PATCH 06/56] use var to eliminate some especially horrible local type declarations --- .../internal/StatefulPersistenceContext.java | 7 +++-- .../AbstractFlushingEventListener.java | 28 +++++++++---------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 852404e3e729..4cea1e3fccae 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -55,6 +55,7 @@ import org.hibernate.event.spi.PostLoadEventListener; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.InstanceIdentityMap; +import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -1513,7 +1514,7 @@ public Entry[] reentrantSafeEntityEntries() { public Object getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap) { final String collectionRole = entityName + '.' + propertyName; - final MappingMetamodelImplementor mappingMetamodel = session.getFactory().getMappingMetamodel(); + final MappingMetamodel mappingMetamodel = session.getFactory().getMappingMetamodel(); final EntityPersister persister = mappingMetamodel.getEntityDescriptor( entityName ); final CollectionPersister collectionPersister = mappingMetamodel.getCollectionDescriptor( collectionRole ); @@ -1534,7 +1535,7 @@ && isFoundInParent( propertyName, childEntity, persister, collectionPersister, p //not found in case, proceed // iterate all the entities currently associated with the persistence context. - for ( Entry me : reentrantSafeEntityEntries() ) { + for ( var me : reentrantSafeEntityEntries() ) { final EntityEntry entityEntry = me.getValue(); // does this entity entry pertain to the entity persister in which we are interested (owner)? if ( persister.isSubclassEntityName( entityEntry.getEntityName() ) ) { @@ -1680,7 +1681,7 @@ public Object getIndexInOwner(String entity, String property, Object childEntity } //Not found in cache, proceed - for ( Entry me : reentrantSafeEntityEntries() ) { + for ( var me : reentrantSafeEntityEntries() ) { final EntityEntry ee = me.getValue(); if ( persister.isSubclassEntityName( ee.getEntityName() ) ) { final Object instance = me.getKey(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index 926bd5095d0a..319e45d7c66a 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -5,7 +5,6 @@ package org.hibernate.event.internal; import java.lang.invoke.MethodHandles; -import java.util.Map; import org.hibernate.HibernateException; import org.hibernate.Interceptor; @@ -16,7 +15,6 @@ import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; -import org.hibernate.engine.internal.Collections; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.CascadingActions; @@ -39,6 +37,7 @@ import org.jboss.logging.Logger; +import static org.hibernate.engine.internal.Collections.processUnreachableCollection; import static org.hibernate.engine.internal.Collections.skipRemoval; /** @@ -126,11 +125,12 @@ protected void logFlushResults(FlushEvent event) { * any newly referenced entity that must be passed to saveOrUpdate(), * and also apply orphan delete */ - private void prepareEntityFlushes(EventSource session, PersistenceContext persistenceContext) throws HibernateException { + private void prepareEntityFlushes(EventSource session, PersistenceContext persistenceContext) + throws HibernateException { LOG.debug( "Processing flush-time cascades" ); final PersistContext context = PersistContext.create(); // safe from concurrent modification because of how concurrentEntries() is implemented on IdentityMap - for ( Map.Entry me : persistenceContext.reentrantSafeEntityEntries() ) { + for ( var me : persistenceContext.reentrantSafeEntityEntries() ) { // for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getEntityEntries() ) ) { final EntityEntry entry = me.getValue(); if ( flushable( entry ) ) { @@ -145,7 +145,7 @@ void checkForTransientReferences(EventSource session, PersistenceContext persist // processed, so that all entities which will be persisted are // persistent when we do the check (I wonder if we could move this // into Nullability, instead of abusing the Cascade infrastructure) - for ( Map.Entry me : persistenceContext.reentrantSafeEntityEntries() ) { + for ( var me : persistenceContext.reentrantSafeEntityEntries() ) { final EntityEntry entry = me.getValue(); if ( checkable( entry ) ) { Cascade.cascade( @@ -192,11 +192,10 @@ private void prepareCollectionFlushes(PersistenceContext persistenceContext) thr // Initialize dirty flags for arrays + collections with composite elements // and reset reached, doupdate, etc. LOG.debug( "Dirty checking collections" ); - final Map, CollectionEntry> collectionEntries = - persistenceContext.getCollectionEntries(); + final var collectionEntries = persistenceContext.getCollectionEntries(); if ( collectionEntries != null ) { - for ( Map.Entry, CollectionEntry> entry : - ( (InstanceIdentityMap, CollectionEntry>) collectionEntries ).toArray() ) { + final var identityMap = (InstanceIdentityMap, CollectionEntry>) collectionEntries; + for ( var entry : identityMap.toArray() ) { entry.getValue().preFlush( entry.getKey() ); } } @@ -221,12 +220,12 @@ private int flushEntities(final FlushEvent event, final PersistenceContext persi // collections that are changing roles. This might cause entities // to be loaded. // So this needs to be safe from concurrent modification problems. - final Map.Entry[] entityEntries = persistenceContext.reentrantSafeEntityEntries(); + final var entityEntries = persistenceContext.reentrantSafeEntityEntries(); final int count = entityEntries.length; FlushEntityEvent entityEvent = null; //allow reuse of the event as it's heavily allocated in certain use cases int eventGenerationId = 0; //Used to double-check the instance reuse won't cause problems - for ( Map.Entry me : entityEntries ) { + for ( var me : entityEntries ) { // Update the status of the object and if necessary, schedule an update final EntityEntry entry = me.getValue(); final Status status = entry.getStatus(); @@ -270,17 +269,18 @@ private FlushEntityEvent createOrReuseEventInstance( private int flushCollections(final EventSource session, final PersistenceContext persistenceContext) throws HibernateException { LOG.trace( "Processing unreferenced collections" ); - final Map, CollectionEntry> collectionEntries = persistenceContext.getCollectionEntries(); + final var collectionEntries = persistenceContext.getCollectionEntries(); final int count; if ( collectionEntries == null ) { count = 0; } else { count = collectionEntries.size(); - for ( Map.Entry, CollectionEntry> me : ( (InstanceIdentityMap, CollectionEntry>) collectionEntries ).toArray() ) { + final var identityMap = (InstanceIdentityMap, CollectionEntry>) collectionEntries; + for ( var me : identityMap.toArray() ) { final CollectionEntry ce = me.getValue(); if ( !ce.isReached() && !ce.isIgnore() ) { - Collections.processUnreachableCollection( me.getKey(), session ); + processUnreachableCollection( me.getKey(), session ); } } } From 8b45e29edbdea2180ac363865144180019bff1d3 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 29 Apr 2025 20:39:53 +0200 Subject: [PATCH 07/56] HHH-19394 optimize cascade processing for CHECK_ON_FLUSH --- .../hibernate/engine/internal/Cascade.java | 11 +++--- .../hibernate/engine/spi/CascadingAction.java | 17 ++++++++++ .../engine/spi/CascadingActions.java | 34 +++++++++++++++++++ .../entity/AbstractEntityPersister.java | 5 +++ .../persister/entity/EntityPersister.java | 10 ++++++ .../tuple/entity/EntityMetamodel.java | 32 ++++++++++++++--- .../GoofyPersisterClassProvider.java | 5 +++ .../PersisterClassProviderTest.java | 5 +++ .../orm/test/legacy/CustomPersister.java | 5 +++ 9 files changed, 114 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index af26336d4d3b..3aad9e2c8ab0 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -84,7 +84,7 @@ public static void cascade( final EntityPersister persister, final Object parent, final T anything) throws HibernateException { - if ( persister.hasCascades() || action == CHECK_ON_FLUSH ) { // performance opt + if ( action.anythingToCascade( persister ) ) { // performance opt final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { LOG.tracev( "Processing cascade {0} for: {1}", action, persister.getEntityName() ); @@ -118,7 +118,7 @@ public static void cascade( && !persister.getBytecodeEnhancementMetadata() .isAttributeLoaded( parent, propertyName ); - if ( style.doCascade( action ) ) { + if ( action.appliesTo( type, style ) ) { final Object child; if ( isUninitializedProperty ) { assert enhancedForLazyLoading; @@ -435,8 +435,9 @@ private static void cascadeComponent( for ( int i = 0; i < types.length; i++ ) { final CascadeStyle componentPropertyStyle = componentType.getCascadeStyle( i ); final String subPropertyName = propertyNames[i]; - if ( componentPropertyStyle.doCascade( action ) - || componentPropertyStyle.hasOrphanDelete() && action.deleteOrphans() ) { + final Type subPropertyType = types[i]; + if ( action.appliesTo( subPropertyType, componentPropertyStyle ) + || componentPropertyStyle.hasOrphanDelete() && action.deleteOrphans() ) { if ( children == null ) { // Get children on demand. children = componentType.getPropertyValues( child, eventSource ); @@ -448,7 +449,7 @@ private static void cascadeComponent( componentPath, parent, children[i], - types[i], + subPropertyType, componentPropertyStyle, subPropertyName, anything, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java index a718aa4eef1e..ba325fffee9e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java @@ -8,7 +8,9 @@ import org.hibernate.HibernateException; import org.hibernate.event.spi.EventSource; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.CollectionType; +import org.hibernate.type.Type; /** * A session action that may be cascaded from parent entity to its children @@ -60,4 +62,19 @@ Iterator getCascadableChildrenIterator( * Should this action be performed (or noCascade consulted) in the case of lazy properties. */ boolean performOnLazyProperty(); + + /** + * Does this action have any work to do for the entity type with the given persister? + * + * @since 7 + */ + boolean anythingToCascade(EntityPersister persister); + + /** + * Does this action have any work to do for fields of the given type with the given + * cascade style? + * + * @since 7 + */ + boolean appliesTo(Type type, CascadeStyle style); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java index 15054b72b33f..fb5635624b45 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java @@ -17,7 +17,9 @@ import org.hibernate.event.spi.PersistContext; import org.hibernate.event.spi.RefreshContext; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.CollectionType; +import org.hibernate.type.Type; import org.jboss.logging.Logger; import java.lang.invoke.MethodHandles; @@ -73,6 +75,11 @@ public boolean deleteOrphans() { return true; } + @Override + public boolean anythingToCascade(EntityPersister persister) { + return persister.hasCascadeDelete(); + } + @Override public String toString() { return "ACTION_DELETE"; @@ -371,6 +378,21 @@ public Iterator getCascadableChildrenIterator( } } + @Override + public boolean anythingToCascade(EntityPersister persister) { + // if the entity has no associations, we can just ignore it + return persister.hasToOnes() + || persister.hasOwnedCollections(); + } + + @Override + public boolean appliesTo(Type type, CascadeStyle style) { + return super.appliesTo( type, style ) + // we only care about associations here, + // but they can hide inside embeddables + && ( type.isComponentType() || type.isAssociationType() ); + } + @Override public boolean deleteOrphans() { return false; @@ -457,6 +479,18 @@ public abstract static class BaseCascadingAction implements CascadingAction getSubclassEntityNames() { return subclassEntityNames; } + private static boolean indicatesToOne(Type type) { + if ( type.isEntityType() ) { + return true; + } + else if ( type instanceof CompositeType compositeType ) { + for ( Type subtype : compositeType.getSubtypes() ) { + if ( indicatesToOne( subtype ) ) { + return true; + } + } + } + return false; + } + private static boolean indicatesCollection(Type type) { if ( type instanceof CollectionType ) { return true; } - else if ( type.isComponentType() ) { - Type[] subtypes = ( (CompositeType) type ).getSubtypes(); - for ( Type subtype : subtypes ) { + else if ( type instanceof CompositeType compositeType ) { + for ( Type subtype : compositeType.getSubtypes() ) { if ( indicatesCollection( subtype ) ) { return true; } @@ -640,8 +659,7 @@ private static boolean indicatesOwnedCollection(Type type, MetadataImplementor m if ( type instanceof CollectionType collectionType ) { return !metadata.getCollectionBinding( collectionType.getRole() ).isInverse(); } - else if ( type.isComponentType() ) { - final CompositeType compositeType = (CompositeType) type; + else if ( type instanceof CompositeType compositeType ) { for ( Type subtype : compositeType.getSubtypes() ) { if ( indicatesOwnedCollection( subtype, metadata ) ) { return true; @@ -742,6 +760,10 @@ public boolean hasCascades() { return hasCascades; } + public boolean hasToOnes() { + return hasToOnes; + } + public boolean hasCascadeDelete() { return hasCascadeDelete; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java index e9ad7a21e4e2..c51b1c63ae1a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java @@ -304,6 +304,11 @@ public boolean hasCascades() { return false; } + @Override + public boolean hasToOnes() { + return false; + } + @Override public boolean isMutable() { return false; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java index 23545af29035..99fe26bd92a6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java @@ -329,6 +329,11 @@ public boolean hasCascades() { return false; } + @Override + public boolean hasToOnes() { + return false; + } + @Override public boolean isMutable() { return false; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java index dcf97609cf70..bccc24a1616a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java @@ -257,6 +257,11 @@ public boolean hasCascades() { return false; } + @Override + public boolean hasToOnes() { + return false; + } + public boolean isMutable() { return true; } From 6a1adee85a8e6de92bfed4c21425e6d939138cce Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 30 Apr 2025 01:04:18 +0200 Subject: [PATCH 08/56] HHH-19394 fix a test which made a VERY strange assumption This test seems to have been written to assume that Hibernate has to call getPersistProperty() during the flush cycle, but with it was actually wrong thet Hibernate was previously doing this. --- .../enhancement/access/HierarchyPropertyAccessTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/HierarchyPropertyAccessTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/HierarchyPropertyAccessTest.java index cf3482429a68..e1efc7e569b8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/HierarchyPropertyAccessTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/HierarchyPropertyAccessTest.java @@ -44,6 +44,10 @@ public void testParent(SessionFactoryScope scope) { assertThat( entity.getSuperProperty() ).isEqualTo( 8 ); entity.setProperty( "transient: updated" ); + // I had to add this call because this test was + // assuming that Hibernate would call this getter + // during flush, but that is no longer true + entity.getPersistProperty(); } ); scope.inTransaction( session -> { @@ -66,6 +70,10 @@ public void testChild(SessionFactoryScope scope) { assertThat( entity.getSuperProperty() ).isEqualTo( 8 ); entity.setProperty( "transient: updated" ); + // I had to add this call because this test was + // assuming that Hibernate would call this getter + // during flush, but that is no longer true + entity.getPersistProperty(); } ); scope.inTransaction( session -> { From b735b61facc8c08985e09653623f1bfaf64ae7ed Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 30 Apr 2025 01:24:29 +0200 Subject: [PATCH 09/56] HHH-19394 add an optimization to short-circuit cascade PERSIST --- .../engine/spi/CascadingActions.java | 16 +++++++++++++ .../entity/AbstractEntityPersister.java | 5 ++++ .../persister/entity/EntityPersister.java | 23 +++++++++++-------- .../tuple/entity/EntityMetamodel.java | 11 +++++++++ .../GoofyPersisterClassProvider.java | 15 ++++++++++++ .../PersisterClassProviderTest.java | 15 ++++++++++++ .../orm/test/legacy/CustomPersister.java | 15 ++++++++++++ 7 files changed, 90 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java index fb5635624b45..32f9688bfdee 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java @@ -283,6 +283,11 @@ public boolean performOnLazyProperty() { return false; } + @Override + public boolean anythingToCascade(EntityPersister persister) { + return persister.hasCascadePersist(); + } + @Override public String toString() { return "ACTION_PERSIST"; @@ -326,6 +331,11 @@ public boolean performOnLazyProperty() { return false; } + @Override + public boolean anythingToCascade(EntityPersister persister) { + return persister.hasCascadePersist(); + } + @Override public String toString() { return "ACTION_PERSIST_ON_FLUSH"; @@ -380,6 +390,9 @@ public Iterator getCascadableChildrenIterator( @Override public boolean anythingToCascade(EntityPersister persister) { + // Must override the implementation from the superclass + // because transient checking happens even for entities + // with cascade NONE on all associations // if the entity has no associations, we can just ignore it return persister.hasToOnes() || persister.hasOwnedCollections(); @@ -387,6 +400,9 @@ public boolean anythingToCascade(EntityPersister persister) { @Override public boolean appliesTo(Type type, CascadeStyle style) { + // Very important to override the implementation from + // the superclass, because CHECK_ON_FLUSH is the only + // style that executes for fields with cascade NONE return super.appliesTo( type, style ) // we only care about associations here, // but they can hide inside embeddables diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 0403627ee373..8781aaa4c13b 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -3750,6 +3750,11 @@ public boolean hasToOnes() { return entityMetamodel.hasToOnes(); } + @Override + public boolean hasCascadePersist() { + return entityMetamodel.hasCascadePersist(); + } + @Override public boolean hasCascadeDelete() { return entityMetamodel.hasCascadeDelete(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index dbb233549066..76e13f5e09f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -337,24 +337,30 @@ default void visitQuerySpaces(Consumer querySpaceConsumer) { /** * Determine whether this entity has any - * (non-{@linkplain org.hibernate.engine.spi.CascadeStyles#NONE none}) cascading. + * {@linkplain org.hibernate.engine.spi.CascadeStyles#NONE cascading} operations. * * @return True if the entity has any properties with a cascade other than NONE; * false otherwise (aka, no cascading). */ boolean hasCascades(); + /** + * Determine whether this entity has any + * {@linkplain org.hibernate.engine.spi.CascadeStyles#PERSIST persist cascading}. + * + * @return True if the entity has any properties with a cascade PERSIST or ALL; + * false otherwise. + */ + boolean hasCascadePersist(); + /** * Determine whether this entity has any * {@linkplain org.hibernate.engine.spi.CascadeStyles#DELETE delete cascading}. * - * @return True if the entity has any properties with a cascade other than NONE; + * @return True if the entity has any properties with a cascade REMOVE or ALL; * false otherwise. */ - default boolean hasCascadeDelete() { - //bad default implementation for compatibility - return hasCascades(); - } + boolean hasCascadeDelete(); /** * Determine whether this entity has any many-to-one or one-to-one associations. @@ -372,10 +378,7 @@ default boolean hasCascadeDelete() { * @return True if the entity has an owned collection; * false otherwise. */ - default boolean hasOwnedCollections() { - //bad default implementation for compatibility - return hasCollections(); - } + boolean hasOwnedCollections(); /** * Determine whether instances of this entity are considered mutable. diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index f91494fdee3b..301902ccdfc1 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -128,6 +128,7 @@ public class EntityMetamodel implements Serializable { private boolean lazy; //not final because proxy factory creation can fail private final boolean hasCascades; private final boolean hasToOnes; + private final boolean hasCascadePersist; private final boolean hasCascadeDelete; private final boolean mutable; private final boolean isAbstract; @@ -242,6 +243,7 @@ public EntityMetamodel( int tempVersionProperty = NO_VERSION_INDX; boolean foundCascade = false; boolean foundToOne = false; + boolean foundCascadePersist = false; boolean foundCascadeDelete = false; boolean foundCollection = false; boolean foundOwnedCollection = false; @@ -378,6 +380,10 @@ else if ( !allowMutation ) { if ( cascadeStyles[i] != CascadeStyles.NONE ) { foundCascade = true; } + if ( cascadeStyles[i].doCascade(CascadingActions.PERSIST) + || cascadeStyles[i].doCascade(CascadingActions.PERSIST_ON_FLUSH) ) { + foundCascadePersist = true; + } if ( cascadeStyles[i].doCascade(CascadingActions.REMOVE) ) { foundCascadeDelete = true; } @@ -420,6 +426,7 @@ else if ( !allowMutation ) { hasCascades = foundCascade; hasToOnes = foundToOne; + hasCascadePersist = foundCascadePersist; hasCascadeDelete = foundCascadeDelete; hasNonIdentifierPropertyNamedId = foundNonIdentifierPropertyNamedId; versionPropertyIndex = tempVersionProperty; @@ -768,6 +775,10 @@ public boolean hasCascadeDelete() { return hasCascadeDelete; } + public boolean hasCascadePersist() { + return hasCascadePersist; + } + public boolean isMutable() { return mutable; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java index c51b1c63ae1a..4fb902c35d82 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java @@ -304,11 +304,26 @@ public boolean hasCascades() { return false; } + @Override + public boolean hasCascadeDelete() { + return false; + } + @Override public boolean hasToOnes() { return false; } + @Override + public boolean hasCascadePersist() { + return false; + } + + @Override + public boolean hasOwnedCollections() { + return false; + } + @Override public boolean isMutable() { return false; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java index 99fe26bd92a6..69a5c41a974f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java @@ -329,11 +329,26 @@ public boolean hasCascades() { return false; } + @Override + public boolean hasCascadeDelete() { + return false; + } + @Override public boolean hasToOnes() { return false; } + @Override + public boolean hasCascadePersist() { + return false; + } + + @Override + public boolean hasOwnedCollections() { + return false; + } + @Override public boolean isMutable() { return false; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java index bccc24a1616a..b6a629ad30a4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java @@ -257,11 +257,26 @@ public boolean hasCascades() { return false; } + @Override + public boolean hasCascadeDelete() { + return false; + } + @Override public boolean hasToOnes() { return false; } + @Override + public boolean hasCascadePersist() { + return false; + } + + @Override + public boolean hasOwnedCollections() { + return false; + } + public boolean isMutable() { return true; } From 5b69a9258db93d39a02a49248d1bd14aed3d37f5 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 5 May 2025 19:49:51 +0200 Subject: [PATCH 10/56] don't create unnecessary instances of MultipleCascadeStyle --- .../java/org/hibernate/mapping/Property.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index 8ab6a700dbec..5adaf315b893 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -38,6 +38,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; +import static org.hibernate.internal.util.StringHelper.isBlank; /** * A mapping model object representing a property or field of an {@linkplain PersistentClass entity} @@ -184,17 +185,23 @@ else if ( elementType instanceof ComponentType componentType ) { } private static CascadeStyle getCascadeStyle(String cascade) { - if ( cascade==null || cascade.equals("none") ) { + if ( cascade==null || cascade.equals("none") || isBlank(cascade) ) { return CascadeStyles.NONE; } else { final StringTokenizer tokens = new StringTokenizer(cascade, ", "); - final CascadeStyle[] styles = new CascadeStyle[ tokens.countTokens() ] ; - int i=0; - while ( tokens.hasMoreTokens() ) { - styles[i++] = CascadeStyles.getCascadeStyle( tokens.nextToken() ); + final int length = tokens.countTokens(); + if ( length == 1) { + return CascadeStyles.getCascadeStyle( tokens.nextToken() ); + } + else { + final CascadeStyle[] styles = new CascadeStyle[length]; + int i = 0; + while ( tokens.hasMoreTokens() ) { + styles[i++] = CascadeStyles.getCascadeStyle( tokens.nextToken() ); + } + return new CascadeStyles.MultipleCascadeStyle( styles ); } - return new CascadeStyles.MultipleCascadeStyle(styles); } } From b96ec13e39a4ed7ef04f842f0d920489ec862930 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 5 May 2025 19:50:23 +0200 Subject: [PATCH 11/56] move away from stringifying CascadeTypes --- .../boot/model/internal/AnyBinder.java | 8 ++-- .../boot/model/internal/BinderHelper.java | 27 +++++++------ .../boot/model/internal/CollectionBinder.java | 39 +++++++------------ .../model/internal/OneToOneSecondPass.java | 26 +++++++------ .../boot/model/internal/PropertyBinder.java | 9 +++-- .../boot/model/internal/ToOneBinder.java | 14 ++++--- .../java/org/hibernate/mapping/Property.java | 7 ++++ 7 files changed, 70 insertions(+), 60 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java index d7c2b4d6daa7..c9ffc5bb8f9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java @@ -4,6 +4,7 @@ */ package org.hibernate.boot.model.internal; +import java.util.EnumSet; import java.util.Locale; import org.hibernate.AnnotationException; @@ -11,6 +12,7 @@ import org.hibernate.annotations.AnyDiscriminator; import org.hibernate.annotations.AnyDiscriminatorImplicitValues; import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.Columns; import org.hibernate.annotations.Formula; import org.hibernate.annotations.OnDelete; @@ -29,7 +31,7 @@ import jakarta.persistence.FetchType; import jakarta.persistence.JoinTable; -import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy; +import static org.hibernate.boot.model.internal.BinderHelper.aggregateCascadeTypes; import static org.hibernate.boot.model.internal.DialectOverridesAnnotationHelper.getOverridableAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.getPath; @@ -67,7 +69,7 @@ static void bindAny( } } bindAny( - getCascadeStrategy( null, hibernateCascade, false, context ), + aggregateCascadeTypes( null, hibernateCascade, false, context ), //@Any has no cascade attribute joinColumns, onDeleteAnn == null ? null : onDeleteAnn.action(), @@ -81,7 +83,7 @@ static void bindAny( } private static void bindAny( - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns columns, OnDeleteAction onDeleteAction, Nullability nullability, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java index 3454689f186c..b149fa650ed8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java @@ -902,26 +902,29 @@ public static FetchMode getFetchMode(FetchType fetch) { }; } - public static String getCascadeStrategy( - jakarta.persistence.CascadeType[] ejbCascades, - Cascade hibernateCascadeAnnotation, + public static EnumSet aggregateCascadeTypes( + jakarta.persistence.CascadeType[] cascadeTypes, + Cascade cascadeAnnotation, boolean orphanRemoval, MetadataBuildingContext context) { - final EnumSet cascadeTypes = convertToHibernateCascadeType( ejbCascades ); + final EnumSet cascades = + convertToHibernateCascadeType( cascadeTypes ); final CascadeType[] hibernateCascades = - hibernateCascadeAnnotation == null ? null : hibernateCascadeAnnotation.value(); + cascadeAnnotation == null + ? null + : cascadeAnnotation.value(); if ( !isEmpty( hibernateCascades ) ) { - addAll( cascadeTypes, hibernateCascades ); + addAll( cascades, hibernateCascades ); } if ( orphanRemoval ) { - cascadeTypes.add( CascadeType.DELETE_ORPHAN ); - cascadeTypes.add( CascadeType.REMOVE ); + cascades.add( CascadeType.DELETE_ORPHAN ); + cascades.add( CascadeType.REMOVE ); } - if ( cascadeTypes.contains( CascadeType.REPLICATE ) ) { + if ( cascades.contains( CascadeType.REPLICATE ) ) { warnAboutDeprecatedCascadeType( CascadeType.REPLICATE ); } - cascadeTypes.addAll( context.getEffectiveDefaults().getDefaultCascadeTypes() ); - return renderCascadeTypeList( cascadeTypes ); + cascades.addAll( context.getEffectiveDefaults().getDefaultCascadeTypes() ); + return cascades; } private static EnumSet convertToHibernateCascadeType(jakarta.persistence.CascadeType[] cascades) { @@ -945,7 +948,7 @@ private static CascadeType convertCascadeType(jakarta.persistence.CascadeType ca }; } - private static String renderCascadeTypeList(EnumSet cascadeTypes) { + public static String renderCascadeTypeList(EnumSet cascadeTypes) { final StringBuilder cascade = new StringBuilder(); for ( CascadeType cascadeType : cascadeTypes ) { cascade.append( "," ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java index d38ec737701c..a619cd19201b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java @@ -6,6 +6,7 @@ import java.lang.annotation.Annotation; import java.util.Comparator; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -89,6 +90,7 @@ import static jakarta.persistence.ConstraintMode.NO_CONSTRAINT; import static jakarta.persistence.ConstraintMode.PROVIDER_DEFAULT; import static jakarta.persistence.FetchType.LAZY; +import static org.hibernate.annotations.CascadeType.DELETE_ORPHAN; import static org.hibernate.boot.model.internal.AnnotatedClassType.EMBEDDABLE; import static org.hibernate.boot.model.internal.AnnotatedClassType.NONE; import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnFromAnnotation; @@ -97,11 +99,11 @@ import static org.hibernate.boot.model.internal.AnnotatedColumn.buildFormulaFromAnnotation; import static org.hibernate.boot.model.internal.AnnotatedJoinColumns.buildJoinColumnsWithDefaultColumnSuffix; import static org.hibernate.boot.model.internal.AnnotatedJoinColumns.buildJoinTableJoinColumns; +import static org.hibernate.boot.model.internal.BinderHelper.aggregateCascadeTypes; import static org.hibernate.boot.model.internal.BinderHelper.buildAnyValue; import static org.hibernate.boot.model.internal.BinderHelper.checkMappedByType; import static org.hibernate.boot.model.internal.BinderHelper.createSyntheticPropertyReference; import static org.hibernate.boot.model.internal.BinderHelper.extractFromPackage; -import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy; import static org.hibernate.boot.model.internal.BinderHelper.getFetchMode; import static org.hibernate.boot.model.internal.BinderHelper.getPath; import static org.hibernate.boot.model.internal.BinderHelper.isDefault; @@ -152,7 +154,7 @@ public abstract class CollectionBinder { protected MemberDetails property; private TypeDetails collectionElementType; private TypeDetails targetEntity; - private String cascadeStrategy; + private EnumSet cascadeTypes; private String cacheConcurrencyStrategy; private String cacheRegionName; private CacheLayout queryCacheLayout; @@ -459,12 +461,9 @@ private static String handleTargetEntity( collectionBinder.setFkJoinColumns( joinColumns ); mappedBy = nullIfEmpty( oneToManyAnn.mappedBy() ); collectionBinder.setTargetEntity( oneToManyAnn.targetEntity() ); - collectionBinder.setCascadeStrategy( getCascadeStrategy( - oneToManyAnn.cascade(), - hibernateCascade, - oneToManyAnn.orphanRemoval(), - context - ) ); + collectionBinder.setCascadeStrategy( + aggregateCascadeTypes( oneToManyAnn.cascade(), hibernateCascade, + oneToManyAnn.orphanRemoval(), context ) ); collectionBinder.setOneToMany( true ); } else if ( elementCollectionAnn != null ) { @@ -480,23 +479,15 @@ else if ( elementCollectionAnn != null ) { else if ( manyToManyAnn != null ) { mappedBy = nullIfEmpty( manyToManyAnn.mappedBy() ); collectionBinder.setTargetEntity( manyToManyAnn.targetEntity() ); - collectionBinder.setCascadeStrategy( getCascadeStrategy( - manyToManyAnn.cascade(), - hibernateCascade, - false, - context - ) ); + collectionBinder.setCascadeStrategy( + aggregateCascadeTypes( manyToManyAnn.cascade(), hibernateCascade, false, context ) ); collectionBinder.setOneToMany( false ); } else if ( property.hasDirectAnnotationUsage( ManyToAny.class ) ) { mappedBy = null; collectionBinder.setTargetEntity( ClassDetails.VOID_CLASS_DETAILS ); - collectionBinder.setCascadeStrategy( getCascadeStrategy( - null, - hibernateCascade, - false, - context - ) ); + collectionBinder.setCascadeStrategy( + aggregateCascadeTypes( null, hibernateCascade, false, context ) ); collectionBinder.setOneToMany( false ); } else { @@ -752,8 +743,8 @@ private void setInsertable(boolean insertable) { this.insertable = insertable; } - private void setCascadeStrategy(String cascadeStrategy) { - this.cascadeStrategy = cascadeStrategy; + private void setCascadeStrategy(EnumSet cascadeTypes) { + this.cascadeTypes = cascadeTypes; } private void setAccessType(AccessType accessType) { @@ -1213,8 +1204,8 @@ private void bindProperty() { PropertyBinder binder = new PropertyBinder(); binder.setName( propertyName ); binder.setValue( collection ); - binder.setCascade( cascadeStrategy ); - if ( cascadeStrategy != null && cascadeStrategy.contains( "delete-orphan" ) ) { + binder.setCascade( cascadeTypes ); + if ( cascadeTypes != null && cascadeTypes.contains( DELETE_ORPHAN ) ) { collection.setOrphanDelete( true ); } binder.setLazy( collection.isLazy() ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java index d42b4a9478f7..be58e5c25db2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java @@ -4,10 +4,12 @@ */ package org.hibernate.boot.model.internal; +import java.util.EnumSet; import java.util.Map; import org.hibernate.AnnotationException; import org.hibernate.MappingException; +import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.LazyGroup; import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OnDeleteAction; @@ -23,6 +25,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.SortableValue; +import org.hibernate.mapping.Value; import org.hibernate.models.spi.MemberDetails; import org.hibernate.type.ForeignKeyDirection; @@ -49,7 +52,7 @@ public class OneToOneSecondPass implements SecondPass { private final NotFoundAction notFoundAction; private final OnDeleteAction onDeleteAction; private final boolean optional; - private final String cascadeStrategy; + private final EnumSet cascadeStrategy; private final AnnotatedJoinColumns joinColumns; private final MetadataBuildingContext buildingContext; private final String referencedEntityName; @@ -65,7 +68,7 @@ public OneToOneSecondPass( NotFoundAction notFoundAction, OnDeleteAction onDeleteAction, boolean optional, - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns columns, MetadataBuildingContext buildingContext) { this.ownerEntity = ownerEntity; @@ -84,11 +87,9 @@ public OneToOneSecondPass( @Override public void doSecondPass(Map persistentClasses) throws MappingException { - final OneToOne value = new OneToOne( - buildingContext, - propertyHolder.getTable(), - propertyHolder.getPersistentClass() - ); + final OneToOne value = + new OneToOne( buildingContext, propertyHolder.getTable(), + propertyHolder.getPersistentClass() ); final String propertyName = inferredData.getPropertyName(); value.setPropertyName( propertyName ); value.setReferencedEntityName( referencedEntityName ); @@ -100,7 +101,9 @@ public void doSecondPass(Map persistentClasses) throws value.setConstrained( !optional ); value.setForeignKeyType( getForeignKeyDirection() ); - bindForeignKeyNameAndDefinition( value, property, property.getDirectAnnotationUsage( ForeignKey.class ), buildingContext ); + bindForeignKeyNameAndDefinition( value, property, + property.getDirectAnnotationUsage( ForeignKey.class ), + buildingContext ); final PropertyBinder binder = new PropertyBinder(); binder.setName( propertyName ); @@ -144,10 +147,11 @@ private void bindUnowned(Map persistentClasses, OneToOn + "' targets the type '" + targetEntityName + "'" + problem ); } final Property targetProperty = targetProperty( oneToOne, targetEntity ); - if ( targetProperty.getValue() instanceof OneToOne ) { + final Value targetPropertyValue = targetProperty.getValue(); + if ( targetPropertyValue instanceof OneToOne ) { propertyHolder.addProperty( property, inferredData.getAttributeMember(), inferredData.getDeclaringClass() ); } - else if ( targetProperty.getValue() instanceof ManyToOne ) { + else if ( targetPropertyValue instanceof ManyToOne ) { bindTargetManyToOne( persistentClasses, oneToOne, property, targetEntity, targetProperty ); } else { @@ -158,7 +162,7 @@ else if ( targetProperty.getValue() instanceof ManyToOne ) { } checkMappedByType( mappedBy, - targetProperty.getValue(), + targetPropertyValue, oneToOne.getPropertyName(), propertyHolder, persistentClasses diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java index 1646b0969534..664f75df6408 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java @@ -32,6 +32,7 @@ import org.hibernate.MappingException; import org.hibernate.annotations.Any; import org.hibernate.annotations.AttributeBinderType; +import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.CompositeType; import org.hibernate.annotations.IdGeneratorType; import org.hibernate.annotations.Immutable; @@ -122,7 +123,7 @@ public class PropertyBinder { private Component componentElement; private boolean insertable = true; private boolean updatable = true; - private String cascade; + private EnumSet cascadeTypes; private BasicValueBinder basicValueBinder; private ClassDetails declaringClass; private boolean declaringClassSet; @@ -199,8 +200,8 @@ private void setComponentElement(Component componentElement) { this.componentElement = componentElement; } - public void setCascade(String cascadeStrategy) { - this.cascade = cascadeStrategy; + public void setCascade(EnumSet cascadeTypes) { + this.cascadeTypes = cascadeTypes; } public void setBuildingContext(MetadataBuildingContext buildingContext) { @@ -438,7 +439,7 @@ public Property makeProperty() { property.setValue( value ); property.setLazy( lazy ); property.setLazyGroup( lazyGroup ); - property.setCascade( cascade ); + property.setCascade( cascadeTypes ); property.setPropertyAccessorName( accessType.getType() ); property.setReturnedClassName( returnedClassName ); // property.setPropertyAccessStrategy( propertyAccessStrategy ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java index 22b0ebc4a796..43f8db02a824 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java @@ -5,12 +5,14 @@ package org.hibernate.boot.model.internal; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.Columns; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchProfileOverride; @@ -46,7 +48,7 @@ import static jakarta.persistence.FetchType.EAGER; import static jakarta.persistence.FetchType.LAZY; -import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy; +import static org.hibernate.boot.model.internal.BinderHelper.aggregateCascadeTypes; import static org.hibernate.boot.model.internal.BinderHelper.getFetchMode; import static org.hibernate.boot.model.internal.BinderHelper.getPath; import static org.hibernate.boot.model.internal.BinderHelper.isDefault; @@ -103,7 +105,7 @@ && isIdentifier( propertyHolder, propertyBinder, isIdentifierMapper ) ) { final OnDelete onDelete = property.getDirectAnnotationUsage( OnDelete.class ); final JoinTable joinTable = propertyHolder.getJoinTable( property ); bindManyToOne( - getCascadeStrategy( manyToOne.cascade(), hibernateCascade, false, context ), + aggregateCascadeTypes( manyToOne.cascade(), hibernateCascade, false, context ), joinColumns, joinTable, !isMandatory( manyToOne.optional(), property, notFoundAction ), @@ -144,7 +146,7 @@ private static boolean isMandatory(boolean optional, MemberDetails property, Not } private static void bindManyToOne( - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns joinColumns, JoinTable joinTable, boolean optional, @@ -256,7 +258,7 @@ static boolean isTargetAnnotatedEntity(ClassDetails targetEntity, MemberDetails } private static void processManyToOneProperty( - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns columns, boolean optional, PropertyData inferredData, @@ -426,7 +428,7 @@ static void bindOneToOne( final OnDelete onDelete = property.getDirectAnnotationUsage( OnDelete.class ); final JoinTable joinTable = propertyHolder.getJoinTable( property ); bindOneToOne( - getCascadeStrategy( oneToOne.cascade(), hibernateCascade, oneToOne.orphanRemoval(), context ), + aggregateCascadeTypes( oneToOne.cascade(), hibernateCascade, oneToOne.orphanRemoval(), context ), joinColumns, joinTable, !isMandatory( oneToOne.optional(), property, notFoundAction ), @@ -447,7 +449,7 @@ static void bindOneToOne( } private static void bindOneToOne( - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns joinColumns, JoinTable joinTable, boolean optional, diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index 5adaf315b893..7b5406f41d99 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -6,12 +6,14 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.EnumSet; import java.util.Map; import java.util.StringTokenizer; import org.hibernate.HibernateException; import org.hibernate.Internal; import org.hibernate.MappingException; +import org.hibernate.annotations.CascadeType; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; @@ -38,6 +40,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; +import static org.hibernate.boot.model.internal.BinderHelper.renderCascadeTypeList; import static org.hibernate.internal.util.StringHelper.isBlank; /** @@ -213,6 +216,10 @@ public void setCascade(String cascade) { this.cascade = cascade; } + public void setCascade(EnumSet cascadeTypes) { + cascade = cascadeTypes == null ? null : renderCascadeTypeList( cascadeTypes ); + } + public void setName(String name) { this.name = name==null ? null : name.intern(); } From acbb9d1d219fedc14a16704b77a07193c94b314b Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 5 May 2025 22:22:43 +0200 Subject: [PATCH 12/56] minor code cleanup in Cascade.cascade --- .../hibernate/engine/internal/Cascade.java | 77 +++++++++---------- .../entity/AbstractEntityPersister.java | 8 +- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index 3aad9e2c8ab0..bab8fd05e17b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -10,7 +10,6 @@ import java.util.List; import org.hibernate.HibernateException; -import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadingAction; @@ -89,12 +88,10 @@ public static void cascade( if ( traceEnabled ) { LOG.tracev( "Processing cascade {0} for: {1}", action, persister.getEntityName() ); } - final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); - final boolean enhancedForLazyLoading = - persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + final var bytecodeEnhancement = persister.getBytecodeEnhancementMetadata(); final EntityEntry entry; - if ( enhancedForLazyLoading ) { - entry = persistenceContext.getEntry( parent ); + if ( bytecodeEnhancement.isEnhancedForLazyLoading() ) { + entry = eventSource.getPersistenceContextInternal().getEntry( parent ); if ( entry != null && entry.getLoadedState() == null && entry.getStatus() == Status.MANAGED ) { @@ -104,10 +101,11 @@ public static void cascade( else { entry = null; } + final Type[] types = persister.getPropertyTypes(); final String[] propertyNames = persister.getPropertyNames(); final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); - final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); + final boolean hasUninitializedLazyProperties = bytecodeEnhancement.hasUnFetchedAttributes( parent ); for ( int i = 0; i < types.length; i++) { final CascadeStyle style = cascadeStyles[ i ]; @@ -115,13 +113,14 @@ public static void cascade( final Type type = types[i]; final boolean isUninitializedProperty = hasUninitializedLazyProperties - && !persister.getBytecodeEnhancementMetadata() - .isAttributeLoaded( parent, propertyName ); + && !bytecodeEnhancement.isAttributeLoaded( parent, propertyName ); if ( action.appliesTo( type, style ) ) { final Object child; - if ( isUninitializedProperty ) { - assert enhancedForLazyLoading; + if ( isUninitializedProperty ) { + assert bytecodeEnhancement.isEnhancedForLazyLoading(); + // Hibernate does not support lazy embeddables + assert !type.isComponentType(); // parent is a bytecode enhanced entity. // Cascade to an uninitialized, lazy value only if // parent is managed in the PersistenceContext. @@ -132,13 +131,14 @@ public static void cascade( // parent was not in the PersistenceContext continue; } - if ( type instanceof CollectionType collectionType ) { - // CollectionType#getCollection gets the PersistentCollection + else if ( type instanceof CollectionType collectionType ) { + // CollectionType.getCollection() gets the PersistentCollection // that corresponds to the uninitialized collection from the // PersistenceContext. If not present, an uninitialized // PersistentCollection will be added to the PersistenceContext. // The action may initialize it later, if necessary. - // This needs to be done even when action.performOnLazyProperty() returns false. + // This needs to be done even when action.performOnLazyProperty() + // returns false. child = collectionType.getCollection( collectionType.getKeyOfOwner( parent, eventSource ), eventSource, @@ -146,17 +146,11 @@ public static void cascade( null ); } - else if ( type instanceof AnyType || type instanceof ComponentType ) { - // Hibernate does not support lazy embeddables, so this shouldn't happen. - throw new UnsupportedOperationException( "Lazy embeddables are not supported" ); - } else if ( action.performOnLazyProperty() && type instanceof EntityType ) { - // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() - // returns true. - final LazyAttributeLoadingInterceptor interceptor = - persister.getBytecodeEnhancementMetadata() - .extractInterceptor( parent ); - child = interceptor.fetchAttribute( parent, propertyName ); + // Only need to initialize a lazy entity attribute when + // action.performOnLazyProperty() returns true. + child = bytecodeEnhancement.extractInterceptor( parent ) + .fetchAttribute( parent, propertyName ); } else { @@ -181,21 +175,21 @@ else if ( action.performOnLazyProperty() && type instanceof EntityType ) { false ); } - else { - // If the property is uninitialized, then there cannot be any orphans. - if ( action.deleteOrphans() && !isUninitializedProperty && isLogicalOneToOne( type ) ) { - cascadeLogicalOneToOneOrphanRemoval( - action, - eventSource, - null, - parent, - persister.getValue( parent, i ), - type, - style, - propertyName, - false - ); - } + else if ( action.deleteOrphans() + // If the property is uninitialized, there cannot be any orphans. + && !isUninitializedProperty + && isLogicalOneToOne( type ) ) { + cascadeLogicalOneToOneOrphanRemoval( + action, + eventSource, + null, + parent, + persister.getValue( parent, i ), + type, + style, + propertyName, + false + ); } } @@ -387,10 +381,11 @@ private static boolean isForeignKeyToParent(Type type) { * * @param type The type representing the attribute metadata * - * @return True if the attribute represents a logical one to one association + * @return True if the attribute represents a logical one-to-one association */ private static boolean isLogicalOneToOne(Type type) { - return type instanceof EntityType entityType && entityType.isLogicalOneToOne(); + return type instanceof EntityType entityType + && entityType.isLogicalOneToOne(); } private static boolean cascadeAssociationNow( diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 8781aaa4c13b..d2200705aa41 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -4295,7 +4295,7 @@ protected void linkToSession(Object entity, SharedSessionContractImplementor ses private void setSession(PersistentAttributeInterceptable entity, SharedSessionContractImplementor session) { final BytecodeLazyAttributeInterceptor interceptor = - getEntityMetamodel().getBytecodeEnhancementMetadata() + entityMetamodel.getBytecodeEnhancementMetadata() .extractLazyInterceptor( entity ); if ( interceptor != null ) { interceptor.setSession( session ); @@ -4338,9 +4338,9 @@ && hasSubclasses() // todo (6.0) : this previously used `org.hibernate.tuple.entity.EntityTuplizer#determineConcreteSubclassEntityName` // - we may need something similar here... for ( EntityMappingType subclassMappingType : subclassMappingTypes.values() ) { - if ( subclassMappingType.getEntityPersister().getRepresentationStrategy() - .getInstantiator().isSameClass(instance ) ) { - return subclassMappingType.getEntityPersister(); + final EntityPersister persister = subclassMappingType.getEntityPersister(); + if ( persister.getRepresentationStrategy().getInstantiator().isSameClass( instance ) ) { + return persister; } } } From fd529319514541d537550854fb454453e7d0184a Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 5 May 2025 22:52:37 +0200 Subject: [PATCH 13/56] HHH-19394 recover the functionality of hibernate.unowned_association_transient_check --- .../hibernate/engine/internal/Cascade.java | 36 +-------------- .../hibernate/engine/spi/CascadingAction.java | 12 +++++ .../engine/spi/CascadingActions.java | 45 ++++++++++++++++++- 3 files changed, 57 insertions(+), 36 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index bab8fd05e17b..baa750d43fc7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -16,7 +16,6 @@ import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; @@ -31,13 +30,11 @@ import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.ForeignKeyDirection; -import org.hibernate.type.ManyToOneType; import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; import static java.util.Collections.EMPTY_LIST; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; -import static org.hibernate.engine.spi.CascadingActions.CHECK_ON_FLUSH; import static org.hibernate.pretty.MessageHelper.infoString; /** @@ -217,11 +214,7 @@ private static void cascadeProperty( if ( child != null ) { if ( type instanceof EntityType || type instanceof CollectionType || type instanceof AnyType ) { - final AssociationType associationType = (AssociationType) type; - final boolean unownedTransient = eventSource.getSessionFactory() - .getSessionFactoryOptions() - .isUnownedAssociationTransientCheck(); - if ( cascadeAssociationNow( action, cascadePoint, associationType, eventSource.getFactory(), unownedTransient ) ) { + if ( action.cascadeNow( cascadePoint, (AssociationType) type, eventSource.getFactory() ) ) { cascadeAssociation( action, cascadePoint, @@ -388,33 +381,6 @@ private static boolean isLogicalOneToOne(Type type) { && entityType.isLogicalOneToOne(); } - private static boolean cascadeAssociationNow( - CascadingAction action, - CascadePoint cascadePoint, - AssociationType associationType, - SessionFactoryImplementor factory, - boolean unownedTransient) { - return associationType.getForeignKeyDirection().cascadeNow( cascadePoint ) - // For check on flush, we should only check unowned associations when strictness is enforced - && ( action != CHECK_ON_FLUSH || unownedTransient || !isUnownedAssociation( associationType, factory ) ); - } - - private static boolean isUnownedAssociation(AssociationType associationType, SessionFactoryImplementor factory) { - if ( associationType instanceof ManyToOneType manyToOne ) { - // logical one-to-one + non-null unique key property name indicates unowned - return manyToOne.isLogicalOneToOne() && manyToOne.getRHSUniqueKeyPropertyName() != null; - } - else if ( associationType instanceof OneToOneType oneToOne ) { - // constrained false + non-null unique key property name indicates unowned - return oneToOne.isNullable() && oneToOne.getRHSUniqueKeyPropertyName() != null; - } - else if ( associationType instanceof CollectionType collectionType ) { - // for collections, we can ask the persister if we're on the inverse side - return collectionType.isInverse( factory ); - } - return false; - } - private static void cascadeComponent( final CascadingAction action, final CascadePoint cascadePoint, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java index ba325fffee9e..6fe8057f6609 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java @@ -7,8 +7,10 @@ import java.util.Iterator; import org.hibernate.HibernateException; +import org.hibernate.engine.internal.CascadePoint; import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.Type; @@ -77,4 +79,14 @@ Iterator getCascadableChildrenIterator( * @since 7 */ boolean appliesTo(Type type, CascadeStyle style); + + /** + * Does this action cascade to the given association at the given {@link CascadePoint}? + * + * @since 7 + */ + boolean cascadeNow( + CascadePoint cascadePoint, + AssociationType associationType, + SessionFactoryImplementor factory); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java index 32f9688bfdee..4bc7fb4dcf13 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java @@ -11,6 +11,7 @@ import org.hibernate.ReplicationMode; import org.hibernate.TransientObjectException; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.internal.CascadePoint; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.MergeContext; @@ -18,7 +19,10 @@ import org.hibernate.event.spi.RefreshContext; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; import org.jboss.logging.Logger; @@ -395,7 +399,12 @@ public boolean anythingToCascade(EntityPersister persister) { // with cascade NONE on all associations // if the entity has no associations, we can just ignore it return persister.hasToOnes() - || persister.hasOwnedCollections(); + || persister.hasOwnedCollections() + // when hibernate.unowned_association_transient_check + // is enabled, we have to check unowned associations + || persister.hasCollections() + && persister.getFactory().getSessionFactoryOptions() + .isUnownedAssociationTransientCheck(); } @Override @@ -409,6 +418,32 @@ public boolean appliesTo(Type type, CascadeStyle style) { && ( type.isComponentType() || type.isAssociationType() ); } + @Override + public boolean cascadeNow( + CascadePoint cascadePoint, + AssociationType associationType, + SessionFactoryImplementor factory) { + return super.cascadeNow( cascadePoint, associationType, factory ) + && ( factory.getSessionFactoryOptions().isUnownedAssociationTransientCheck() + || !isUnownedAssociation( associationType, factory ) ); + } + + private static boolean isUnownedAssociation(AssociationType associationType, SessionFactoryImplementor factory) { + if ( associationType instanceof ManyToOneType manyToOne ) { + // logical one-to-one + non-null unique key property name indicates unowned + return manyToOne.isLogicalOneToOne() && manyToOne.getRHSUniqueKeyPropertyName() != null; + } + else if ( associationType instanceof OneToOneType oneToOne ) { + // constrained false + non-null unique key property name indicates unowned + return oneToOne.isNullable() && oneToOne.getRHSUniqueKeyPropertyName() != null; + } + else if ( associationType instanceof CollectionType collectionType ) { + // for collections, we can ask the persister if we're on the inverse side + return collectionType.isInverse( factory ); + } + return false; + } + @Override public boolean deleteOrphans() { return false; @@ -507,6 +542,14 @@ public boolean anythingToCascade(EntityPersister persister) { public boolean appliesTo(Type type, CascadeStyle style) { return style.doCascade( this ); } + + @Override + public boolean cascadeNow( + CascadePoint cascadePoint, + AssociationType associationType, + SessionFactoryImplementor factory) { + return associationType.getForeignKeyDirection().cascadeNow( cascadePoint ); + } } /** From 05e9f1f44e37428cd4ee48e0cf398ed5409deb41 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 14 May 2025 17:49:25 +0200 Subject: [PATCH 14/56] HHH-19463 when we have @Repository(provider="Hibernate") don't skip repo in this case it can't be intended for another provider --- .../annotation/AnnotationMetaEntity.java | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java index 2534e3df0b9a..4de93d65f6d7 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMetaEntity.java @@ -417,8 +417,9 @@ else if ( method.getEnclosingElement().getKind().isInterface() } primaryEntity = primaryEntity( lifecycleMethods ); - if ( primaryEntity != null && !hasAnnotation(primaryEntity, ENTITY) - || !checkEntities(lifecycleMethods)) { + final boolean hibernateRepo = isExplicitlyHibernateRepository(); + if ( !checkEntity( primaryEntity, hibernateRepo ) + || !checkEntities( lifecycleMethods, hibernateRepo ) ) { // NOTE EARLY EXIT with initialized = false return; } @@ -468,6 +469,29 @@ && containsAnnotation( method, HQL, SQL, FIND ) ) { initialized = true; } + private boolean checkEntity(@Nullable TypeElement entity, boolean hibernateRepo) { + if ( entity != null && !hasAnnotation( entity, ENTITY ) ) { + if ( hibernateRepo ) { + context.message( element, + "unrecognized primary entity type: " + entity.getQualifiedName(), + Diagnostic.Kind.ERROR ); + } + return false; + } + return true; + } + + private boolean isExplicitlyHibernateRepository() { + final AnnotationMirror repository = getAnnotationMirror( element, JD_REPOSITORY ); + if ( repository != null ) { + final AnnotationValue provider = getAnnotationValue( repository, "provider" ); + return provider != null && provider.getValue().toString().equalsIgnoreCase( "hibernate" ); + } + else { + return false; + } + } + /** * Creates a generated id class named {@code Entity_.Id} if the * entity has multiple {@code @Id} fields, but no {@code @IdClass} @@ -612,7 +636,7 @@ private boolean isEquivalentPrimitiveType(TypeMirror type, TypeMirror match) { && isSameType( context.getTypeUtils().boxedClass( ((PrimitiveType) type) ).asType(), match ); } - private boolean checkEntities(List lifecycleMethods) { + private boolean checkEntities(List lifecycleMethods, boolean hibernateRepo) { boolean foundPersistenceEntity = false; VariableElement nonPersistenceParameter = null; for (ExecutableElement lifecycleMethod : lifecycleMethods) { @@ -637,7 +661,7 @@ else if ( declaredType == parameterType message(nonPersistenceParameter, "parameter type '" + nonPersistenceParameter.asType() + "' is not a Jakarta Persistence entity class (skipping entire repository)", - Diagnostic.Kind.WARNING); + hibernateRepo ? Diagnostic.Kind.ERROR : Diagnostic.Kind.WARNING); } return nonPersistenceParameter == null; } From f9e1cfcdb0631a91f929f119af6a7b0ca839885f Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 20 May 2025 19:14:41 +0200 Subject: [PATCH 15/56] some very tiny cleanups --- .../TransientPropertyValueException.java | 3 +- .../mapping/EntityIdentifierMapping.java | 37 +++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java b/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java index 39a35c780a7b..9859ec447ac3 100644 --- a/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java +++ b/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java @@ -68,6 +68,7 @@ public String getPropertyName() { @Override public String getMessage() { return super.getMessage() + " for entity " - + qualify( propertyOwnerEntityName, propertyName ) + " -> " + transientEntityName; + + qualify( propertyOwnerEntityName, propertyName ) + + " -> " + transientEntityName; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java index 06e5aeb714c4..bab18faf1c35 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityIdentifierMapping.java @@ -71,28 +71,30 @@ default String getPartName() { /** * Extract the identifier from an instance of the entity * - * It's supposed to be use during the merging process + * @apiNote Intended for use during the merging process */ default Object getIdentifier(Object entity, MergeContext mergeContext){ return getIdentifier( entity ); } /** - * Return the identifier of the persistent or transient object, or throw - * an exception if the instance is "unsaved" - *

- * Used by OneToOneType and ManyToOneType to determine what id value should - * be used for an object that may or may not be associated with the session. - * This does a "best guess" using any/all info available to use (not just the - * EntityEntry). + * Return the identifier of the persistent or transient object, or throw an + * exception if the instance is "unsaved" + * + * @apiNote This method is called by {@link org.hibernate.type.OneToOneType} + * and {@link org.hibernate.type.ManyToOneType} to determine the id value + * which should be used for an object that may or may not be associated with + * the session. This does a "best guess" using any/all info available to use + * (not just the {@link org.hibernate.engine.spi.EntityEntry}). * * @param entity The entity instance * @param session The session * * @return The identifier * - * @throws TransientObjectException if the entity is transient (does not yet have an identifier) - * @see org.hibernate.engine.internal.ForeignKeys#getEntityIdentifierIfNotUnsaved(String, Object, SharedSessionContractImplementor) + * @throws TransientObjectException if the entity is transient + * (does not yet have an identifier) + * @see org.hibernate.engine.internal.ForeignKeys#getEntityIdentifierIfNotUnsaved * @since 6.1.1 */ default Object getIdentifierIfNotUnsaved(Object entity, SharedSessionContractImplementor session) { @@ -103,20 +105,23 @@ else if ( session == null ) { // If we have no session available, just return the identifier return getIdentifier( entity ); } - Object id = session.getContextEntityIdentifier( entity ); + final Object id = session.getContextEntityIdentifier( entity ); if ( id == null ) { - // context-entity-identifier returns null explicitly if the entity - // is not associated with the persistence context; so make some - // deeper checks... + // getContextEntityIdentifier() returned null, indicating that + // the entity is not associated with the persistence context, + // so look deeper for its id final String entityName = findContainingEntityMapping().getEntityName(); if ( ForeignKeys.isTransient( entityName, entity, Boolean.FALSE, session ) ) { + // TODO should be a TransientPropertyValueException throw new TransientObjectException( "object references an unsaved transient instance of '" + (entityName == null ? session.guessEntityName( entity ) : entityName) + "' save the transient instance before flushing" ); } - id = getIdentifier( entity ); + return getIdentifier( entity ); + } + else { + return id; } - return id; } /** From b73d381ef88c1b59124bb3b3dd69d8fbf14c9374 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 20 May 2025 19:31:05 +0200 Subject: [PATCH 16/56] improve javadoc of TransientObjectException --- .../org/hibernate/TransientObjectException.java | 16 ++++++++++++++-- .../TransientPropertyValueException.java | 14 ++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/TransientObjectException.java b/hibernate-core/src/main/java/org/hibernate/TransientObjectException.java index 980b1c9b16ec..88f65da62fbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/TransientObjectException.java +++ b/hibernate-core/src/main/java/org/hibernate/TransientObjectException.java @@ -5,8 +5,20 @@ package org.hibernate; /** - * Thrown when the user passes a transient instance to a {@link Session} - * method that expects a persistent instance. + * Thrown if a transient instance of an entity class is passed to + * a {@link Session} method that expects a persistent instance, + * or if the state of an entity instance cannot be made persistent + * because the instance holds a reference to a transient entity. + *

+ * An entity is considered transient if it is: + *

    + *
  • a newly-instantiated instance of an entity class which has + * never been {@linkplain Session#persist made persistent} in + * the database, or + *
  • an entity instance previously associated with a persistence + * context which has been {@linkplain Session#remove removed} + * from the database. + *
* * @author Gavin King */ diff --git a/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java b/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java index 9859ec447ac3..bff773778fc7 100644 --- a/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java +++ b/hibernate-core/src/main/java/org/hibernate/TransientPropertyValueException.java @@ -7,8 +7,18 @@ import static org.hibernate.internal.util.StringHelper.qualify; /** - * Thrown when a property cannot be persisted because it is an association - * with a transient unsaved entity instance. + * Thrown when the state of an entity cannot be made persistent + * because the entity holds a reference to a transient entity. + *

+ * An entity is considered transient if it is: + *

    + *
  • a newly-instantiated instance of an entity class which has + * never been {@linkplain Session#persist made persistent} in + * the database, or + *
  • an entity instance previously associated with a persistence + * context which has been {@linkplain Session#remove removed} + * from the database. + *
* * @author Gail Badner */ From 0a7863e0836b4c40697dc5abe22cdce5e2fa0be5 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 20 May 2025 19:44:03 +0200 Subject: [PATCH 17/56] very minor code cleanups to NativeQueryImpl --- .../query/sql/internal/NativeQueryImpl.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index 07e3535da8ac..bf0d98267166 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -158,7 +158,7 @@ public class NativeQueryImpl private Callback callback; /** - * Constructs a NativeQueryImpl given a sql query defined in the mappings. + * Constructs a {@code NativeQueryImpl} given a SQL query defined in the mappings. * Used by Hibernate Reactive. */ @SuppressWarnings("unused") @@ -191,7 +191,7 @@ public NativeQueryImpl(NamedNativeQueryMemento memento, SharedSessionContract } /** - * Constructs a NativeQueryImpl given a sql query defined in the mappings. + * Constructs a {@code NativeQueryImpl} given a SQL query defined in the mappings. */ public NativeQueryImpl( NamedNativeQueryMemento memento, @@ -228,7 +228,7 @@ public NativeQueryImpl( } /** - * Constructs a NativeQueryImpl given a sql query defined in the mappings. + * Constructs a {@code NativeQueryImpl} given a SQL query defined in the mappings. */ public NativeQueryImpl( NamedNativeQueryMemento memento, @@ -378,7 +378,7 @@ else if ( Map.class.equals( resultClass ) ) { else if ( List.class.equals( resultClass ) ) { return NativeQueryListTransformer.INSTANCE; } - else if ( Object[].class.equals( resultClass )) { + else if ( Object[].class.equals( resultClass ) ) { return NativeQueryArrayTransformer.INSTANCE; } else if ( resultClass != Object.class ) { @@ -535,9 +535,12 @@ public Class getResultType() { @Override public NamedNativeQueryMemento toMemento(String name) { + final QueryOptions options = getQueryOptions(); return new NamedNativeQueryMementoImpl<>( name, - resultType != null ? resultType : extractResultClass( resultSetMapping ), + resultType == null + ? extractResultClass( resultSetMapping ) + : resultType, sqlString, originalSqlString, resultSetMapping.getMappingIdentifier(), @@ -545,13 +548,13 @@ public NamedNativeQueryMemento toMemento(String name) { isCacheable(), getCacheRegion(), getCacheMode(), - getQueryOptions().getFlushMode(), + options.getFlushMode(), isReadOnly(), getTimeout(), getFetchSize(), getComment(), - getQueryOptions().getLimit().getFirstRow(), - getQueryOptions().getLimit().getMaxRows(), + options.getLimit().getFirstRow(), + options.getLimit().getMaxRows(), getHints() ); } @@ -729,16 +732,16 @@ public KeyedResultList getKeyedResultList(KeyedPage page) { protected SelectQueryPlan resolveSelectQueryPlan() { final ResultSetMapping mapping; if ( resultType != null && resultSetMapping.isDynamic() && resultSetMapping.getNumberOfResultBuilders() == 0 ) { - mapping = ResultSetMapping.resolveResultSetMapping( originalSqlString, true, getSessionFactory() ); - - if ( getSessionFactory().getMappingMetamodel().isEntityClass( resultType ) ) { + final SessionFactoryImplementor sessionFactory = getSessionFactory(); + mapping = ResultSetMapping.resolveResultSetMapping( originalSqlString, true, sessionFactory ); + if ( sessionFactory.getMappingMetamodel().isEntityClass( resultType ) ) { mapping.addResultBuilder( Builders.entityCalculated( unqualify( resultType.getName() ), resultType.getName(), - LockMode.READ, getSessionFactory() ) ); + LockMode.READ, sessionFactory ) ); } else if ( !isResultTypeAlwaysAllowed( resultType ) && (!isClass( resultType ) || hasJavaTypeDescriptor( resultType )) ) { - mapping.addResultBuilder( Builders.resultClassBuilder( resultType, getSessionFactory().getMappingMetamodel() ) ); + mapping.addResultBuilder( Builders.resultClassBuilder( resultType, sessionFactory.getMappingMetamodel() ) ); } } else { @@ -960,12 +963,13 @@ public static int determineBindValueMaxCount(boolean paddingEnabled, int inExprL } private SelectInterpretationsKey selectInterpretationsKey(ResultSetMapping resultSetMapping) { + final QueryOptions options = getQueryOptions(); return new SelectInterpretationsKey( getQueryString(), resultSetMapping, getSynchronizedQuerySpaces(), - getQueryOptions().getTupleTransformer(), - getQueryOptions().getResultListTransformer() + options.getTupleTransformer(), + options.getResultListTransformer() ); } From 7315de5cdb94f35614cf50f63eb7411e534856de Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 20 May 2025 19:44:42 +0200 Subject: [PATCH 18/56] use var to get rid of some ugly generic types in AbstractSqmSelectionQuery --- .../internal/AbstractSqmSelectionQuery.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java index d66ebb47a026..f4da8f36a638 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java @@ -15,7 +15,6 @@ import org.hibernate.query.Page; import org.hibernate.query.QueryLogging; import org.hibernate.query.SelectionQuery; -import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.hql.internal.QuerySplitter; import org.hibernate.query.spi.AbstractSelectionQuery; import org.hibernate.query.spi.HqlInterpretation; @@ -32,7 +31,6 @@ import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.query.sqm.tree.select.SqmSelectableNode; import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.sql.results.internal.TupleMetadata; import org.hibernate.type.BasicTypeRegistry; @@ -318,23 +316,24 @@ private static boolean isInstantiableWithoutMetadata(Class resultType) { } private TupleMetadata getTupleMetadata(List> selections) { - if ( getQueryOptions().getTupleTransformer() == null ) { + final var tupleTransformer = getQueryOptions().getTupleTransformer(); + if ( tupleTransformer == null ) { return new TupleMetadata( buildTupleElementArray( selections ), buildTupleAliasArray( selections ) ); } else { throw new IllegalArgumentException( "Illegal combination of Tuple resultType and (non-JpaTupleBuilder) TupleTransformer: " - + getQueryOptions().getTupleTransformer() + + tupleTransformer ); } } private static TupleElement[] buildTupleElementArray(List> selections) { if ( selections.size() == 1 ) { - final SqmSelectableNode selectableNode = selections.get( 0).getSelectableNode(); + final var selectableNode = selections.get( 0 ).getSelectableNode(); if ( selectableNode instanceof CompoundSelection ) { - final List> selectionItems = selectableNode.getSelectionItems(); - final TupleElement[] elements = new TupleElement[ selectionItems.size() ]; + final var selectionItems = selectableNode.getSelectionItems(); + final var elements = new TupleElement[ selectionItems.size() ]; for ( int i = 0; i < selectionItems.size(); i++ ) { elements[i] = selectionItems.get( i ); } @@ -345,7 +344,7 @@ private static TupleElement[] buildTupleElementArray(List> se } } else { - final TupleElement[] elements = new TupleElement[ selections.size() ]; + final var elements = new TupleElement[ selections.size() ]; for ( int i = 0; i < selections.size(); i++ ) { elements[i] = selections.get( i ).getSelectableNode(); } @@ -355,10 +354,10 @@ private static TupleElement[] buildTupleElementArray(List> se private static String[] buildTupleAliasArray(List> selections) { if ( selections.size() == 1 ) { - final SqmSelectableNode selectableNode = selections.get(0).getSelectableNode(); + final var selectableNode = selections.get(0).getSelectableNode(); if ( selectableNode instanceof CompoundSelection ) { - final List> selectionItems = selectableNode.getSelectionItems(); - final String[] elements = new String[ selectionItems.size() ]; + final var selectionItems = selectableNode.getSelectionItems(); + final String[] elements = new String[ selectionItems.size() ]; for ( int i = 0; i < selectionItems.size(); i++ ) { elements[i] = selectionItems.get( i ).getAlias(); } From 0cd8e941cb966c994460563ef81eb3435b88b114 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 20 May 2025 22:22:04 +0200 Subject: [PATCH 19/56] widen type of SelectionSpecification.resort() --- .../hibernate/query/specification/SelectionSpecification.java | 2 +- .../specification/internal/SelectionSpecificationImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/specification/SelectionSpecification.java b/hibernate-core/src/main/java/org/hibernate/query/specification/SelectionSpecification.java index 08a62e441715..c64ddc652b16 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/specification/SelectionSpecification.java +++ b/hibernate-core/src/main/java/org/hibernate/query/specification/SelectionSpecification.java @@ -104,7 +104,7 @@ public interface SelectionSpecification extends QuerySpecification { * * @return {@code this} for method chaining. */ - SelectionSpecification resort(List> orders); + SelectionSpecification resort(List> orders); @Override SelectionSpecification restrict(Restriction restriction); diff --git a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java index 5cdd424dc8ea..0cfd3b50dabf 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java @@ -136,7 +136,7 @@ public final SelectionSpecification resort(Order order) { } @Override - public final SelectionSpecification resort(List> orders) { + public final SelectionSpecification resort(List> orders) { specifications.add( (sqmStatement, root) -> { sqmStatement.getQuerySpec().setOrderByClause( new SqmOrderByClause() ); orders.forEach( order -> addOrder( order, sqmStatement ) ); From 46a1af58638766e40de71857de07336445cae3e8 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 20 May 2025 22:26:27 +0200 Subject: [PATCH 20/56] update to Hibernate Validator 9 --- documentation/src/main/asciidoc/introduction/Introduction.adoc | 2 +- settings.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/main/asciidoc/introduction/Introduction.adoc b/documentation/src/main/asciidoc/introduction/Introduction.adoc index 442ff61b2877..d3d398867e37 100644 --- a/documentation/src/main/asciidoc/introduction/Introduction.adoc +++ b/documentation/src/main/asciidoc/introduction/Introduction.adoc @@ -186,7 +186,7 @@ dependencies { annotationProcessor 'org.hibernate.orm:hibernate-processor:{fullVersion}' // Hibernate Validator - implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' + implementation 'org.hibernate.validator:hibernate-validator:9.0.0.Final' implementation 'org.glassfish:jakarta.el:4.0.2' // Agroal connection pool diff --git a/settings.gradle b/settings.gradle index ed32f50af7b4..530caff2f9ed 100644 --- a/settings.gradle +++ b/settings.gradle @@ -179,7 +179,7 @@ dependencyResolutionManagement { def jfrUnitVersion = version "jfrUnit", "1.0.0.Alpha2" - def hibernateValidatorVersion = version "hibernateValidator", "9.0.0.Beta3" + def hibernateValidatorVersion = version "hibernateValidator", "9.0.0.Final" library( "validator", "org.hibernate.validator", "hibernate-validator" ).versionRef( hibernateValidatorVersion ) From 208cb5d9fd61c1b70704ebdd7018df4bdee5db00 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 20 May 2025 22:34:47 +0200 Subject: [PATCH 21/56] Update documentation/src/main/asciidoc/introduction/Introduction.adoc Co-authored-by: Marko Bekhta --- documentation/src/main/asciidoc/introduction/Introduction.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/introduction/Introduction.adoc b/documentation/src/main/asciidoc/introduction/Introduction.adoc index d3d398867e37..b4bcbf5157fa 100644 --- a/documentation/src/main/asciidoc/introduction/Introduction.adoc +++ b/documentation/src/main/asciidoc/introduction/Introduction.adoc @@ -187,7 +187,7 @@ dependencies { // Hibernate Validator implementation 'org.hibernate.validator:hibernate-validator:9.0.0.Final' - implementation 'org.glassfish:jakarta.el:4.0.2' + implementation 'org.glassfish.expressly:expressly:6.0.0' // Agroal connection pool runtimeOnly 'org.hibernate.orm:hibernate-agroal:{fullVersion}' From 22305dedab2774e9ae904b08a0292b8e456f45b5 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 20 May 2025 22:38:54 +0200 Subject: [PATCH 22/56] should use org.glassfish.expressly:expressly with HV9 --- documentation/src/main/asciidoc/introduction/Configuration.adoc | 2 +- documentation/src/main/asciidoc/repositories/Configuration.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/src/main/asciidoc/introduction/Configuration.adoc b/documentation/src/main/asciidoc/introduction/Configuration.adoc index 9d98c08b5dcc..6e845f42b3f9 100644 --- a/documentation/src/main/asciidoc/introduction/Configuration.adoc +++ b/documentation/src/main/asciidoc/introduction/Configuration.adoc @@ -109,7 +109,7 @@ and `io.agroal:agroal-pool` | The {query-validator}[Query Validator], for compile-time checking of HQL | `org.hibernate:query-validator` | {validator}[Hibernate Validator], an implementation of {bean-validation}[Bean Validation] | `org.hibernate.validator:hibernate-validator` + -and `org.glassfish:jakarta.el` +and `org.glassfish.expressly:expressly` | Local second-level cache support via JCache and {ehcache}[EHCache] | `org.hibernate.orm:hibernate-jcache` + and `org.ehcache:ehcache` | Local second-level cache support via JCache and {caffeine}[Caffeine]| `org.hibernate.orm:hibernate-jcache` + diff --git a/documentation/src/main/asciidoc/repositories/Configuration.adoc b/documentation/src/main/asciidoc/repositories/Configuration.adoc index f9dcd23ae913..d41584ec463e 100644 --- a/documentation/src/main/asciidoc/repositories/Configuration.adoc +++ b/documentation/src/main/asciidoc/repositories/Configuration.adoc @@ -45,7 +45,7 @@ In addition, we might add some of the following to the mix. | Optional dependency | Explanation | `org.hibernate.validator:hibernate-validator` + -and `org.glassfish:jakarta.el` | Hibernate Validator +and `org.glassfish.expressly:expressly` | Hibernate Validator | `org.apache.logging.log4j:log4j-core` | log4j | `org.jboss.weld:weld-core-impl` | Weld CDI |=== From d83eadff60bf5015b7bcd1804a2e025e996c8c43 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 21 May 2025 08:39:58 +0200 Subject: [PATCH 23/56] much better error when query has 'having' but no 'group by' --- .../org/hibernate/grammars/hql/HqlParser.g4 | 9 +++-- .../hql/internal/SemanticQueryBuilder.java | 34 ++++++++++++------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index 4fc90da898cd..5db8339e9a72 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -185,11 +185,14 @@ limitOffset * * - The 'select' clause may come first, in which case 'from' is optional * - The 'from' clause may come first, in which case 'select' is optional, and comes last + * - If both 'select' and 'from' are missing, a 'where' clause on its own is allowed + * + * Note that 'having' is only allowed with 'group by', but we don't enforce + * that in the grammar. */ query -// TODO: add with clause - : selectClause fromClause? whereClause? (groupByClause havingClause?)? - | fromClause whereClause? (groupByClause havingClause?)? selectClause? + : selectClause fromClause? whereClause? groupByClause? havingClause? + | fromClause whereClause? groupByClause? havingClause? selectClause? | whereClause ; diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index e1cf905ceabd..f5b92903f115 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -1175,30 +1175,40 @@ else if ( fetchClauseContext == null ) { public SqmQuerySpec visitQuery(HqlParser.QueryContext ctx) { final SqmQuerySpec sqmQuerySpec = currentQuerySpec(); + final HqlParser.FromClauseContext fromClauseContext = ctx.fromClause(); + final HqlParser.WhereClauseContext whereClauseContext = ctx.whereClause(); + final HqlParser.GroupByClauseContext groupByClauseContext = ctx.groupByClause(); + final HqlParser.HavingClauseContext havingClauseContext = ctx.havingClause(); + final HqlParser.SelectClauseContext selectClauseContext = ctx.selectClause(); + + if ( havingClauseContext != null && groupByClauseContext == null ) { + throw new SemanticException( "Query has 'having' but no 'group by'", query ); + } + // visit from clause first!!! final SqmFromClause fromClause = - ctx.fromClause() == null - ? buildInferredFromClause( ctx.selectClause() ) - : visitFromClause( ctx.fromClause() ); + fromClauseContext == null + ? buildInferredFromClause( selectClauseContext ) + : visitFromClause( fromClauseContext ); sqmQuerySpec.setFromClause( fromClause ); final SqmSelectClause selectClause = - ctx.selectClause() == null + selectClauseContext == null ? buildInferredSelectClause( fromClause ) - : visitSelectClause( ctx.selectClause() ); + : visitSelectClause( selectClauseContext ); sqmQuerySpec.setSelectClause( selectClause ); - final SqmWhereClause whereClause = new SqmWhereClause( creationContext.getNodeBuilder() ); - if ( ctx.whereClause() != null ) { - whereClause.setPredicate( (SqmPredicate) ctx.whereClause().accept( this ) ); + final SqmWhereClause whereClause = new SqmWhereClause( nodeBuilder() ); + if ( whereClauseContext != null ) { + whereClause.setPredicate( (SqmPredicate) whereClauseContext.accept( this ) ); } sqmQuerySpec.setWhereClause( whereClause ); - if ( ctx.groupByClause() != null ) { - sqmQuerySpec.setGroupByClauseExpressions( visitGroupByClause( ctx.groupByClause() ) ); + if ( groupByClauseContext != null ) { + sqmQuerySpec.setGroupByClauseExpressions( visitGroupByClause( groupByClauseContext ) ); } - if ( ctx.havingClause() != null ) { - sqmQuerySpec.setHavingClausePredicate( visitHavingClause( ctx.havingClause() ) ); + if ( havingClauseContext != null ) { + sqmQuerySpec.setHavingClausePredicate( visitHavingClause( havingClauseContext ) ); } return sqmQuerySpec; From ade8af8085103c0eef290e67688323fb80864737 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 21 May 2025 08:40:29 +0200 Subject: [PATCH 24/56] extract trivial convenience methods --- .../hql/internal/SemanticQueryBuilder.java | 370 +++++++++--------- 1 file changed, 191 insertions(+), 179 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index f5b92903f115..9888d96adae2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -71,6 +71,7 @@ import org.hibernate.query.criteria.JpaRoot; import org.hibernate.query.criteria.JpaSearchOrder; import org.hibernate.query.criteria.JpaXmlTableColumnNode; +import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.SqmBindableType; import org.hibernate.query.sqm.tree.domain.SqmEntityDomainType; import org.hibernate.query.sqm.tuple.internal.AnonymousTupleType; @@ -413,6 +414,14 @@ public Stack getProcessingStateStack() { return processingStateStack; } + private NodeBuilder nodeBuilder() { + return creationContext.getNodeBuilder(); + } + + private QueryEngine queryEngine() { + return creationContext.getQueryEngine(); + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Grammar rules @@ -453,7 +462,7 @@ else if ( parseTree instanceof HqlParser.DeleteStatementContext deleteStatementC @Override public SqmSelectStatement visitSelectStatement(HqlParser.SelectStatementContext ctx) { final HqlParser.QueryExpressionContext queryExpressionContext = ctx.queryExpression(); - final SqmSelectStatement selectStatement = new SqmSelectStatement<>( creationContext.getNodeBuilder() ); + final SqmSelectStatement selectStatement = new SqmSelectStatement<>( nodeBuilder() ); parameterCollector = selectStatement; @@ -482,7 +491,7 @@ public SqmRoot visitTargetEntity(HqlParser.TargetEntityContext dmlTargetConte (EntityDomainType) visitEntityName( dmlTargetContext.entityName() ), extractAlias( dmlTargetContext.variable() ), false, - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -502,7 +511,7 @@ public SqmInsertStatement visitInsertStatement(HqlParser.InsertStatementConte final HqlParser.QueryExpressionContext queryExpressionContext = ctx.queryExpression(); if ( queryExpressionContext != null ) { final SqmInsertSelectStatement insertStatement = - new SqmInsertSelectStatement<>( root, creationContext.getNodeBuilder() ); + new SqmInsertSelectStatement<>( root, nodeBuilder() ); parameterCollector = insertStatement; final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState( insertStatement, @@ -542,7 +551,7 @@ public SqmInsertStatement visitInsertStatement(HqlParser.InsertStatementConte } else { final SqmInsertValuesStatement insertStatement = - new SqmInsertValuesStatement<>( root, creationContext.getNodeBuilder() ); + new SqmInsertValuesStatement<>( root, nodeBuilder() ); parameterCollector = insertStatement; final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState( insertStatement, @@ -638,7 +647,7 @@ public SqmConflictClause visitConflictClause(HqlParser.ConflictClauseContext @Override public SqmUpdateStatement visitUpdateStatement(HqlParser.UpdateStatementContext ctx) { - final SqmUpdateStatement updateStatement = new SqmUpdateStatement<>( creationContext.getNodeBuilder() ); + final SqmUpdateStatement updateStatement = new SqmUpdateStatement<>( nodeBuilder() ); parameterCollector = updateStatement; final SqmDmlCreationProcessingState processingState = new SqmDmlCreationProcessingState( updateStatement, @@ -697,7 +706,7 @@ public SqmAssignment visitAssignment(HqlParser.AssignmentContext ctx) { @Override public SqmDeleteStatement visitDeleteStatement(HqlParser.DeleteStatementContext ctx) { - final SqmDeleteStatement deleteStatement = new SqmDeleteStatement<>( creationContext.getNodeBuilder() ); + final SqmDeleteStatement deleteStatement = new SqmDeleteStatement<>( nodeBuilder() ); parameterCollector = deleteStatement; final SqmDmlCreationProcessingState sqmDeleteCreationState = new SqmDmlCreationProcessingState( deleteStatement, this ); @@ -761,8 +770,8 @@ public Object visitCte(HqlParser.CteContext ctx) { (HqlParser.QueryExpressionContext) ctx.getChild( queryExpressionIndex ); final SqmSelectQuery cte = cteContainer instanceof SqmSubQuery subQuery - ? new SqmSubQuery<>( subQuery.getParent(), creationContext.getNodeBuilder() ) - : new SqmSelectStatement<>( creationContext.getNodeBuilder() ); + ? new SqmSubQuery<>( subQuery.getParent(), nodeBuilder() ) + : new SqmSelectStatement<>( nodeBuilder() ); processingStateStack.push( new SqmQueryPartCreationProcessingStateStandardImpl( processingStateStack.getCurrent(), @@ -800,7 +809,7 @@ public Object visitCte(HqlParser.CteContext ctx) { // The structure (SqmCteTable) for the SqmCteStatement is based on the non-recursive part, // which is necessary to have, so that the SqmCteRoot/SqmCteJoin can resolve sub-paths. final SqmSelectStatement recursivePart = - new SqmSelectStatement<>( creationContext.getNodeBuilder() ); + new SqmSelectStatement<>( nodeBuilder() ); processingStateStack.pop(); processingStateStack.push( @@ -962,7 +971,7 @@ private void applySearchClause(JpaCteCriteria cteDefinition, HqlParser.Search }; } } - searchOrders.add( creationContext.getNodeBuilder().search( attribute, sortOrder, nullPrecedence ) ); + searchOrders.add( nodeBuilder().search( attribute, sortOrder, nullPrecedence ) ); } cteDefinition.search( getCteSearchClauseKind( ctx ), searchAttributeName, searchOrders ); } @@ -1068,21 +1077,21 @@ private SqmQueryGroup getSqmQueryGroup( else { queryParts = new ArrayList<>( size - ( i >> 1 ) ); queryParts.add( queryGroup ); - queryGroup = new SqmQueryGroup<>( creationContext.getNodeBuilder(), operator, queryParts ); + queryGroup = new SqmQueryGroup<>( nodeBuilder(), operator, queryParts ); setCurrentQueryPart( queryGroup ); } try { final List subChildren = simpleQueryCtx.children; if ( subChildren.get( 0 ) instanceof HqlParser.QueryContext ) { - final SqmQuerySpec querySpec = new SqmQuerySpec<>( creationContext.getNodeBuilder() ); + final SqmQuerySpec querySpec = new SqmQuerySpec<>( nodeBuilder() ); queryParts.add( querySpec ); visitQuerySpecExpression( (HqlParser.QuerySpecExpressionContext) simpleQueryCtx ); } else { try { final SqmSelectStatement selectStatement = - new SqmSelectStatement<>( creationContext.getNodeBuilder() ); + new SqmSelectStatement<>( nodeBuilder() ); processingStateStack.push( new SqmQueryPartCreationProcessingStateStandardImpl( processingStateStack.getCurrent(), @@ -1231,7 +1240,7 @@ private SqmFromClause buildInferredFromClause(HqlParser.SelectClauseContext sele final EntityDomainType entityDescriptor = getResultEntity(); if ( entityDescriptor != null ) { final SqmRoot sqmRoot = - new SqmRoot<>( entityDescriptor, null, false, creationContext.getNodeBuilder() ); + new SqmRoot<>( entityDescriptor, null, false, nodeBuilder() ); processingStateStack.getCurrent().getPathRegistry().register( sqmRoot ); fromClause.addRoot( sqmRoot ); } @@ -1277,8 +1286,7 @@ protected SqmSelectClause buildInferredSelectClause(SqmFromClause fromClause) { query ); } - final NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); - + final NodeBuilder nodeBuilder = nodeBuilder(); for ( SqmRoot sqmRoot : fromClause.getRoots() ) { if ( "this".equals( sqmRoot.getExplicitAlias() ) ) { // we found an entity with the alias 'this' @@ -1379,7 +1387,7 @@ protected SqmSelectClause buildInferredSelectClause(SqmFromClause fromClause) { private void applyJoinsToInferredSelectClause(SqmFrom sqm, SqmSelectClause selectClause) { sqm.visitSqmJoins( sqmJoin -> { if ( sqmJoin.isImplicitlySelectable() ) { - selectClause.addSelection( new SqmSelection<>( sqmJoin, sqmJoin.getAlias(), creationContext.getNodeBuilder() ) ); + selectClause.addSelection( new SqmSelection<>( sqmJoin, sqmJoin.getAlias(), nodeBuilder() ) ); applyJoinsToInferredSelectClause( sqmJoin, selectClause ); } } ); @@ -1389,7 +1397,7 @@ private void applyJoinsToInferredSelectClause(SqmFrom sqm, SqmSelectClause public SqmSelectClause visitSelectClause(HqlParser.SelectClauseContext ctx) { // todo (6.0) : primer a select-clause-specific SemanticPathPart into the stack final SqmSelectClause selectClause = - new SqmSelectClause( ctx.DISTINCT() != null, creationContext.getNodeBuilder() ); + new SqmSelectClause( ctx.DISTINCT() != null, nodeBuilder() ); final HqlParser.SelectionListContext selectionListContext = ctx.selectionList(); for ( HqlParser.SelectionContext selectionContext : selectionListContext.selection() ) { selectClause.addSelection( visitSelection( selectionContext ) ); @@ -1407,7 +1415,7 @@ public SqmSelection visitSelection(HqlParser.SelectionContext ctx) { // NOTE : SqmSelection forces the alias down to its selectableNode. // - no need to do that here resultIdentifier, - creationContext.getNodeBuilder() + nodeBuilder() ); // if the node is not a dynamic-instantiation, register it with @@ -1470,10 +1478,10 @@ public SqmDynamicInstantiation visitInstantiation(HqlParser.InstantiationCont @Override public SqmDynamicInstantiation visitInstantiationTarget(HqlParser.InstantiationTargetContext ctx) { if ( ctx.MAP() != null ) { - return forMapInstantiation( mapJavaType, creationContext.getNodeBuilder() ); + return forMapInstantiation( mapJavaType, nodeBuilder() ); } else if ( ctx.LIST() != null ) { - return forListInstantiation( listJavaType, creationContext.getNodeBuilder() ); + return forListInstantiation( listJavaType, nodeBuilder() ); } else { final HqlParser.SimplePathContext simplePath = ctx.simplePath(); @@ -1482,7 +1490,7 @@ else if ( ctx.LIST() != null ) { } final String className = instantiationClassName( simplePath ); try { - return forClassInstantiation( resolveInstantiationTargetType( className ), creationContext.getNodeBuilder() ); + return forClassInstantiation( resolveInstantiationTargetType( className ), nodeBuilder() ); } catch (ClassLoadingException e) { throw new SemanticException( "Could not resolve class '" + className + "' named for instantiation", query ); @@ -1510,7 +1518,7 @@ public SqmDynamicInstantiationArgument visitInstantiationArgument(HqlParser.I final SqmSelectableNode argExpression = (SqmSelectableNode) ctx.instantiationArgumentExpression().accept( this ); final SqmDynamicInstantiationArgument argument = - new SqmDynamicInstantiationArgument<>( argExpression, alias, creationContext.getNodeBuilder() ); + new SqmDynamicInstantiationArgument<>( argExpression, alias, nodeBuilder() ); if ( !(argExpression instanceof SqmDynamicInstantiation) ) { processingStateStack.getCurrent().getPathRegistry().register( argument ); } @@ -1550,7 +1558,7 @@ private SqmExpression resolveOrderByOrGroupByExpression( ParseTree child, boolean definedCollate, boolean allowPositionalOrAliases) { - final NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); + final NodeBuilder nodeBuilder = nodeBuilder(); final SqmCreationProcessingState processingState = processingStateStack.getCurrent(); final SqmQuery processingQuery = processingState.getProcessingQuery(); final SqmQueryPart queryPart; @@ -2046,7 +2054,7 @@ public SqmRoot visitRootEntity(HqlParser.RootEntityContext ctx) { } final SqmRoot sqmRoot = - new SqmRoot<>( entityDescriptor, alias, true, creationContext.getNodeBuilder() ); + new SqmRoot<>( entityDescriptor, alias, true, nodeBuilder() ); pathRegistry.register( sqmRoot ); return sqmRoot; } @@ -2371,7 +2379,7 @@ public SqmPredicate visitWhereClause(HqlParser.WhereClauseContext ctx) { public SqmGroupedPredicate visitGroupedPredicate(HqlParser.GroupedPredicateContext ctx) { return new SqmGroupedPredicate( (SqmPredicate) ctx.predicate().accept( this ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -2406,7 +2414,7 @@ private SqmPredicate junction(Predicate.BooleanOperator operator, SqmPredicate l return junction; } } - return new SqmJunctionPredicate( operator, lhs, rhs, creationContext.getNodeBuilder() ); + return new SqmJunctionPredicate( operator, lhs, rhs, nodeBuilder() ); } @Override @@ -2417,7 +2425,7 @@ public SqmPredicate visitNegatedPredicate(HqlParser.NegatedPredicateContext ctx) return predicate; } else { - return new SqmNegatedPredicate( predicate, creationContext.getNodeBuilder() ); + return new SqmNegatedPredicate( predicate, nodeBuilder() ); } } @@ -2428,7 +2436,7 @@ public SqmBetweenPredicate visitBetweenPredicate(HqlParser.BetweenPredicateConte (SqmExpression) ctx.expression( 1 ).accept( this ), (SqmExpression) ctx.expression( 2 ).accept( this ), ctx.NOT() != null, - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -2438,7 +2446,7 @@ public SqmNullnessPredicate visitIsNullPredicate(HqlParser.IsNullPredicateContex return new SqmNullnessPredicate( (SqmExpression) ctx.expression().accept( this ), ctx.NOT() != null, - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -2449,7 +2457,7 @@ public SqmEmptinessPredicate visitIsEmptyPredicate(HqlParser.IsEmptyPredicateCon return new SqmEmptinessPredicate( pluralValuedSimplePath, ctx.NOT() != null, - creationContext.getNodeBuilder() + nodeBuilder() ); } else { @@ -2463,7 +2471,7 @@ public Object visitIsTruePredicate(HqlParser.IsTruePredicateContext ctx) { (SqmExpression) ctx.expression().accept( this ), true, ctx.NOT() != null, - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -2473,7 +2481,7 @@ public Object visitIsFalsePredicate(HqlParser.IsFalsePredicateContext ctx) { (SqmExpression) ctx.expression().accept( this ), false, ctx.NOT() != null, - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -2554,7 +2562,7 @@ else if ( r instanceof AnyDiscriminatorSqmPath anyDiscriminatorPath && l inst left, comparisonOperator, right, - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -2566,7 +2574,7 @@ private SqmExpression createDiscriminatorValue( expressible.getPathName(), getJpaMetamodel().resolveHqlEntityReference( valueExpressionContext.getText() ), expressible.getPathType(), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -2646,9 +2654,9 @@ public SqmPredicate visitContainsPredicate(HqlParser.ContainsPredicateContext ct final SelfRenderingSqmFunction contains = getFunctionDescriptor( "array_contains" ).generateSqmExpression( asList( lhs, rhs ), null, - creationContext.getQueryEngine() + queryEngine() ); - return new SqmBooleanExpressionPredicate( contains, negated, creationContext.getNodeBuilder() ); + return new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() ); } @Override @@ -2666,7 +2674,7 @@ public SqmExpression visitJsonValueFunction(HqlParser.JsonValueFunctionContex ? asList( jsonDocument, jsonPath ) : asList( jsonDocument, jsonPath, castTarget ), null, - creationContext.getQueryEngine() + queryEngine() ); visitJsonValueOnErrorOrEmptyClause( jsonValue, ctx.jsonValueOnErrorOrEmptyClause() ); final HqlParser.JsonPassingClauseContext passingClause = ctx.jsonPassingClause(); @@ -2714,7 +2722,7 @@ public SqmExpression visitJsonQueryFunction(HqlParser.JsonQueryFunctionContex final SqmJsonQueryExpression jsonQuery = (SqmJsonQueryExpression) getFunctionDescriptor( "json_query" ).generateSqmExpression( asList( jsonDocument, jsonPath ), null, - creationContext.getQueryEngine() + queryEngine() ); visitJsonQueryWrapperClause( jsonQuery, ctx.jsonQueryWrapperClause() ); visitJsonQueryOnErrorOrEmptyClause( jsonQuery, ctx.jsonQueryOnErrorOrEmptyClause() ); @@ -2796,7 +2804,7 @@ public SqmExpression visitJsonExistsFunction(HqlParser.JsonExistsFunctionCont final SqmJsonExistsExpression jsonExists = (SqmJsonExistsExpression) getFunctionDescriptor( "json_exists" ).generateSqmExpression( asList( jsonDocument, jsonPath ), null, - creationContext.getQueryEngine() + queryEngine() ); final HqlParser.JsonExistsOnErrorClauseContext subCtx = ctx.jsonExistsOnErrorClause(); if ( subCtx != null ) { @@ -2842,7 +2850,7 @@ public SqmExpression visitJsonArrayFunction(HqlParser.JsonArrayFunctionContex return getFunctionDescriptor( "json_array" ).generateSqmExpression( arguments, null, - creationContext.getQueryEngine() + queryEngine() ); } @@ -2874,7 +2882,7 @@ public SqmExpression visitJsonObjectFunction(HqlParser.JsonObjectFunctionCont return getFunctionDescriptor( "json_object" ).generateSqmExpression( arguments, null, - creationContext.getQueryEngine() + queryEngine() ); } @@ -2899,7 +2907,7 @@ public Object visitJsonArrayAggFunction(HqlParser.JsonArrayAggFunctionContext ct ? null : visitOrderByClause( ctx.orderByClause(), false ), null, - creationContext.getQueryEngine() + queryEngine() ); } @@ -2932,7 +2940,7 @@ public Object visitJsonObjectAggFunction(HqlParser.JsonObjectAggFunctionContext arguments, getFilterExpression( ctx ), null, - creationContext.getQueryEngine() + queryEngine() ); } @@ -2943,12 +2951,12 @@ public Object visitJsonTableFunction(HqlParser.JsonTableFunctionContext ctx) { final SqmExpression jsonDocument = (SqmExpression) argumentsContexts.get( 0 ).accept( this ); final SqmJsonTableFunction jsonTable; if ( argumentsContexts.size() == 1 ) { - jsonTable = creationContext.getNodeBuilder().jsonTable( jsonDocument ); + jsonTable = nodeBuilder().jsonTable( jsonDocument ); } else { //noinspection unchecked final SqmExpression jsonPath = (SqmExpression) argumentsContexts.get( 1 ).accept( this ); - jsonTable = creationContext.getNodeBuilder().jsonTable( jsonDocument, jsonPath ); + jsonTable = nodeBuilder().jsonTable( jsonDocument, jsonPath ); } final HqlParser.JsonPassingClauseContext passingClauseContext = ctx.jsonPassingClause(); if ( passingClauseContext != null ) { @@ -3038,7 +3046,7 @@ private void checkJsonFunctionsEnabled(ParserRuleContext ctx) { public SqmExpression visitXmlelementFunction(HqlParser.XmlelementFunctionContext ctx) { checkXmlFunctionsEnabled( ctx ); final String elementName = visitIdentifier( ctx.identifier() ); - final SqmXmlElementExpression xmlelement = creationContext.getNodeBuilder().xmlelement( elementName ); + final SqmXmlElementExpression xmlelement = nodeBuilder().xmlelement( elementName ); final HqlParser.XmlattributesFunctionContext attributeCtx = ctx.xmlattributesFunction(); if ( attributeCtx != null ) { final List expressions = attributeCtx.expressionOrPredicate(); @@ -3079,7 +3087,7 @@ public SqmExpression visitXmlforestFunction(HqlParser.XmlforestFunctionContex } } } - return creationContext.getNodeBuilder().xmlforest( elementExpressions ); + return nodeBuilder().xmlforest( elementExpressions ); } @Override @@ -3089,8 +3097,8 @@ public SqmExpression visitXmlpiFunction(HqlParser.XmlpiFunctionContext ctx) { final HqlParser.ExpressionContext exprCtx = ctx.expression(); //noinspection unchecked return exprCtx == null - ? creationContext.getNodeBuilder().xmlpi( name ) - : creationContext.getNodeBuilder().xmlpi( name, (Expression) exprCtx.accept( this ) ); + ? nodeBuilder().xmlpi( name ) + : nodeBuilder().xmlpi( name, (Expression) exprCtx.accept( this ) ); } @Override @@ -3098,7 +3106,7 @@ public SqmExpression visitXmlqueryFunction(HqlParser.XmlqueryFunctionContext checkXmlFunctionsEnabled( ctx ); final SqmExpression query = (SqmExpression) ctx.expression( 0 ).accept( this ); final SqmExpression xmlDocument = (SqmExpression) ctx.expression( 1 ).accept( this ); - return creationContext.getNodeBuilder().xmlquery( query, xmlDocument ); + return nodeBuilder().xmlquery( query, xmlDocument ); } @Override @@ -3106,7 +3114,7 @@ public SqmExpression visitXmlexistsFunction(HqlParser.XmlexistsFunctionContex checkXmlFunctionsEnabled( ctx ); final SqmExpression query = (SqmExpression) ctx.expression( 0 ).accept( this ); final SqmExpression xmlDocument = (SqmExpression) ctx.expression( 1 ).accept( this ); - return creationContext.getNodeBuilder().xmlexists( query, xmlDocument ); + return nodeBuilder().xmlexists( query, xmlDocument ); } @Override @@ -3124,7 +3132,7 @@ public SqmExpression visitXmlaggFunction(HqlParser.XmlaggFunctionContext ctx) ? null : visitOrderByClause( ctx.orderByClause(), false ), null, - creationContext.getQueryEngine() + queryEngine() ) ); } @@ -3136,7 +3144,7 @@ public Object visitXmltableFunction(HqlParser.XmltableFunctionContext ctx) { //noinspection unchecked final SqmExpression xpath = (SqmExpression) argumentsContexts.get( 0 ).accept( this ); final SqmExpression document = (SqmExpression) argumentsContexts.get( 1 ).accept( this ); - final SqmXmlTableFunction xmlTable = creationContext.getNodeBuilder().xmlTable( xpath, document); + final SqmXmlTableFunction xmlTable = nodeBuilder().xmlTable( xpath, document); visitColumns( xmlTable, ctx.xmltableColumnsClause().xmltableColumn() ); return xmlTable; } @@ -3209,9 +3217,9 @@ public SqmPredicate visitIncludesPredicate(HqlParser.IncludesPredicateContext ct final SelfRenderingSqmFunction contains = getFunctionDescriptor( "array_includes" ).generateSqmExpression( asList( lhs, rhs ), null, - creationContext.getQueryEngine() + queryEngine() ); - return new SqmBooleanExpressionPredicate( contains, negated, creationContext.getNodeBuilder() ); + return new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() ); } @Override @@ -3230,9 +3238,9 @@ public SqmPredicate visitIntersectsPredicate(HqlParser.IntersectsPredicateContex final SelfRenderingSqmFunction contains = getFunctionDescriptor( "array_intersects" ).generateSqmExpression( asList( lhs, rhs ), null, - creationContext.getQueryEngine() + queryEngine() ); - return new SqmBooleanExpressionPredicate( contains, negated, creationContext.getNodeBuilder() ); + return new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() ); } @Override @@ -3245,7 +3253,7 @@ public SqmPredicate visitLikePredicate(HqlParser.LikePredicateContext ctx) { (SqmExpression) ctx.expression(1).accept( this ), negated, caseSensitive, - creationContext.getNodeBuilder() + nodeBuilder() ); } else { @@ -3255,7 +3263,7 @@ public SqmPredicate visitLikePredicate(HqlParser.LikePredicateContext ctx) { (SqmExpression) ctx.likeEscape().accept( this ), negated, caseSensitive, - creationContext.getNodeBuilder() + nodeBuilder() ); } } @@ -3263,7 +3271,7 @@ public SqmPredicate visitLikePredicate(HqlParser.LikePredicateContext ctx) { @Override public Object visitLikeEscape(HqlParser.LikeEscapeContext ctx) { final HqlParser.ParameterContext parameter = ctx.parameter(); - final BasicType characterType = creationContext.getNodeBuilder().getCharacterType(); + final BasicType characterType = nodeBuilder().getCharacterType(); if ( parameter instanceof HqlParser.NamedParameterContext namedParameterContext ) { return visitNamedParameter( namedParameterContext, characterType ); } @@ -3279,7 +3287,7 @@ else if ( parameter instanceof HqlParser.PositionalParameterContext positionalPa query ); } - return new SqmLiteral<>( escape.charAt( 0 ), characterType, creationContext.getNodeBuilder() ); + return new SqmLiteral<>( escape.charAt( 0 ), characterType, nodeBuilder() ); } } @@ -3292,7 +3300,7 @@ public SqmPredicate visitMemberOfPredicate(HqlParser.MemberOfPredicateContext ct (SqmExpression) ctx.expression().accept( this ), pluralValuedSimplePath, negated, - creationContext.getNodeBuilder() + nodeBuilder() ); } else { @@ -3341,7 +3349,7 @@ public SqmPredicate visitInPredicate(HqlParser.InPredicateContext ctx) { testExpression, listExpressions, negated, - creationContext.getNodeBuilder() + nodeBuilder() ); } finally { @@ -3355,7 +3363,7 @@ else if ( inListContext instanceof HqlParser.ParamInListContext tupleExpressionL testExpression, singletonList( tupleExpressionListContext.parameter().accept( this ) ), negated, - creationContext.getNodeBuilder() + nodeBuilder() ); } finally { @@ -3367,7 +3375,7 @@ else if ( inListContext instanceof HqlParser.SubqueryInListContext subQueryOrPar testExpression, visitSubquery( subQueryOrParamInListContext.subquery() ), negated, - creationContext.getNodeBuilder() + nodeBuilder() ); } else if ( inListContext instanceof HqlParser.PersistentCollectionReferenceInListContext collectionReferenceInListContext ) { @@ -3381,7 +3389,7 @@ else if ( inListContext instanceof HqlParser.PersistentCollectionReferenceInList (TerminalNode) collectionReferenceInListContext.collectionQuantifier().getChild(0).getChild(0) ), negated, - creationContext.getNodeBuilder() + nodeBuilder() ); } else if ( inListContext instanceof HqlParser.ArrayInListContext arrayInListContext ) { @@ -3404,9 +3412,9 @@ else if ( inListContext instanceof HqlParser.ArrayInListContext arrayInListConte final SelfRenderingSqmFunction contains = getFunctionDescriptor( "array_contains" ).generateSqmExpression( asList( arrayExpr, testExpression ), null, - creationContext.getQueryEngine() + queryEngine() ); - return new SqmBooleanExpressionPredicate( contains, negated, creationContext.getNodeBuilder() ); + return new SqmBooleanExpressionPredicate( contains, negated, nodeBuilder() ); } else { throw new ParsingException( "Unexpected IN predicate type [" + ctx.getClass().getSimpleName() + "] : " @@ -3417,13 +3425,13 @@ else if ( inListContext instanceof HqlParser.ArrayInListContext arrayInListConte @Override public SqmPredicate visitExistsCollectionPartPredicate(HqlParser.ExistsCollectionPartPredicateContext ctx) { final SqmSubQuery subQuery = createCollectionReferenceSubQuery( ctx.simplePath(), null ); - return new SqmExistsPredicate( subQuery, creationContext.getNodeBuilder() ); + return new SqmExistsPredicate( subQuery, nodeBuilder() ); } @Override public SqmPredicate visitExistsPredicate(HqlParser.ExistsPredicateContext ctx) { final SqmExpression expression = (SqmExpression) ctx.expression().accept( this ); - return new SqmExistsPredicate( expression, creationContext.getNodeBuilder() ); + return new SqmExistsPredicate( expression, nodeBuilder() ); } @Override @@ -3437,7 +3445,7 @@ public SqmPredicate visitBooleanExpressionPredicate(HqlParser.BooleanExpressionP } @SuppressWarnings("unchecked") final SqmExpression booleanExpression = (SqmExpression) expression; - return new SqmBooleanExpressionPredicate( booleanExpression, creationContext.getNodeBuilder() ); + return new SqmBooleanExpressionPredicate( booleanExpression, nodeBuilder() ); } @Override @@ -3452,7 +3460,7 @@ public Object visitEntityTypeExpression(HqlParser.EntityTypeExpressionContext ct // we have form (2) return new SqmParameterizedEntityType<>( (SqmParameter) parameter.accept( this ), - creationContext.getNodeBuilder() + nodeBuilder() ); } else if ( path != null ) { @@ -3588,7 +3596,7 @@ public SqmFkExpression visitToOneFkReference(HqlParser.ToOneFkReferenceContex public SqmMapEntryReference visitMapEntrySelection(HqlParser.MapEntrySelectionContext ctx) { return new SqmMapEntryReference<>( consumePluralAttributeReference( ctx.path() ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -3607,7 +3615,7 @@ public SqmExpression visitConcatenationExpression(HqlParser.ConcatenationExpr return getFunctionDescriptor( "array_concat" ).generateSqmExpression( asList( lhs, rhs ), null, - creationContext.getQueryEngine() + queryEngine() ); } else { @@ -3615,7 +3623,7 @@ public SqmExpression visitConcatenationExpression(HqlParser.ConcatenationExpr return getFunctionDescriptor( "array_append" ).generateSqmExpression( asList( lhs, rhs ), null, - creationContext.getQueryEngine() + queryEngine() ); } } @@ -3625,7 +3633,7 @@ else if ( rhsExpressible != null && rhsExpressible.getSqmType() instanceof Basic return getFunctionDescriptor( "array_concat" ).generateSqmExpression( asList( lhs, rhs ), null, - creationContext.getQueryEngine() + queryEngine() ); } else { @@ -3633,7 +3641,7 @@ else if ( rhsExpressible != null && rhsExpressible.getSqmType() instanceof Basic return getFunctionDescriptor( "array_prepend" ).generateSqmExpression( asList( lhs, rhs ), null, - creationContext.getQueryEngine() + queryEngine() ); } } @@ -3641,7 +3649,7 @@ else if ( rhsExpressible != null && rhsExpressible.getSqmType() instanceof Basic return getFunctionDescriptor( "concat" ).generateSqmExpression( asList( lhs, rhs ), null, - creationContext.getQueryEngine() + queryEngine() ); } } @@ -3695,7 +3703,7 @@ public Object visitAdditionExpression(HqlParser.AdditionExpressionContext ctx) { operator, left, right, - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -3714,7 +3722,7 @@ public Object visitMultiplicationExpression(HqlParser.MultiplicationExpressionCo return getFunctionDescriptor("mod").generateSqmExpression( asList( left, right ), null, - creationContext.getQueryEngine() + queryEngine() ); } else { @@ -3722,7 +3730,7 @@ public Object visitMultiplicationExpression(HqlParser.MultiplicationExpressionCo operator, left, right, - creationContext.getNodeBuilder() + nodeBuilder() ); } } @@ -3733,7 +3741,7 @@ public Object visitToDurationExpression(HqlParser.ToDurationExpressionContext ct (SqmExpression) ctx.expression().accept( this ), toDurationUnit( (SqmExtractUnit) ctx.datetimeField().accept( this ) ), resolveExpressibleTypeBasic( Duration.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -3741,7 +3749,7 @@ private SqmDurationUnit toDurationUnit(SqmExtractUnit extractUnit) { return new SqmDurationUnit<>( extractUnit.getUnit(), resolveExpressibleTypeBasic( Long.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -3753,7 +3761,7 @@ public Object visitFromDurationExpression(HqlParser.FromDurationExpressionContex toDurationUnit( (SqmExtractUnit) ctx.datetimeField().accept( this ) ), expression, resolveExpressibleTypeBasic( Long.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -3784,7 +3792,7 @@ public Object visitCollateFunction(HqlParser.CollateFunctionContext ctx) { return getFunctionDescriptor("collate").generateSqmExpression( asList( expressionToCollate, castTargetExpression ), null, //why not string? - creationContext.getQueryEngine() + queryEngine() ); } @@ -3805,7 +3813,7 @@ public Object visitCollation(HqlParser.CollationContext ctx) { collation.append("\""); } return new SqmCollation( collation.toString(), null, - creationContext.getNodeBuilder() ); + nodeBuilder() ); } @Override @@ -3816,7 +3824,7 @@ public Object visitTupleExpression(HqlParser.TupleExpressionContext ctx) { ); } final List> expressions = visitExpressions( ctx ); - return new SqmTuple<>( expressions, creationContext.getNodeBuilder() ); + return new SqmTuple<>( expressions, nodeBuilder() ); } private List> visitExpressions(ParserRuleContext parentContext) { @@ -3842,7 +3850,7 @@ public Object visitCaseExpression(HqlParser.CaseExpressionContext ctx) { public SqmCaseSimple visitSimpleCaseList(HqlParser.SimpleCaseListContext ctx) { final int size = ctx.simpleCaseWhen().size(); final SqmExpression expression = (SqmExpression) ctx.expressionOrPredicate().accept(this); - final SqmCaseSimple caseExpression = new SqmCaseSimple<>( expression, size, creationContext.getNodeBuilder() ); + final SqmCaseSimple caseExpression = new SqmCaseSimple<>( expression, size, nodeBuilder() ); for ( int i = 0; i < size; i++ ) { final HqlParser.SimpleCaseWhenContext simpleCaseWhenContext = ctx.simpleCaseWhen( i ); @@ -3876,7 +3884,7 @@ public Object visitCaseExpression(HqlParser.CaseExpressionContext ctx) { @Override @SuppressWarnings({"rawtypes", "unchecked"}) public SqmCaseSearched visitSearchedCaseList(HqlParser.SearchedCaseListContext ctx) { final int size = ctx.searchedCaseWhen().size(); - final SqmCaseSearched caseExpression = new SqmCaseSearched<>( size, creationContext.getNodeBuilder() ); + final SqmCaseSearched caseExpression = new SqmCaseSearched<>( size, nodeBuilder() ); for ( int i = 0; i < size; i++ ) { final HqlParser.SearchedCaseWhenContext searchedCaseWhenContext = ctx.searchedCaseWhen( i ); @@ -3899,7 +3907,7 @@ public SqmExpression visitCurrentDateFunction(HqlParser.CurrentDateFunctionCo return getFunctionDescriptor("current_date") .generateSqmExpression( resolveExpressibleTypeBasic( Date.class ), - creationContext.getQueryEngine() + queryEngine() ); } @@ -3908,7 +3916,7 @@ public SqmExpression visitCurrentTimeFunction(HqlParser.CurrentTimeFunctionCo return getFunctionDescriptor("current_time") .generateSqmExpression( resolveExpressibleTypeBasic( Time.class ), - creationContext.getQueryEngine() + queryEngine() ); } @@ -3917,7 +3925,7 @@ public SqmExpression visitCurrentTimestampFunction(HqlParser.CurrentTimestamp return getFunctionDescriptor("current_timestamp") .generateSqmExpression( resolveExpressibleTypeBasic( Timestamp.class ), - creationContext.getQueryEngine() + queryEngine() ); } @@ -3926,7 +3934,7 @@ public SqmExpression visitInstantFunction(HqlParser.InstantFunctionContext ct return getFunctionDescriptor("instant") .generateSqmExpression( resolveExpressibleTypeBasic( Instant.class ), - creationContext.getQueryEngine() + queryEngine() ); } @@ -3935,7 +3943,7 @@ public SqmExpression visitLocalDateFunction(HqlParser.LocalDateFunctionContex return getFunctionDescriptor("local_date") .generateSqmExpression( resolveExpressibleTypeBasic( LocalDate.class ), - creationContext.getQueryEngine() + queryEngine() ); } @@ -3944,7 +3952,7 @@ public SqmExpression visitLocalTimeFunction(HqlParser.LocalTimeFunctionContex return getFunctionDescriptor("local_time") .generateSqmExpression( resolveExpressibleTypeBasic( LocalTime.class ), - creationContext.getQueryEngine() + queryEngine() ); } @@ -3953,7 +3961,7 @@ public SqmExpression visitLocalDateTimeFunction(HqlParser.LocalDateTimeFuncti return getFunctionDescriptor("local_datetime") .generateSqmExpression( resolveExpressibleTypeBasic( LocalDateTime.class ), - creationContext.getQueryEngine() + queryEngine() ); } @@ -3962,7 +3970,7 @@ public SqmExpression visitOffsetDateTimeFunction(HqlParser.OffsetDateTimeFunc return getFunctionDescriptor("offset_datetime") .generateSqmExpression( resolveExpressibleTypeBasic( OffsetDateTime.class ), - creationContext.getQueryEngine() + queryEngine() ); } @@ -4022,7 +4030,7 @@ public Object visitArrayLiteral(HqlParser.ArrayLiteralContext ctx) { return getFunctionDescriptor( "array" ).generateSqmExpression( arguments, null, - creationContext.getQueryEngine() + queryEngine() ); } @@ -4048,7 +4056,7 @@ public SqmExpression visitTerminal(TerminalNode node) { case HqlParser.BIG_DECIMAL_LITERAL -> bigDecimalLiteral( node.getText() ); case HqlParser.FALSE -> booleanLiteral( false ); case HqlParser.TRUE -> booleanLiteral( true ); - case HqlParser.NULL -> new SqmLiteralNull<>( creationContext.getNodeBuilder() ); + case HqlParser.NULL -> new SqmLiteralNull<>( nodeBuilder() ); case HqlParser.BINARY_LITERAL -> binaryLiteral( node.getText() ); default -> throw new ParsingException( "Unexpected terminal node [" + node.getText() + "]" ); }; @@ -4156,7 +4164,7 @@ private SqmLiteral localDateTimeLiteralFrom( return new SqmLiteral<>( LocalDateTime.of( localDate( date ), localTime( time ) ), resolveExpressibleTypeBasic( LocalDateTime.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -4167,7 +4175,7 @@ private SqmLiteral zonedDateTimeLiteralFrom( return new SqmLiteral<>( ZonedDateTime.of( localDate( date ), localTime( time ), visitZoneId( timezone ) ), resolveExpressibleTypeBasic( ZonedDateTime.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -4197,7 +4205,7 @@ private SqmLiteral offsetDatetimeLiteralFrom( return new SqmLiteral<>( OffsetDateTime.of( localDate( date ), localTime( time ), zoneOffset( offset ) ), resolveExpressibleTypeBasic( OffsetDateTime.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -4208,7 +4216,7 @@ private SqmLiteral offsetDatetimeLiteralFrom( return new SqmLiteral<>( OffsetDateTime.of( localDate( date ), localTime( time ), zoneOffset( offset ) ), resolveExpressibleTypeBasic( OffsetDateTime.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -4217,7 +4225,7 @@ public Object visitDate(HqlParser.DateContext ctx) { return new SqmLiteral<>( localDate( ctx ), resolveExpressibleTypeBasic( LocalDate.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -4226,7 +4234,7 @@ public Object visitTime(HqlParser.TimeContext ctx) { return new SqmLiteral<>( localTime( ctx ), resolveExpressibleTypeBasic( LocalTime.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } @@ -4291,15 +4299,19 @@ private static ZoneOffset zoneOffset(HqlParser.OffsetWithMinutesContext offset) ); } + private static CharSequence stripQuotes(String literalText) { + return literalText.subSequence( 1, literalText.length() - 1 ); + } + private SqmLiteral sqlTimestampLiteralFrom(String literalText) { - final TemporalAccessor parsed = DATE_TIME.parse( literalText.subSequence( 1, literalText.length() - 1 ) ); + final TemporalAccessor parsed = DATE_TIME.parse( stripQuotes( literalText ) ); try { final ZonedDateTime zonedDateTime = ZonedDateTime.from( parsed ); final Calendar literal = GregorianCalendar.from( zonedDateTime ); return new SqmLiteral<>( literal, resolveExpressibleTypeBasic( Calendar.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } catch (DateTimeException dte) { @@ -4308,28 +4320,28 @@ private SqmLiteral sqlTimestampLiteralFrom(String literalText) { return new SqmLiteral<>( literal, resolveExpressibleTypeBasic( Timestamp.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } } private SqmLiteral sqlDateLiteralFrom(String literalText) { - final LocalDate localDate = LocalDate.from( ISO_LOCAL_DATE.parse( literalText.subSequence( 1, literalText.length() - 1 ) ) ); + final LocalDate localDate = LocalDate.from( ISO_LOCAL_DATE.parse( stripQuotes( literalText ) ) ); final Date literal = Date.valueOf( localDate ); return new SqmLiteral<>( literal, resolveExpressibleTypeBasic( Date.class ), - creationContext.getNodeBuilder() + nodeBuilder() ); } private SqmLiteral