From 7565aeb77342291a364290f225288810f7ff01d7 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 28 May 2025 16:26:09 +0200 Subject: [PATCH 001/185] [#1793] Test HQL 'new' construct with many-to-one Test case showing the issue has been resolved --- .../org/hibernate/reactive/HQLQueryTest.java | 152 +++++++++++++++++- 1 file changed, 149 insertions(+), 3 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java index e48d3ec69..00e541285 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java @@ -5,6 +5,10 @@ */ package org.hibernate.reactive; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -20,6 +24,8 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; +import static jakarta.persistence.CascadeType.PERSIST; +import static jakarta.persistence.FetchType.LAZY; import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -34,15 +40,22 @@ public class HQLQueryTest extends BaseReactiveTest { Flour rye = new Flour( 2, "Rye", "Used to bake the traditional sourdough breads of Germany.", "Wheat flour" ); Flour almond = new Flour( 3, "Almond", "made from ground almonds.", "Gluten free" ); + Author miller = new Author( "Madeline Miller"); + Author camilleri = new Author( "Andrea Camilleri"); + Book circe = new Book( "9780316556347", "Circe", miller ); + Book shapeOfWater = new Book( "0-330-49286-1 ", "The Shape of Water", camilleri ); + Book spider = new Book( "978-0-14-311203-7", "The Patience of the Spider", camilleri ); + @Override protected Collection> annotatedEntities() { - return List.of( Flour.class ); + return List.of( Flour.class, Book.class, Author.class ); } @BeforeEach public void populateDb(VertxTestContext context) { - test( context, getMutinySessionFactory() - .withTransaction( (session, transaction) -> session.persistAll( spelt, rye, almond ) ) ); + test( context, getMutinySessionFactory().withTransaction( session -> session + .persistAll( spelt, rye, almond, miller, camilleri, circe, shapeOfWater, spider ) ) + ); } @Test @@ -129,6 +142,21 @@ public void testFromQuery(VertxTestContext context) { ); } + @Test + public void testSelectNewConstructor(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( session -> session + .createQuery( "SELECT NEW Book(b.title, b.author) FROM Book b ORDER BY b.title DESC", Book.class ) + .getResultList() + ) + .invoke( books -> assertThat( books ).containsExactly( + new Book( shapeOfWater.title, camilleri ), + new Book( spider.title, camilleri ), + new Book( circe.title, miller ) + ) ) + ); + } + @Entity(name = "Flour") @Table(name = "Flour") public static class Flour { @@ -204,4 +232,122 @@ public int hashCode() { return Objects.hash( name, description, type ); } } + + @Entity(name = "Book") + @Table(name = "Book_HQL") + public static class Book { + @Id + @GeneratedValue + private Integer id; + + private String isbn; + + private String title; + + @ManyToOne(fetch = LAZY) + private Author author; + + public Book() { + } + + public Book(String title, Author author) { + this.title = title; + this.author = author; + } + + public Book(String isbn, String title, Author author) { + this.isbn = isbn; + this.title = title; + this.author = author; + author.books.add( this ); + } + + public Integer getId() { + return id; + } + + public String getIsbn() { + return isbn; + } + + public String getTitle() { + return title; + } + + public Author getAuthor() { + return author; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book) o; + return Objects.equals( isbn, book.isbn ) && Objects.equals( + title, + book.title + ) && Objects.equals( author, book.author ); + } + + @Override + public int hashCode() { + return Objects.hash( isbn, title, author ); + } + + @Override + public String toString() { + return id + ":" + isbn + ":" + title + ":" + author; + } + } + + @Entity(name = "Author") + @Table(name = "Author_HQL") + public static class Author { + @Id @GeneratedValue + private Integer id; + + private String name; + + @OneToMany(mappedBy = "author", cascade = PERSIST) + private List books = new ArrayList<>(); + + public Author() { + } + + public Author(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public List getBooks() { + return books; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Author author = (Author) o; + return Objects.equals( name, author.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + + @Override + public String toString() { + return id + ":" + name; + } + } } From 2d6f6c7363c186b726be760d5f65f18fd098c821 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 30 May 2025 10:36:08 +0200 Subject: [PATCH 002/185] chore: clean up HQLQueryTest class * Code formattation * Replace Jupiter assertions with AssertJ ones --- .../org/hibernate/reactive/HQLQueryTest.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java index 00e541285..77331fe2b 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/HQLQueryTest.java @@ -5,9 +5,6 @@ */ package org.hibernate.reactive; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -21,7 +18,10 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import static jakarta.persistence.CascadeType.PERSIST; @@ -29,11 +29,8 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; @Timeout(value = 10, timeUnit = MINUTES) - public class HQLQueryTest extends BaseReactiveTest { Flour spelt = new Flour( 1, "Spelt", "An ancient grain, is a hexaploid species of wheat.", "Wheat flour" ); @@ -82,7 +79,7 @@ public void testAutoFlushOnResultList(VertxTestContext context) { public void testSelectScalarString(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "SELECT 'Prova' FROM Flour WHERE id = " + rye.getId(), String.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( found -> assertEquals( "Prova", found ) ) ); } @@ -91,7 +88,7 @@ public void testSelectScalarString(VertxTestContext context) { public void testSelectScalarCount(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "SELECT count(*) FROM Flour", Long.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( found -> assertEquals( 3L, found ) ) ); } @@ -99,14 +96,17 @@ public void testSelectScalarCount(VertxTestContext context) { @Test public void testSelectWithMultipleScalarValues(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { - Stage.SelectionQuery qr = s.createSelectionQuery( "SELECT 'Prova', f.id FROM Flour f WHERE f.id = " + rye.getId(), Object[].class ); - assertNotNull( qr ); - return qr.getSingleResult(); - } ).thenAccept( found -> { - assertTrue( found instanceof Object[] ); - assertEquals( "Prova", ( (Object[]) found )[0] ); - assertEquals( rye.getId(), ( (Object[]) found )[1] ); - } ) + Stage.SelectionQuery qr = s.createSelectionQuery( + "SELECT 'Prova', f.id FROM Flour f WHERE f.id = " + rye.getId(), + Object[].class + ); + assertThat( qr ).isNotNull(); + return qr.getSingleResult(); + } ).thenAccept( found -> { + assertThat( found ).isInstanceOf( Object[].class ); + assertEquals( "Prova", ( (Object[]) found )[0] ); + assertEquals( rye.getId(), ( (Object[]) found )[1] ); + } ) ); } @@ -114,7 +114,7 @@ public void testSelectWithMultipleScalarValues(VertxTestContext context) { public void testSingleResultQueryOnId(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "FROM Flour WHERE id = 1", Flour.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( flour -> assertEquals( spelt, flour ) ) ); @@ -124,7 +124,7 @@ public void testSingleResultQueryOnId(VertxTestContext context) { public void testSingleResultQueryOnName(VertxTestContext context) { test( context, getSessionFactory().withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "FROM Flour WHERE name = 'Almond'", Flour.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getSingleResult(); } ).thenAccept( flour -> assertEquals( almond, flour ) ) ); @@ -135,7 +135,7 @@ public void testFromQuery(VertxTestContext context) { test( context, getSessionFactory() .withSession( s -> { Stage.SelectionQuery qr = s.createSelectionQuery( "FROM Flour ORDER BY name", Flour.class ); - assertNotNull( qr ); + assertThat( qr ).isNotNull(); return qr.getResultList(); } ) .thenAccept( results -> assertThat( results ).containsExactly( almond, rye, spelt ) ) From 30970819e7dff5ed0688d16eab8a30da47533c03 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 27 May 2025 12:26:30 +0200 Subject: [PATCH 003/185] [#2230] ClassCastException when with @EmebedddedId and @OneToOne relationship --- ...ReactiveEmbeddedIdentifierMappingImpl.java | 11 ++ .../internal/ReactiveEmbeddableAssembler.java | 42 ++++++ .../internal/ReactiveEmbeddableFetchImpl.java | 22 +++ .../ReactiveEmbeddableInitializerImpl.java | 135 +++++++++++++++++- .../ReactiveEntityInitializerImpl.java | 107 +++++++++----- 5 files changed, 279 insertions(+), 38 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java index 3f5244fa9..204a23d3a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveEmbeddedIdentifierMappingImpl.java @@ -13,6 +13,7 @@ import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableFetchImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.SqlSelection; @@ -20,6 +21,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; public class ReactiveEmbeddedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping { @@ -104,4 +106,13 @@ public String getSqlAliasStem() { public String getFetchableName() { return delegate.getFetchableName(); } + + @Override + public Fetchable getFetchable(int position) { + Fetchable fetchable = delegate.getFetchable( position ); + if ( fetchable instanceof ToOneAttributeMapping ) { + return new ReactiveToOneAttributeMapping( (ToOneAttributeMapping) fetchable ); + } + return fetchable; + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java new file mode 100644 index 000000000..db8222838 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableAssembler.java @@ -0,0 +1,42 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableAssembler; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; + +import java.util.concurrent.CompletionStage; + +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; + +/** + * @see org.hibernate.sql.results.graph.embeddable.internal.EmbeddableAssembler + */ +public class ReactiveEmbeddableAssembler extends EmbeddableAssembler implements ReactiveDomainResultsAssembler { + + public ReactiveEmbeddableAssembler(EmbeddableInitializer initializer) { + super( initializer ); + } + + @Override + public CompletionStage reactiveAssemble(ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { + final ReactiveInitializer reactiveInitializer = (ReactiveInitializer) getInitializer(); + final InitializerData data = reactiveInitializer.getData( rowProcessingState ); + final Initializer.State state = data.getState(); + if ( state == Initializer.State.KEY_RESOLVED ) { + return reactiveInitializer + .reactiveResolveInstance( data ) + .thenApply( v -> reactiveInitializer.getResolvedInstance( data ) ); + } + return completedFuture( reactiveInitializer.getResolvedInstance( data ) ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java index 50034533b..b83b49817 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java @@ -6,14 +6,20 @@ package org.hibernate.reactive.sql.results.graph.embeddable.internal; import org.hibernate.engine.FetchTiming; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchSelectImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; public class ReactiveEmbeddableFetchImpl extends EmbeddableFetchImpl { @@ -37,4 +43,20 @@ public EmbeddableInitializer createInitializer( AssemblerCreationState creationState) { return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), parent, creationState, true ); } + + @Override + public DomainResultAssembler createAssembler(InitializerParent parent, AssemblerCreationState creationState) { + Initializer initializer = creationState.resolveInitializer( this, parent, this ); + EmbeddableInitializer embeddableInitializer = initializer.asEmbeddableInitializer(); + return new ReactiveEmbeddableAssembler( embeddableInitializer ); + } + + @Override + public Fetch findFetch(Fetchable fetchable) { + Fetch fetch = super.findFetch( fetchable ); + if ( fetch instanceof EntityFetchSelectImpl entityFetchSelect ) { + return new ReactiveEntityFetchSelectImpl( entityFetchSelect ); + } + return fetch; + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java index a1d4a890e..a9feb3fee 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -5,12 +5,18 @@ */ package org.hibernate.reactive.sql.results.graph.embeddable.internal; + import java.util.concurrent.CompletionStage; import java.util.function.BiFunction; import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.VirtualModelPart; +import org.hibernate.metamodel.spi.EmbeddableInstantiator; +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerData; import org.hibernate.sql.results.graph.InitializerParent; @@ -19,8 +25,13 @@ import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; +import static org.hibernate.sql.results.graph.embeddable.EmbeddableLoadingLogger.EMBEDDED_LOAD_LOGGER; +import static org.hibernate.sql.results.graph.entity.internal.BatchEntityInsideEmbeddableSelectFetchInitializer.BATCH_PROPERTY; public class ReactiveEmbeddableInitializerImpl extends EmbeddableInitializerImpl implements ReactiveInitializer { @@ -33,6 +44,10 @@ public ReactiveEmbeddableInitializerData( super( initializer, rowProcessingState ); } + public Object[] getRowState(){ + return rowState; + } + @Override public void setState(State state) { super.setState( state ); @@ -64,10 +79,128 @@ protected InitializerData createInitializerData(RowProcessingState rowProcessing @Override public CompletionStage reactiveResolveInstance(EmbeddableInitializerData data) { - super.resolveInstance( data ); + if ( data.getState() != State.KEY_RESOLVED ) { + return voidFuture(); + } + + data.setState( State.RESOLVED ); + return extractRowState( (ReactiveEmbeddableInitializerData) data ) + .thenAccept( unused -> prepareCompositeInstance( (ReactiveEmbeddableInitializerData) data ) ); + } + + private CompletionStage extractRowState(ReactiveEmbeddableInitializerData data) { + final DomainResultAssembler[] subAssemblers = assemblers[data.getSubclassId()]; + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object[] rowState = data.getRowState(); + final boolean[] stateAllNull = {true}; + final int[] index = {0}; + final boolean[] forceExit = { false }; + return whileLoop( + () -> index[0] < subAssemblers.length && !forceExit[0], + () -> { + final int i = index[0]++; + final DomainResultAssembler assembler = subAssemblers[i]; + if ( assembler instanceof ReactiveDomainResultsAssembler reactiveAssembler ) { + return reactiveAssembler.reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + .thenAccept( contributorValue -> setContributorValue( + contributorValue, + i, + rowState, + stateAllNull, + forceExit + ) ); + } + else { + setContributorValue( + assembler == null ? null : assembler.assemble( rowProcessingState ), + i, + rowState, + stateAllNull, + forceExit + ); + return voidFuture(); + } + }) + .whenComplete( + (unused, throwable) -> { + if ( stateAllNull[0] ) { + data.setState( State.MISSING ); + } + } + ); + } + + private void setContributorValue( + Object contributorValue, + int index, + Object[] rowState, + boolean[] stateAllNull, + boolean[] forceExit) { + if ( contributorValue == BATCH_PROPERTY ) { + rowState[index] = null; + } + else { + rowState[index] = contributorValue; + } + if ( contributorValue != null ) { + stateAllNull[0] = false; + } + else if ( isPartOfKey() ) { + // If this is a foreign key and there is a null part, the whole thing has to be turned into null + stateAllNull[0] = true; + forceExit[0] = true; + } + } + + private CompletionStage prepareCompositeInstance(ReactiveEmbeddableInitializerData data) { + // Virtual model parts use the owning entity as container which the fetch parent access provides. + // For an identifier or foreign key this is called during the resolveKey phase of the fetch parent, + // so we can't use the fetch parent access in that case. + final ReactiveInitializer parent = (ReactiveInitializer) getParent(); + if ( parent != null && getInitializedPart() instanceof VirtualModelPart && !isPartOfKey() && data.getState() != State.MISSING ) { + final ReactiveEmbeddableInitializerData subData = parent.getData( data.getRowProcessingState() ); + return parent + .reactiveResolveInstance( subData ) + .thenCompose( + unused -> { + data.setInstance( parent.getResolvedInstance( subData ) ); + if ( data.getState() == State.INITIALIZED ) { + return voidFuture(); + } + return doCreateCompositeInstance( data ) + .thenAccept( v -> EMBEDDED_LOAD_LOGGER.debugf( + "Created composite instance [%s]", + getNavigablePath() + ) ); + } ); + } + + return doCreateCompositeInstance( data ) + .thenAccept( v -> EMBEDDED_LOAD_LOGGER.debugf( "Created composite instance [%s]", getNavigablePath() ) ); + + } + + private CompletionStage doCreateCompositeInstance(ReactiveEmbeddableInitializerData data) { + if ( data.getInstance() == null ) { + return createCompositeInstance( data ) + .thenAccept( data::setInstance ); + } return voidFuture(); } + private CompletionStage createCompositeInstance(ReactiveEmbeddableInitializerData data) { + if ( data.getState() == State.MISSING ) { + return nullFuture(); + } + + final EmbeddableInstantiator instantiator = data.getConcreteEmbeddableType() == null + ? getInitializedPart().getEmbeddableTypeDescriptor().getRepresentationStrategy().getInstantiator() + : data.getConcreteEmbeddableType().getInstantiator(); + final Object instance = instantiator.instantiate( data ); + data.setState( State.RESOLVED ); + return completedFuture( instance ); + } + @Override public CompletionStage reactiveInitializeInstance(EmbeddableInitializerData data) { super.initializeInstance( data ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java index a7342275d..6a690f34e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -26,6 +26,7 @@ import org.hibernate.proxy.map.MapProxy; import org.hibernate.reactive.session.ReactiveQueryProducer; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; @@ -253,47 +254,79 @@ public CompletionStage reactiveResolveInstance(EntityInitializerData origi } final RowProcessingState rowProcessingState = data.getRowProcessingState(); data.setState( State.RESOLVED ); + return assembleId( data, rowProcessingState ) + .thenCompose( unused -> { + if ( data.getState() == State.MISSING ) { + return voidFuture(); + } + else { + final PersistenceContext persistenceContext = rowProcessingState + .getSession().getPersistenceContextInternal(); + data.setEntityHolder( persistenceContext.claimEntityHolderIfPossible( + data.getEntityKey(), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this + ) ); + + if ( useEmbeddedIdentifierInstanceAsEntity( data ) ) { + data.setEntityInstanceForNotify( rowProcessingState.getEntityId() ); + data.setInstance( data.getEntityInstanceForNotify() ); + } + else { + return reactiveResolveEntityInstance1( data ) + .thenAccept( v -> { + if ( data.getUniqueKeyAttributePath() != null ) { + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); + final EntityUniqueKey euk = new EntityUniqueKey( + concreteDescriptor.getEntityName(), + data.getUniqueKeyAttributePath(), + rowProcessingState.getEntityUniqueKey(), + data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], + session.getFactory() + ); + session.getPersistenceContextInternal().addEntity( + euk, + data.getInstance() + ); + } + postResolveInstance( data ); + } ); + } + postResolveInstance( data ); + return voidFuture(); + } + } ); + } + + private CompletionStage assembleId( + ReactiveEntityInitializerData data, + RowProcessingState rowProcessingState) { if ( data.getEntityKey() == null ) { - assert getIdentifierAssembler() != null; - final Object id = getIdentifierAssembler().assemble( rowProcessingState ); - if ( id == null ) { - setMissing( data ); + DomainResultAssembler identifierAssembler = getIdentifierAssembler(); + assert identifierAssembler != null; + if ( identifierAssembler instanceof ReactiveDomainResultsAssembler reactiveAssembler ) { + return reactiveAssembler + .reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + .thenAccept( id -> { + if ( id == null ) { + setMissing( data ); + return ; + } + resolveEntityKey( data, id ); + } ); + } + else { + final Object id = identifierAssembler.assemble( rowProcessingState ); + if ( id == null ) { + setMissing( data ); + return voidFuture(); + } + resolveEntityKey( data, id ); return voidFuture(); } - resolveEntityKey( data, id ); - } - final PersistenceContext persistenceContext = rowProcessingState.getSession() - .getPersistenceContextInternal(); - data.setEntityHolder( persistenceContext.claimEntityHolderIfPossible( - data.getEntityKey(), - null, - rowProcessingState.getJdbcValuesSourceProcessingState(), - this - ) ); - - if ( useEmbeddedIdentifierInstanceAsEntity( data ) ) { - data.setEntityInstanceForNotify( rowProcessingState.getEntityId() ); - data.setInstance( data.getEntityInstanceForNotify() ); - } - else { - return reactiveResolveEntityInstance1( data ) - .thenAccept( v -> { - if ( data.getUniqueKeyAttributePath() != null ) { - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); - final EntityUniqueKey euk = new EntityUniqueKey( - concreteDescriptor.getEntityName(), - data.getUniqueKeyAttributePath(), - rowProcessingState.getEntityUniqueKey(), - data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], - session.getFactory() - ); - session.getPersistenceContextInternal().addEntity( euk, data.getInstance() ); - } - postResolveInstance( data ); - } ); } - postResolveInstance( data ); return voidFuture(); } From d606324f6cca54bd1045ccbe322951e945b08f01 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 29 Apr 2025 15:57:45 +0200 Subject: [PATCH 004/185] [#2230] Test embedded id with one-to-one --- .../reactive/EmbeddedIdWithOneToOneTest.java | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java new file mode 100644 index 000000000..f5cbf5fd9 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdWithOneToOneTest.java @@ -0,0 +1,131 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EmbeddedIdWithOneToOneTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( FooEntity.class, BarEntity.class ); + } + + @Test + public void test(VertxTestContext context) { + BarEntity barEntity = new BarEntity( "1" ); + FooId fooId = new FooId( barEntity ); + FooEntity entity = new FooEntity( fooId ); + + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.persistAll( barEntity, entity ) ) + .chain( () -> getMutinySessionFactory() + .withTransaction( s -> s.find( FooEntity.class, fooId ) ) + ) + .invoke( result -> { + assertThat( result.getId() ).isEqualTo( fooId ); + assertThat( result.getId().getIdEntity() ).isEqualTo( fooId.getIdEntity() ); + assertThat( result.getId().getIdEntity().getId() ).isEqualTo( fooId.getIdEntity().getId() ); + } ) + ); + } + + @Entity(name = "bar") + public static class BarEntity { + + @Id + private String id; + + public BarEntity() { + } + + public BarEntity(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + @Entity(name = "foo") + public static class FooEntity { + + @EmbeddedId + private FooId id; + + public FooEntity() { + } + + public FooEntity(FooId id) { + this.id = id; + } + + public FooId getId() { + return id; + } + + public void setId(FooId id) { + this.id = id; + } + } + + @Embeddable + public static class FooId { + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "id", nullable = false) + private BarEntity barEntity; + + public FooId() { + } + + public FooId(BarEntity barEntity) { + this.barEntity = barEntity; + } + + public BarEntity getIdEntity() { + return barEntity; + } + + public void setIdEntity(BarEntity barEntity) { + this.barEntity = barEntity; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + FooId fooId = (FooId) o; + return Objects.equals( barEntity, fooId.barEntity ); + } + + @Override + public int hashCode() { + return Objects.hashCode( barEntity ); + } + } +} From 5e645e11963fc9e3bd9af81ae9bb1b5ec0cc8e7b Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 4 Jun 2025 10:53:49 +0200 Subject: [PATCH 005/185] [#2230] Small refactoring Make the structure of the code more similar to the one in Hibernate ORM (See EntityInitializerImpl) --- .../ReactiveEntityInitializerImpl.java | 121 ++++++++---------- 1 file changed, 51 insertions(+), 70 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java index 6a690f34e..c6ee36363 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -254,82 +254,63 @@ public CompletionStage reactiveResolveInstance(EntityInitializerData origi } final RowProcessingState rowProcessingState = data.getRowProcessingState(); data.setState( State.RESOLVED ); - return assembleId( data, rowProcessingState ) - .thenCompose( unused -> { - if ( data.getState() == State.MISSING ) { - return voidFuture(); - } - else { - final PersistenceContext persistenceContext = rowProcessingState - .getSession().getPersistenceContextInternal(); - data.setEntityHolder( persistenceContext.claimEntityHolderIfPossible( - data.getEntityKey(), - null, - rowProcessingState.getJdbcValuesSourceProcessingState(), - this - ) ); - - if ( useEmbeddedIdentifierInstanceAsEntity( data ) ) { - data.setEntityInstanceForNotify( rowProcessingState.getEntityId() ); - data.setInstance( data.getEntityInstanceForNotify() ); - } - else { - return reactiveResolveEntityInstance1( data ) - .thenAccept( v -> { - if ( data.getUniqueKeyAttributePath() != null ) { - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); - final EntityUniqueKey euk = new EntityUniqueKey( - concreteDescriptor.getEntityName(), - data.getUniqueKeyAttributePath(), - rowProcessingState.getEntityUniqueKey(), - data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], - session.getFactory() - ); - session.getPersistenceContextInternal().addEntity( - euk, - data.getInstance() - ); - } - postResolveInstance( data ); - } ); + if ( data.getEntityKey() == null ) { + return assembleId( rowProcessingState ) + .thenCompose( id -> { + if ( id == null ) { + setMissing( data ); + return voidFuture(); } - postResolveInstance( data ); - return voidFuture(); - } - } ); + resolveEntityKey( data, id ); + return postAssembleId( rowProcessingState, data ); + } ); + } + return postAssembleId( rowProcessingState, data ); } - private CompletionStage assembleId( - ReactiveEntityInitializerData data, - RowProcessingState rowProcessingState) { - if ( data.getEntityKey() == null ) { - DomainResultAssembler identifierAssembler = getIdentifierAssembler(); - assert identifierAssembler != null; - if ( identifierAssembler instanceof ReactiveDomainResultsAssembler reactiveAssembler ) { - return reactiveAssembler - .reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) - .thenAccept( id -> { - if ( id == null ) { - setMissing( data ); - return ; - } - resolveEntityKey( data, id ); - } ); - } - else { - final Object id = identifierAssembler.assemble( rowProcessingState ); - if ( id == null ) { - setMissing( data ); - return voidFuture(); - } - resolveEntityKey( data, id ); - return voidFuture(); - } + private CompletionStage postAssembleId(RowProcessingState rowProcessingState, ReactiveEntityInitializerData data) { + final PersistenceContext persistenceContext = rowProcessingState.getSession().getPersistenceContextInternal(); + data.setEntityHolder( persistenceContext.claimEntityHolderIfPossible( + data.getEntityKey(), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this + ) ); + + if ( useEmbeddedIdentifierInstanceAsEntity( data ) ) { + data.setEntityInstanceForNotify( rowProcessingState.getEntityId() ); + data.setInstance( data.getEntityInstanceForNotify() ); + postResolveInstance( data ); + return voidFuture(); } - return voidFuture(); + + return reactiveResolveEntityInstance1( data ) + .thenAccept( v -> { + if ( data.getUniqueKeyAttributePath() != null ) { + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); + final EntityUniqueKey euk = new EntityUniqueKey( + concreteDescriptor.getEntityName(), + data.getUniqueKeyAttributePath(), + rowProcessingState.getEntityUniqueKey(), + data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], + session.getFactory() + ); + session.getPersistenceContextInternal().addEntity( euk, data.getInstance() ); + } + postResolveInstance( data ); + } ); + } + + private CompletionStage assembleId(RowProcessingState rowProcessingState) { + final DomainResultAssembler identifierAssembler = getIdentifierAssembler(); + assert identifierAssembler != null; + return identifierAssembler instanceof ReactiveDomainResultsAssembler reactiveAssembler + ? reactiveAssembler.reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + : completedFuture( identifierAssembler.assemble( rowProcessingState ) ); } + // We could move this method in ORM private void postResolveInstance(ReactiveEntityInitializerData data) { if ( data.getInstance() != null ) { upgradeLockMode( data ); From 341dee3aedc30ff9cbdbc460e9e88c8554782512 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 5 Jun 2025 10:50:45 +0200 Subject: [PATCH 006/185] [#2284] ClassCastException with MapsId, EmbeddedId, and many-to-one --- .../internal/ReactiveEmbeddableFetchImpl.java | 3 +++ .../ReactiveEmbeddableInitializerImpl.java | 19 ++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java index b83b49817..46a15c1e9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java @@ -57,6 +57,9 @@ public Fetch findFetch(Fetchable fetchable) { if ( fetch instanceof EntityFetchSelectImpl entityFetchSelect ) { return new ReactiveEntityFetchSelectImpl( entityFetchSelect ); } + else if ( fetch instanceof EmbeddableFetchImpl embeddableFetch ) { + return new ReactiveEmbeddableFetchImpl( embeddableFetch ); + } return fetch; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java index a9feb3fee..3df56721f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -214,16 +214,21 @@ public CompletionStage forEachReactiveSubInitializer( final ReactiveEmbeddableInitializerData embeddableInitializerData = (ReactiveEmbeddableInitializerData) data; final RowProcessingState rowProcessingState = embeddableInitializerData.getRowProcessingState(); if ( embeddableInitializerData.getConcreteEmbeddableType() == null ) { - return loop( subInitializers, subInitializer -> loop( subInitializer, initializer -> consumer - .apply( (ReactiveInitializer) initializer, rowProcessingState ) - ) ); + return loop( subInitializers, subInitializer -> + loop( subInitializer, initializer -> + initializer != null + ? consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ) + : voidFuture() + ) + ); } else { Initializer[] initializers = subInitializers[embeddableInitializerData.getSubclassId()]; - return loop( 0, initializers.length, i -> { - ReactiveInitializer reactiveInitializer = (ReactiveInitializer) initializers[i]; - return consumer.apply( reactiveInitializer, rowProcessingState ); - } ); + return loop(0, initializers.length, i -> + initializers[i] != null + ? consumer.apply( (ReactiveInitializer) initializers[i], rowProcessingState ) + : voidFuture() + ); } } From dc727132c932d6421e2e7e7237c644a74e69cd82 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 30 May 2025 11:49:25 +0200 Subject: [PATCH 007/185] [#2284] Test Many-to-one with MapsId and EmbeddedId --- .../ManyToOneMapsIdAndEmbeddedIdTest.java | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java new file mode 100644 index 000000000..314c69b48 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToOneMapsIdAndEmbeddedIdTest.java @@ -0,0 +1,189 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.hibernate.annotations.EmbeddedColumnNaming; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; +import jakarta.persistence.OneToMany; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ManyToOneMapsIdAndEmbeddedIdTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( Link.class, GPSPoint.class, NetPoint.class ); + } + + @BeforeEach + public void populateDb(VertxTestContext context) { + NetPointKey startKey = new NetPointKey( 1, NetPointType.STOP_POINT ); + NetPointKey endKey = new NetPointKey( 2, NetPointType.STOP_POINT ); + LinkKey linkKey = new LinkKey( startKey, endKey, "123" ); + + final NetPoint start = new NetPoint(); + fillWithBogusValues( start ); + start.key = startKey; + + final NetPoint end = new NetPoint(); + fillWithBogusValues( end ); + end.key = endKey; + + final Link link = new Link(); + link.key = linkKey; + link.addPoint( new GPSPoint( new GPSPointKey( linkKey, 0 ), link, 1, 1, 1 ) ); + link.addPoint( new GPSPoint( new GPSPointKey( linkKey, 1 ), link, 1, 1, 1 ) ); + link.addPoint( new GPSPoint( new GPSPointKey( linkKey, 2 ), link, 1, 1, 1 ) ); + + test( + context, getMutinySessionFactory().withTransaction( session -> session + .persistAll( start, end, link ) ) + ); + } + + @Test + public void test(VertxTestContext context) { + test( context, getMutinySessionFactory() + .withTransaction( session -> session + .createQuery( "from Link", Link.class ).getResultList() ) + .invoke( links -> assertThat( links ).hasSize( 1 ) ) + ); + } + + void fillWithBogusValues(NetPoint start) { + start.gpsLatitude = 1; + start.gpsLongitude = 1; + start.operatingDepartmentId = "123"; + start.operatingDepartmentShortName = "123 - 123"; + } + + @Entity(name = "GPSPoint") + public static class GPSPoint { + + @EmbeddedId + public GPSPointKey key; + + @ManyToOne + @MapsId("link") + public Link link; + + @Column(nullable = false) + public Integer latitude; + + @Column(nullable = false) + public Integer longitude; + + @Column(nullable = false) + public Integer distance; + + public GPSPoint() { + } + + public GPSPoint(GPSPointKey key, Link link, Integer latitude, Integer longitude, Integer distance) { + this.key = key; + this.link = link; + this.latitude = latitude; + this.longitude = longitude; + this.distance = distance; + } + } + + @Embeddable + public record GPSPointKey( + @Embedded + LinkKey link, + + Integer position + ) { + + } + + @Entity(name = "Link") + public static class Link { + + @EmbeddedId + public LinkKey key; + + @OneToMany(mappedBy = "link", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) + public List gpsPoints = new ArrayList<>(); + + public void addPoint(GPSPoint point) { + gpsPoints.add( point ); + point.link = this; + } + } + + @Embeddable + public record LinkKey( + @Embedded + @EmbeddedColumnNaming("start_%s") + NetPointKey start, + + @Embedded + @EmbeddedColumnNaming("end_%s") + NetPointKey end, + + String operatingDepartmentId + ) { + + } + + @Embeddable + public record NetPointKey( + Integer id, + + @Enumerated(EnumType.ORDINAL) + NetPointType type + ) { + + } + + @Entity(name = "NetPoint") + public static class NetPoint { + + @EmbeddedId + public NetPointKey key; + + @Column(nullable = false) + public String operatingDepartmentId; + + @Column(nullable = false) + public String operatingDepartmentShortName; + + @Column(nullable = false) + public Integer gpsLatitude; + + @Column(nullable = false) + public Integer gpsLongitude; + } + + public enum NetPointType { + @Deprecated UNSPECIFIED, + STOP_POINT, + DEPOT_POINT, + BEACON, + SECTION_POINT + } +} From 97fd1e15a13af779a8400c4e6be10f1cef489713 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 6 Jun 2025 11:55:47 +0200 Subject: [PATCH 008/185] [#2186] Improve error message when asking for a JDBC connection --- .../main/java/org/hibernate/reactive/logging/impl/Log.java | 4 ++++ .../provider/service/NoJdbcConnectionProvider.java | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java index 4f205492e..fa29cd8b5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java @@ -7,6 +7,7 @@ +import java.sql.SQLException; import java.sql.SQLWarning; import jakarta.persistence.PersistenceException; @@ -266,6 +267,9 @@ public interface Log extends BasicLogger { @Message(id = 83, value = "Unexpected request of a non reactive connection") HibernateException unexpectedConnectionRequest(); + @Message(id = 84, value = "The application requested a JDBC connection, but Hibernate Reactive doesn't use JDBC. This could be caused by a bug or the use of an unsupported feature in Hibernate Reactive") + SQLException notUsingJdbc(); + // Same method that exists in CoreMessageLogger @LogMessage(level = WARN) @Message(id = 104, value = "firstResult/maxResults specified with collection fetch; applying in memory!" ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java index 5c3380f65..a02c3e152 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProvider.java @@ -6,10 +6,14 @@ package org.hibernate.reactive.provider.service; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.reactive.logging.impl.Log; import java.sql.Connection; import java.sql.SQLException; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + /** * A dummy Hibernate {@link ConnectionProvider} throws an * exception if a JDBC connection is requested. @@ -17,12 +21,13 @@ * @author Gavin King */ public class NoJdbcConnectionProvider implements ConnectionProvider { + private static final Log LOG = make( Log.class, lookup() ); public static final NoJdbcConnectionProvider INSTANCE = new NoJdbcConnectionProvider(); @Override public Connection getConnection() throws SQLException { - throw new SQLException( "Not using JDBC" ); + throw LOG.notUsingJdbc(); } @Override From 74dcec6e3043587ffed036250e7b79e061c54c1f Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 10 Jun 2025 19:26:09 +0200 Subject: [PATCH 009/185] [#2297] Upgrade Hibernate ORM to 7.0.1.Final --- README.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6047808fb..fb0641d65 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Hibernate Reactive has been tested with: - CockroachDB v24 - MS SQL Server 2022 - Oracle 23 -- [Hibernate ORM][] 7.0.0.Final +- [Hibernate ORM][] 7.0.1.Final - [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.15 - [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.15 - [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.15 diff --git a/gradle.properties b/gradle.properties index 5cd937337..46cd77155 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,7 +35,7 @@ org.gradle.java.installations.auto-download=false #enableMavenLocalRepo = true # The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) -hibernateOrmVersion = 7.0.0.Final +hibernateOrmVersion = 7.0.1.Final # Override default Hibernate ORM Gradle plugin version # Using the stable version because I don't know how to configure the build to download the snapshot version from From 3f95659eb863060630f8ba99fd340635bb950767 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 13 Jun 2025 12:00:51 +0200 Subject: [PATCH 010/185] [#2300] Upgrade Hibernate ORM to 7.0.2.Final --- README.md | 2 +- gradle.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fb0641d65..cf8a9f62b 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Hibernate Reactive has been tested with: - CockroachDB v24 - MS SQL Server 2022 - Oracle 23 -- [Hibernate ORM][] 7.0.1.Final +- [Hibernate ORM][] 7.0.2.Final - [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.15 - [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.15 - [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.15 diff --git a/gradle.properties b/gradle.properties index 46cd77155..53ece31a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,12 +35,12 @@ org.gradle.java.installations.auto-download=false #enableMavenLocalRepo = true # The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) -hibernateOrmVersion = 7.0.1.Final +hibernateOrmVersion = 7.0.2.Final # Override default Hibernate ORM Gradle plugin version # Using the stable version because I don't know how to configure the build to download the snapshot version from # a remote repository -#hibernateOrmGradlePluginVersion = 7.0.0.Final +#hibernateOrmGradlePluginVersion = 7.0.2.Final # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) # this is required when using intervals or weird versions or the build will fail From 90b507a36a91554b8b8b2ddd6c9164cb9d9a816c Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Sat, 7 Jun 2025 16:08:10 +0200 Subject: [PATCH 011/185] [#2154] Composite key with generated value not working properly --- .../id/ReactiveIdentifierGenerator.java | 5 + ...ompositeNestedGeneratedValueGenerator.java | 130 ++++++++++++++++++ .../tuple/entity/ReactiveEntityMetamodel.java | 105 ++++++++++++-- 3 files changed, 225 insertions(+), 15 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java index 7121ea5c4..83ae3290a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java @@ -6,6 +6,7 @@ package org.hibernate.reactive.id; import org.hibernate.Incubating; +import org.hibernate.generator.EventType; import org.hibernate.generator.Generator; import org.hibernate.id.IdentifierGenerator; import org.hibernate.reactive.session.ReactiveConnectionSupplier; @@ -33,4 +34,8 @@ public interface ReactiveIdentifierGenerator extends Generator { * @param session the reactive session */ CompletionStage generate(ReactiveConnectionSupplier session, Object entity); + + default CompletionStage generate(ReactiveConnectionSupplier session, Object owner, Object currentValue, EventType eventType) { + return generate( session, owner ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java new file mode 100644 index 000000000..cb6b03ab5 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java @@ -0,0 +1,130 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.id.impl; + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.Generator; +import org.hibernate.generator.GeneratorCreationContext; +import org.hibernate.id.CompositeNestedGeneratedValueGenerator; +import org.hibernate.id.IdentifierGenerationException; +import org.hibernate.mapping.Component; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.reactive.id.ReactiveIdentifierGenerator; +import org.hibernate.reactive.session.ReactiveConnectionSupplier; +import org.hibernate.reactive.tuple.entity.ReactiveEntityMetamodel; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; + +public class ReactiveCompositeNestedGeneratedValueGenerator extends CompositeNestedGeneratedValueGenerator implements + ReactiveIdentifierGenerator { + + public ReactiveCompositeNestedGeneratedValueGenerator( + CompositeNestedGeneratedValueGenerator generator, + GeneratorCreationContext creationContext, + RuntimeModelCreationContext runtimeModelCreationContext) { + super( + generator.getGenerationContextLocator(), + generator.getCompositeType(), + reactivePlans( generator, creationContext, runtimeModelCreationContext ) + ); + } + + private static List reactivePlans( + CompositeNestedGeneratedValueGenerator generator, + GeneratorCreationContext creationContext, + RuntimeModelCreationContext runtimeModelCreationContext) { + final List plans = new ArrayList<>(); + for ( GenerationPlan plan : generator.getGenerationPlans() ) { + final GenerationPlan reactivePlane = new Component.ValueGenerationPlan( + (BeforeExecutionGenerator) ReactiveEntityMetamodel.augmentWithReactiveGenerator( + plan.getGenerator(), + creationContext, + runtimeModelCreationContext + ), + plan.getInjector(), + plan.getPropertyIndex() + ); + plans.add( reactivePlane ); + } + return plans; + } + + @Override + public CompletionStage generate(ReactiveConnectionSupplier reactiveConnectionSupplier, Object object) { + SharedSessionContractImplementor session = (SharedSessionContractImplementor) reactiveConnectionSupplier; + final Object context = getGenerationContextLocator().locateGenerationContext( session, object ); + + final List generatedValues = getCompositeType().isMutable() + ? null + : new ArrayList<>( getGenerationPlans().size() ); + return loop( getGenerationPlans(), generationPlan -> generateIdentifier( + reactiveConnectionSupplier, + object, + generationPlan, + session, + generatedValues, + context + ) ) + .thenCompose( v -> handleGeneratedValues( generatedValues, context, session ) ); + } + + private CompletionStage generateIdentifier( + ReactiveConnectionSupplier reactiveConnectionSupplier, + Object object, + GenerationPlan generationPlan, + SharedSessionContractImplementor session, + List generatedValues, + Object context) { + final Generator generator = generationPlan.getGenerator(); + if ( generator.generatedBeforeExecution( object, session ) ) { + if ( generator instanceof ReactiveIdentifierGenerator reactiveIdentifierGenerator ) { + return reactiveIdentifierGenerator + .generate( reactiveConnectionSupplier, object ) + .thenAccept( generated -> { + if ( generatedValues != null ) { + generatedValues.add( generated ); + } + else { + generationPlan.getInjector().set( context, generated ); + } + } ); + } + else { + final Object currentValue = generator.allowAssignedIdentifiers() + ? getCompositeType().getPropertyValue( context, generationPlan.getPropertyIndex(), session ) + : null; + return completedFuture( ( (BeforeExecutionGenerator) generator ) + .generate( session, object, currentValue, INSERT ) ); + } + } + else { + throw new IdentifierGenerationException( "Identity generation isn't supported for composite ids" ); + } + } + + private CompletionStage handleGeneratedValues( + List generatedValues, + Object context, + SharedSessionContractImplementor session) { + if ( generatedValues != null ) { + final Object[] values = getCompositeType().getPropertyValues( context ); + for ( int i = 0; i < generatedValues.size(); i++ ) { + values[getGenerationPlans().get( i ).getPropertyIndex()] = generatedValues.get( i ); + } + return completedFuture( getCompositeType().replacePropertyValues( context, values, session ) ); + } + else { + return completedFuture( context ); + } + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java index 2e20e7781..14e227358 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java @@ -8,8 +8,11 @@ import java.util.function.Function; +import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.generator.Generator; import org.hibernate.generator.GeneratorCreationContext; +import org.hibernate.id.CompositeNestedGeneratedValueGenerator; import org.hibernate.id.Configurable; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.SelectGenerator; @@ -18,18 +21,23 @@ import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.id.enhanced.TableGenerator; import org.hibernate.id.enhanced.TableStructure; -import org.hibernate.mapping.GeneratorCreator; +import org.hibernate.mapping.GeneratorSettings; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SimpleValue; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.id.impl.EmulatedSequenceReactiveIdentifierGenerator; +import org.hibernate.reactive.id.impl.ReactiveCompositeNestedGeneratedValueGenerator; import org.hibernate.reactive.id.impl.ReactiveGeneratorWrapper; import org.hibernate.reactive.id.impl.ReactiveSequenceIdentifierGenerator; import org.hibernate.reactive.id.impl.TableReactiveIdentifierGenerator; import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.service.ServiceRegistry; import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.type.Type; import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; @@ -67,20 +75,22 @@ private static Generator buildIdGenerator( return existing; } else { - SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier(); - GeneratorCreator customIdGeneratorCreator = identifier.getCustomIdGeneratorCreator(); - identifier.setCustomIdGeneratorCreator( context -> { - Generator generator = customIdGeneratorCreator.createGenerator( context ); - return augmentWithReactiveGenerator( generator, context, creationContext ); - } ); - final Generator idgenerator = identifier - // returns the cached Generator if it was already created - .createGenerator( + final SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier(); + final Generator idgenerator = augmentWithReactiveGenerator( + identifier.createGenerator( creationContext.getDialect(), persistentClass.getRootClass(), persistentClass.getIdentifierProperty(), creationContext.getGeneratorSettings() - ); + ), + new IdGeneratorCreationContext( + persistentClass.getRootClass(), + persistentClass.getIdentifierProperty(), + creationContext.getGeneratorSettings(), + identifier, + creationContext + ), + creationContext ); creationContext.getGenerators().put( rootName, idgenerator ); return idgenerator; } @@ -90,8 +100,8 @@ public static Generator augmentWithReactiveGenerator( Generator generator, GeneratorCreationContext creationContext, RuntimeModelCreationContext runtimeModelCreationContext) { - if ( generator instanceof SequenceStyleGenerator ) { - final DatabaseStructure structure = ( (SequenceStyleGenerator) generator ).getDatabaseStructure(); + if ( generator instanceof SequenceStyleGenerator sequenceStyleGenerator) { + final DatabaseStructure structure = sequenceStyleGenerator.getDatabaseStructure(); if ( structure instanceof TableStructure ) { return initialize( (IdentifierGenerator) generator, new EmulatedSequenceReactiveIdentifierGenerator( (TableStructure) structure, runtimeModelCreationContext ), creationContext ); } @@ -100,16 +110,28 @@ public static Generator augmentWithReactiveGenerator( } throw LOG.unknownStructureType(); } - if ( generator instanceof TableGenerator ) { + if ( generator instanceof TableGenerator tableGenerator ) { return initialize( (IdentifierGenerator) generator, - new TableReactiveIdentifierGenerator( (TableGenerator) generator, runtimeModelCreationContext ), + new TableReactiveIdentifierGenerator( tableGenerator, runtimeModelCreationContext ), creationContext ); } if ( generator instanceof SelectGenerator ) { throw LOG.selectGeneratorIsNotSupportedInHibernateReactive(); } + if ( generator instanceof CompositeNestedGeneratedValueGenerator compositeNestedGeneratedValueGenerator ) { + final ReactiveCompositeNestedGeneratedValueGenerator reactiveCompositeNestedGeneratedValueGenerator = new ReactiveCompositeNestedGeneratedValueGenerator( + compositeNestedGeneratedValueGenerator, + creationContext, + runtimeModelCreationContext + ); + return initialize( + (IdentifierGenerator) generator, + reactiveCompositeNestedGeneratedValueGenerator, + creationContext + ); + } //nothing to do return generator; } @@ -121,4 +143,57 @@ private static Generator initialize( ( (Configurable) reactiveIdGenerator ).initialize( creationContext.getSqlStringGenerationContext() ); return new ReactiveGeneratorWrapper( reactiveIdGenerator, idGenerator ); } + + private record IdGeneratorCreationContext( + RootClass rootClass, + Property property, + GeneratorSettings defaults, + SimpleValue identifier, + RuntimeModelCreationContext buildingContext) implements GeneratorCreationContext { + + @Override + public Database getDatabase() { + return buildingContext.getBootModel().getDatabase(); + } + + @Override + public ServiceRegistry getServiceRegistry() { + return buildingContext.getBootstrapContext().getServiceRegistry(); + } + + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return defaults.getSqlStringGenerationContext(); + } + + @Override + public String getDefaultCatalog() { + return defaults.getDefaultCatalog(); + } + + @Override + public String getDefaultSchema() { + return defaults.getDefaultSchema(); + } + + @Override + public RootClass getRootClass() { + return rootClass; + } + + @Override + public PersistentClass getPersistentClass() { + return rootClass; + } + + @Override + public Property getProperty() { + return property; + } + + @Override + public Type getType() { + return identifier.getType(); + } + } } From dbaf302ca5534b0722210d9cdfca373d675955d0 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Sat, 7 Jun 2025 16:07:10 +0200 Subject: [PATCH 012/185] [#2154] Test Composite Id with generated values --- .../CompositeIdWithGeneratedValuesTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java new file mode 100644 index 000000000..0192bbbf7 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java @@ -0,0 +1,107 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.SequenceGenerator; + +import java.util.Collection; +import java.util.List; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 10, timeUnit = MINUTES) + +public class CompositeIdWithGeneratedValuesTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( Product.class ); + } + + @Test + public void testCompositeIdWithGeneratedValues(VertxTestContext context) { + final Product product = new Product( "name", 1150L ); + test( + context, + getMutinySessionFactory().withTransaction( session -> session.persist( product ) ) + .invoke( () -> assertThat( product.id ).isNotNull() ) + .chain( () -> getMutinySessionFactory().withTransaction( session -> session.find( + Product.class, + new ProductId( product.version, product.id ) + ) ) ) + .invoke( found -> { + assertThat( found ).hasFieldOrPropertyWithValue( "id", product.id ); + assertThat( found ).hasFieldOrPropertyWithValue( "version", product.version ); + assertThat( found ).hasFieldOrPropertyWithValue( "name", product.name ); + } ) + ); + } + + @Entity + @IdClass(ProductId.class) + public static class Product { + @Id + private Long version; + + @Id + @GeneratedValue + @SequenceGenerator(name = "product_seq", sequenceName = "product_seq") + private Long id; + + private String name; + + public Product() { + } + + public Product(String name, Long version) { + this.name = name; + this.version = version; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "(" + id + "," + version + "):" + name; + } + } + + public static class ProductId { + private Long version; + private Long id; + + private ProductId() { + } + + public ProductId(Long version, Long id) { + this.version = version; + this.id = id; + } + } +} From 2a707ae20c63eb9b3ef4cb2dadacf5ef0b014101 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Fri, 13 Jun 2025 23:45:33 +0200 Subject: [PATCH 013/185] [#2305] Switch to Maven Central publishing --- ci/release/Jenkinsfile | 8 +------- ci/snapshot-publish.Jenkinsfile | 2 +- publish.gradle | 6 +++--- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 8627950a1..3bd5e82a3 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -195,13 +195,7 @@ pipeline { configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { withCredentials([ - // TODO: Once we switch to maven-central publishing (from nexus2) we need to add a new credentials - // to use the following env variable names to set the user/password: - // - JRELEASER_MAVENCENTRAL_USERNAME - // - JRELEASER_MAVENCENTRAL_TOKEN - // Also use the new `credentialsId` for Maven Central, e.g.: - // usernamePassword(credentialsId: '???????', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'JRELEASER_NEXUS2_PASSWORD', usernameVariable: 'JRELEASER_NEXUS2_USERNAME'), + usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile index fea160b23..e50eb4cac 100644 --- a/ci/snapshot-publish.Jenkinsfile +++ b/ci/snapshot-publish.Jenkinsfile @@ -43,7 +43,7 @@ pipeline { // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh // TODO: Once we switch to maven-central publishing (from nexus2) we need to update credentialsId: // https://docs.gradle.org/current/samples/sample_publishing_credentials.html#:~:text=via%20environment%20variables - usernamePassword(credentialsId: 'ossrh.sonatype.org', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), + usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') ]) { diff --git a/publish.gradle b/publish.gradle index f77b26959..6d05724fa 100644 --- a/publish.gradle +++ b/publish.gradle @@ -21,7 +21,7 @@ jar { 'Implementation-Version': project.version, 'Implementation-Vendor': 'Hibernate.org', 'Implementation-Vendor-Id': 'org.hibernate', - 'Implementation-Url': 'http://hibernate.org/reactive', + 'Implementation-Url': 'https://hibernate.org/reactive', ) } } @@ -50,7 +50,7 @@ publishing { license { name = 'Apache License Version 2.0' url = 'https://opensource.org/licenses/Apache-2.0' - comments = 'See discussion at http://hibernate.org/community/license/ for more details.' + comments = 'See discussion at https://hibernate.org/community/license/ for more details.' distribution = 'repo' } } @@ -81,7 +81,7 @@ publishing { } maven { name = 'snapshots' - url = "https://oss.sonatype.org/content/repositories/snapshots/" + url = "https://central.sonatype.com/repository/maven-snapshots/" // So that Gradle uses the `ORG_GRADLE_PROJECT_snapshotsPassword` / `ORG_GRADLE_PROJECT_snapshotsUsername` // env variables to read the username/password for the `snapshots` repository publishing: credentials(PasswordCredentials) From f65f4a44ba0c021bac8bfdae8a75fea0584aeec1 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 17 Jun 2025 10:49:58 +0200 Subject: [PATCH 014/185] [#2309] Add JDK 26-ea to the build --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 63f7c7764..af832654c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -193,6 +193,7 @@ jobs: - { name: "21", java_version_numeric: 21, jvm_args: '--enable-preview' } - { name: "24", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' } - { name: "25-ea", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' } + - { name: "26-ea", java_version_numeric: 26, from: 'jdk.java.net', jvm_args: '--enable-preview' } steps: - name: Checkout ${{ inputs.branch }} uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 From f9347e29edb10e312f231fa495a0541bcc826211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 20 Jun 2025 16:44:59 +0200 Subject: [PATCH 015/185] [#2314] Test setMaxResults() on native queries --- .../org/hibernate/reactive/QueryTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java index c65c79ee4..1011c0b19 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/QueryTest.java @@ -43,6 +43,7 @@ import static jakarta.persistence.FetchType.LAZY; import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; import static org.hibernate.reactive.QueryTest.Author.AUTHOR_TABLE; import static org.hibernate.reactive.QueryTest.Author.HQL_NAMED_QUERY; import static org.hibernate.reactive.QueryTest.Author.SQL_NAMED_QUERY; @@ -395,6 +396,42 @@ public void testNativeEntityQueryWithParam(VertxTestContext context) { ); } + // https://github.com/hibernate/hibernate-reactive/issues/2314 + @Test + public void testNativeEntityQueryWithLimit(VertxTestContext context) { + Author author1 = new Author( "Iain M. Banks" ); + Author author2 = new Author( "Neal Stephenson" ); + Book book1 = new Book( "1-85723-235-6", "Feersum Endjinn", author1 ); + Book book2 = new Book( "0-380-97346-4", "Cryptonomicon", author2 ); + Book book3 = new Book( "0-553-08853-X", "Snow Crash", author2 ); + author1.books.add( book1 ); + author2.books.add( book2 ); + author2.books.add( book3 ); + + test( + context, + openSession() + .thenCompose( session -> session.persist( author1, author2 ) + .thenCompose( v -> session.flush() ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( session -> session.createNativeQuery( + "select * from " + BOOK_TABLE + " order by isbn", + Book.class + ) + .setMaxResults( 2 ) + .getResultList() ) + .thenAccept( books -> { + assertThat( books ) + .extracting( b -> b.id, b -> b.title, b -> b.isbn ) + .containsExactly( + tuple( book2.id, book2.title, book2.isbn ), + tuple( book3.id, book3.title, book3.isbn ) + ); + } ) + ); + } + @Test public void testNativeEntityQueryWithNamedParam(VertxTestContext context) { Author author1 = new Author( "Iain M. Banks" ); From 6740923caed21a664e8e2eb21d0c0c4a95005342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 20 Jun 2025 17:25:25 +0200 Subject: [PATCH 016/185] [#2314] Fix '?' parameters in limit/offset not being replaced with native equivalent when using PostgreSQL/MSSQL Reverts some changes introduced in e257189f643346799703eab7805de6c4b74a8b33, probably assuming this was more efficient and didn't break anything (understandably, considering tests did pass). --- .../sql/internal/ReactiveNativeSelectQueryPlanImpl.java | 5 +---- .../internal/ReactiveDeferredResultSetAccess.java | 9 ++++++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java index f1eca36df..bae87b8b3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java @@ -11,7 +11,6 @@ import java.util.Set; import java.util.concurrent.CompletionStage; -import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.results.ResultSetMapping; import org.hibernate.query.spi.DomainQueryExecutionContext; @@ -23,7 +22,6 @@ import org.hibernate.query.sql.spi.ParameterOccurrence; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.query.internal.ReactiveResultSetMappingProcessor; import org.hibernate.reactive.query.spi.ReactiveNativeSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; @@ -61,8 +59,7 @@ public ReactiveNativeSelectQueryPlanImpl( resultSetMapping.addAffectedTableNames( affectedTableNames, sessionFactory ); } this.affectedTableNames = affectedTableNames; - Dialect dialect = sessionFactory.getJdbcServices().getDialect(); - this.sql = Parameters.instance( dialect ).process( parser.process() ); + this.sql = parser.process(); this.parameterList = parameterList; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java index 3f88bcffb..ac119a7d0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java @@ -14,6 +14,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockOptions; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -22,6 +23,7 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.pool.ReactiveConnection; +import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.util.impl.CompletionStages; @@ -172,6 +174,11 @@ private CompletionStage executeQuery() { return completedFuture( logicalConnection ) .thenCompose( lg -> { LOG.tracef( "Executing query to retrieve ResultSet : %s", getFinalSql() ); + + Dialect dialect = executionContext.getSession().getJdbcServices().getDialect(); + // This must happen at the very last minute in order to process parameters + // added in org.hibernate.dialect.pagination.OffsetFetchLimitHandler.processSql + final String sql = Parameters.instance( dialect ).process( getFinalSql() ); Object[] parameters = PreparedStatementAdaptor.bind( super::bindParameters ); final SessionEventListenerManager eventListenerManager = executionContext @@ -181,7 +188,7 @@ private CompletionStage executeQuery() { eventListenerManager.jdbcExecuteStatementStart(); return connection() - .selectJdbc( getFinalSql(), parameters ) + .selectJdbc( sql, parameters ) .thenCompose( this::validateResultSet ) .whenComplete( (resultSet, throwable) -> { // FIXME: I don't know if this event makes sense for Vert.x From 1dd1a1a9eaccaea936ffa387993580ada44dde37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 23 Jun 2025 12:51:04 +0200 Subject: [PATCH 017/185] Release job: minor aligmnent on ORM's job --- ci/release/Jenkinsfile | 47 ++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 3bd5e82a3..867a249f7 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -1,9 +1,7 @@ #! /usr/bin/groovy /* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors */ /* @@ -89,9 +87,13 @@ pipeline { ) } stages { - stage('Release check') { + stage('Check') { steps { script { + print "INFO: params.RELEASE_VERSION = ${params.RELEASE_VERSION}" + print "INFO: params.DEVELOPMENT_VERSION = ${params.DEVELOPMENT_VERSION}" + print "INFO: params.RELEASE_DRY_RUN? = ${params.RELEASE_DRY_RUN}" + checkoutReleaseScripts() def currentVersion = Version.parseDevelopmentVersion( sh( @@ -107,7 +109,9 @@ pipeline { echo "Release was requested manually" if ( !params.RELEASE_VERSION ) { - throw new IllegalArgumentException( 'Missing value for parameter RELEASE_VERSION. This parameter must be set explicitly to prevent mistakes.' ) + throw new IllegalArgumentException( + 'Missing value for parameter RELEASE_VERSION. This parameter must be set explicitly to prevent mistakes.' + ) } releaseVersion = Version.parseReleaseVersion( params.RELEASE_VERSION ) @@ -149,17 +153,12 @@ pipeline { env.RELEASE_VERSION = releaseVersion.toString() env.DEVELOPMENT_VERSION = developmentVersion.toString() - // Dry run is not supported at the moment - env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : "" + env.SCRIPT_OPTIONS = params.RELEASE_DRY_RUN ? "-d" : "" env.JRELEASER_DRY_RUN = params.RELEASE_DRY_RUN - - // Determine version id to check if Jira version exists - // This step doesn't work for Hibernate Reactive (the project has been created with a different type on JIRA) - // sh ".release/scripts/determine-jira-version-id.sh ${env.JIRA_KEY} ${releaseVersion.withoutFinalQualifier}" } } } - stage('Release prepare') { + stage('Prepare') { steps { script { checkoutReleaseScripts() @@ -168,13 +167,13 @@ pipeline { configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { - - sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { + sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { // set release version // update changelog from JIRA // tags the version // changes the version to the provided development version withEnv([ + "DISABLE_REMOTE_GRADLE_CACHE=true", // Increase the amount of memory for this part since asciidoctor doc rendering consumes a lot of metaspace "GRADLE_OPTS=-Dorg.gradle.jvmargs='-Dlog4j2.disableJmx -Xmx4g -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Duser.language=en -Duser.country=US -Duser.timezone=UTC -Dfile.encoding=UTF-8'" ]) { @@ -185,7 +184,7 @@ pipeline { } } } - stage('Publish release') { + stage('Publish') { steps { script { checkoutReleaseScripts() @@ -195,16 +194,20 @@ pipeline { configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { withCredentials([ - usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), - gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), - file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), - string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), - string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') + usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'JRELEASER_MAVENCENTRAL_TOKEN', usernameVariable: 'JRELEASER_MAVENCENTRAL_USERNAME'), + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default'), + file(credentialsId: 'release.gpg.private-key', variable: 'RELEASE_GPG_PRIVATE_KEY_PATH'), + string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), + string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') ]) { sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { // performs documentation upload and Sonatype release // push to github - sh ".release/scripts/publish.sh -j ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" + withEnv([ + "DISABLE_REMOTE_GRADLE_CACHE=true" + ]) { + sh ".release/scripts/publish.sh -j ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" + } } } } From 8684f9c25713126de5bc2b1d0008a5aefc467d56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 23 Jun 2025 12:52:52 +0200 Subject: [PATCH 018/185] Release job: Avoid auto-release when there are no "releasable" commits The determination of "releasable" is in the release scripts, but currently it boils down to having an issue key in the commit message. --- ci/release/Jenkinsfile | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 867a249f7..87a959714 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -122,14 +122,15 @@ pipeline { else { echo "Release was triggered automatically" - // Avoid doing an automatic release for commits from a release - def lastCommitter = sh(script: 'git show -s --format=\'%an\'', returnStdout: true).trim() - def secondLastCommitter = sh(script: 'git show -s --format=\'%an\' HEAD~1', returnStdout: true).trim() - echo "Last two commits were performed by '${lastCommitter}'/'${secondLastCommitter}'." - - if (lastCommitter == 'Hibernate-CI' && secondLastCommitter == 'Hibernate-CI') { - print "INFO: Automatic release skipped because last commits were for the previous release" - currentBuild.result = 'ABORTED' + // Avoid doing an automatic release if there are no "releasable" commits since the last release (see release scripts for determination) + def releasableCommitCount = sh( + script: ".release/scripts/count-releasable-commits.sh ${env.PROJECT}", + returnStdout: true + ).trim().toInteger() + if ( releasableCommitCount <= 0 ) { + print "INFO: Automatic release skipped because no releasable commits were pushed since the previous release" + currentBuild.getRawBuild().getExecutor().interrupt(Result.NOT_BUILT) + sleep(1) // Interrupt is not blocking and does not take effect immediately. return } From cd4586e7dcf6c3cd1c91d1796d8554713adeceef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 23 Jun 2025 12:54:34 +0200 Subject: [PATCH 019/185] Release job: update the hibernate.org website automatically for micros --- ci/release/Jenkinsfile | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 87a959714..d4afbf6cf 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -215,6 +215,33 @@ pipeline { } } } + stage('Update website') { + steps { + script { + checkoutReleaseScripts() + + configFileProvider([ + configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), + configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") + ]) { + withCredentials([ + gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default') + ]) { + sshagent( ['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net'] ) { + dir( '.release/hibernate.org' ) { + checkout scmGit( + branches: [[name: '*/production']], + extensions: [], + userRemoteConfigs: [[credentialsId: 'ed25519.Hibernate-CI.github.com', url: 'https://github.com/hibernate/hibernate.org.git']] + ) + sh "../scripts/website-release.sh ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION}" + } + } + } + } + } + } + } } post { always { From eab0ee1e8521602f90b42ff544d66352b3e8ae6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 23 Jun 2025 13:02:00 +0200 Subject: [PATCH 020/185] Release job: code for weekly release (disabled for now) --- ci/release/Jenkinsfile | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index d4afbf6cf..6e4aacc33 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -15,11 +15,10 @@ import org.hibernate.jenkins.pipeline.helpers.version.Version // Global build configuration env.PROJECT = "reactive" env.JIRA_KEY = "HREACT" -def RELEASE_ON_PUSH = false // Set to `true` *only* on branches where you want a release on each push. +def RELEASE_ON_SCHEDULE = false // Set to `true` *only* on branches where you want a scheduled release. print "INFO: env.PROJECT = ${env.PROJECT}" print "INFO: env.JIRA_KEY = ${env.JIRA_KEY}" -print "INFO: RELEASE_ON_PUSH = ${RELEASE_ON_PUSH}" // -------------------------------------------- // Build conditions @@ -32,10 +31,17 @@ if (currentBuild.getBuildCauses().toString().contains('BranchIndexingCause')) { } def manualRelease = currentBuild.getBuildCauses().toString().contains( 'UserIdCause' ) +def cronRelease = currentBuild.getBuildCauses().toString().contains( 'TimerTriggerCause' ) // Only do automatic release on branches where we opted in -if ( !manualRelease && !RELEASE_ON_PUSH ) { - print "INFO: Build skipped because automated releases are disabled on this branch. See constant RELEASE_ON_PUSH in ci/release/Jenkinsfile" +if ( !manualRelease && !cronRelease ) { + print "INFO: Build skipped because automated releases on push are disabled on this branch." + currentBuild.result = 'NOT_BUILT' + return +} + +if ( !manualRelease && cronRelease && !RELEASE_ON_SCHEDULE ) { + print "INFO: Build skipped because automated releases are disabled on this branch. See constant RELEASE_ON_SCHEDULE in ci/release/Jenkinsfile" currentBuild.result = 'NOT_BUILT' return } @@ -51,6 +57,7 @@ def checkoutReleaseScripts() { } } + // -------------------------------------------- // Pipeline @@ -58,12 +65,15 @@ pipeline { agent { label 'Release' } + triggers { + // Run every week Sunday 1 AM + cron('0 1 * * 0') + } tools { jdk 'OpenJDK 17 Latest' } options { buildDiscarder logRotator(daysToKeepStr: '30', numToKeepStr: '10') - rateLimitBuilds(throttle: [count: 1, durationName: 'day', userBoost: true]) disableConcurrentBuilds(abortPrevious: false) preserveStashes() } From b8e5847e40fffc8efaa034d8e0869de2d07fee6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 23 Jun 2025 13:05:21 +0200 Subject: [PATCH 021/185] Release job: enable auto release on weekends --- ci/release/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 6e4aacc33..590b1e47c 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -15,7 +15,7 @@ import org.hibernate.jenkins.pipeline.helpers.version.Version // Global build configuration env.PROJECT = "reactive" env.JIRA_KEY = "HREACT" -def RELEASE_ON_SCHEDULE = false // Set to `true` *only* on branches where you want a scheduled release. +def RELEASE_ON_SCHEDULE = true // Set to `true` *only* on branches where you want a scheduled release. print "INFO: env.PROJECT = ${env.PROJECT}" print "INFO: env.JIRA_KEY = ${env.JIRA_KEY}" From 8776831740429c73a81cdb14342122102b43cdc3 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Mon, 23 Jun 2025 11:36:05 +0000 Subject: [PATCH 022/185] Update project version to : `3.0.1.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 6b36d7e6b..41656b3e6 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.1-SNAPSHOT \ No newline at end of file +projectVersion=3.0.1.Final \ No newline at end of file From 7591e9694c653affa64be93f37d19cb1136c9831 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Mon, 23 Jun 2025 11:37:02 +0000 Subject: [PATCH 023/185] Update project version to : `3.0.2-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 41656b3e6..d30ffd67b 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.1.Final \ No newline at end of file +projectVersion=3.0.2-SNAPSHOT \ No newline at end of file From 31e8f8cdfdfe1406741f18bcf7f67a94764c5dde Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Thu, 19 Jun 2025 15:10:49 +0200 Subject: [PATCH 024/185] [#2305] Update optional snapshot repositories to point to central snapshots instead of ossrh ones since now our snapshots are published to central. --- build.gradle | 6 +++--- examples/native-sql-example/build.gradle | 6 +++--- examples/session-example/build.gradle | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 452f7ea2c..24879ad37 100644 --- a/build.gradle +++ b/build.gradle @@ -56,9 +56,9 @@ subprojects { // Useful for local development, it should be disabled otherwise mavenLocal() } - // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep - if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + // Example: ./gradlew build -PenableCentralSonatypeSnapshotsRep + if ( project.hasProperty('enableCentralSonatypeSnapshotsRep') ) { + maven { url 'https://central.sonatype.com/repository/maven-snapshots/' } } mavenCentral() diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle index c79ec8984..2cfd95a16 100644 --- a/examples/native-sql-example/build.gradle +++ b/examples/native-sql-example/build.gradle @@ -8,9 +8,9 @@ buildscript { mavenLocal() } // Optional: Enables snapshots repository - // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep - if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + // Example: ./gradlew build -PenableCentralSonatypeSnapshotsRep + if ( project.hasProperty('enableCentralSonatypeSnapshotsRep') ) { + maven { url 'https://central.sonatype.com/repository/maven-snapshots/' } } mavenCentral() } diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle index 4da40ba69..35fa10dcc 100644 --- a/examples/session-example/build.gradle +++ b/examples/session-example/build.gradle @@ -8,9 +8,9 @@ buildscript { mavenLocal() } // Optional: Enables snapshots repository - // Example: ./gradlew build -PenableSonatypeOpenSourceSnapshotsRep - if ( project.hasProperty('enableSonatypeOpenSourceSnapshotsRep') ) { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + // Example: ./gradlew build -PenableCentralSonatypeSnapshotsRep + if ( project.hasProperty('enableCentralSonatypeSnapshotsRep') ) { + maven { url 'https://central.sonatype.com/repository/maven-snapshots/' } } mavenCentral() } From 925e65953be7d3787769805656e9477cc32084c8 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 27 Jun 2025 15:17:54 +0200 Subject: [PATCH 025/185] [#2324] Upgrade Vert.x SQL client to 4.5.16 --- README.md | 10 +++++----- build.gradle | 2 +- gradle.properties | 6 +++--- tooling/jbang/CockroachDBReactiveTest.java.qute | 4 ++-- tooling/jbang/Db2ReactiveTest.java.qute | 4 ++-- tooling/jbang/Example.java | 6 +++--- tooling/jbang/MariaDBReactiveTest.java.qute | 4 ++-- tooling/jbang/MySQLReactiveTest.java.qute | 4 ++-- tooling/jbang/PostgreSQLReactiveTest.java.qute | 4 ++-- tooling/jbang/ReactiveTest.java | 8 ++++---- 10 files changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index cf8a9f62b..12904bd7b 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,11 @@ Hibernate Reactive has been tested with: - MS SQL Server 2022 - Oracle 23 - [Hibernate ORM][] 7.0.2.Final -- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.15 -- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.15 -- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.15 -- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.15 -- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.15 +- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.16 +- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.16 +- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.16 +- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.16 +- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.16 - [Quarkus][Quarkus] via the Hibernate Reactive extension [PostgreSQL]: https://www.postgresql.org diff --git a/build.gradle b/build.gradle index 24879ad37..c514dd743 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ ext { // Example: // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT if ( !project.hasProperty( 'vertxSqlClientVersion' ) ) { - vertxSqlClientVersion = '4.5.15' + vertxSqlClientVersion = '4.5.16' } testcontainersVersion = '1.21.0' diff --git a/gradle.properties b/gradle.properties index 53ece31a1..fa3ce0914 100644 --- a/gradle.properties +++ b/gradle.properties @@ -47,9 +47,9 @@ hibernateOrmVersion = 7.0.2.Final #skipOrmVersionParsing = true # Override default Vert.x Sql client version -#vertxSqlClientVersion = 4.5.15-SNAPSHOT +#vertxSqlClientVersion = 4.5.16-SNAPSHOT # Override default Vert.x Web client and server versions. For integration tests, both default to vertxSqlClientVersion -#vertxWebVersion = 4.5.15 -#vertxWebtClientVersion = 4.5.15 +#vertxWebVersion = 4.5.16 +#vertxWebtClientVersion = 4.5.16 diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 908c828e5..337916470 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.16} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.16} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute index 1b4f599e5..cff1b8101 100755 --- a/tooling/jbang/Db2ReactiveTest.java.qute +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} +//DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.5.16} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.16} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/Example.java b/tooling/jbang/Example.java index 4342974f8..a807fb33f 100644 --- a/tooling/jbang/Example.java +++ b/tooling/jbang/Example.java @@ -6,9 +6,9 @@ */ //DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.15} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.15} -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.15} +//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.16} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.16} +//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.16} //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:3.0.0.Final} //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index bef838391..3a9698fa1 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.16} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.16} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index 5047f8a4d..1e6debefc 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.5.16} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.16} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index dfd7f4a67..ec4ecee00 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -5,8 +5,8 @@ * Copyright: Red Hat Inc. and Hibernate Authors */ -//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.15} +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.5.16} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.5.16} //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 5b732b70f..c0db48f2b 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -5,11 +5,11 @@ */ ///usr/bin/env jbang "$0" "$@" ; exit $? -//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.15} +//DEPS io.vertx:vertx-pg-client:${vertx.version:4.5.16} //DEPS com.ongres.scram:client:2.1 -//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.15} -//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.15} -//DEPS io.vertx:vertx-unit:${vertx.version:4.5.15} +//DEPS io.vertx:vertx-db2-client:${vertx.version:4.5.16} +//DEPS io.vertx:vertx-mysql-client:${vertx.version:4.5.16} +//DEPS io.vertx:vertx-unit:${vertx.version:4.5.16} //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 From b8cbc436d29fa2bb5966ceedb754094132d5cf1c Mon Sep 17 00:00:00 2001 From: Hibernate CI Date: Mon, 30 Jun 2025 11:26:17 +0200 Subject: [PATCH 026/185] Update project version to : `3.0.2.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index d30ffd67b..fa3eb0392 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.2-SNAPSHOT \ No newline at end of file +projectVersion=3.0.2.Final \ No newline at end of file From 12e08b4c8ab430a24ea76fadd4125d78a09e60ce Mon Sep 17 00:00:00 2001 From: Hibernate CI Date: Mon, 30 Jun 2025 11:27:04 +0200 Subject: [PATCH 027/185] Update project version to : `3.0.3-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index fa3eb0392..963beb935 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.2.Final \ No newline at end of file +projectVersion=3.0.3-SNAPSHOT \ No newline at end of file From df4bb3e78f54c63f3d045bd017a0b7ee360b8dd1 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 6 Jul 2025 01:02:42 +0000 Subject: [PATCH 028/185] Update project version to : `3.0.3.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 963beb935..66330e82f 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.3-SNAPSHOT \ No newline at end of file +projectVersion=3.0.3.Final \ No newline at end of file From 3a723838e56450dfac4e32a170ee3565fccc4d49 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 6 Jul 2025 01:03:39 +0000 Subject: [PATCH 029/185] Update project version to : `3.0.4-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 66330e82f..4fc6321a8 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.3.Final \ No newline at end of file +projectVersion=3.0.4-SNAPSHOT \ No newline at end of file From 6075148d5e2ef4cc942a9924b97e0f9a1deab6a6 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 7 Jul 2025 17:15:27 +0200 Subject: [PATCH 030/185] [#2333] Rename property for enabling the snapshot repository From `enableSonatypeOpenSourceSnapshotsRep` to `enableCentralSonatypeSnapshotsRep` (in [gradle.properties](https://github.com/hibernate/hibernate-reactive/blob/main/gradle.properties)). It was changed in commit c254ebb5a6cda29843b8a3c1a8d6f8713450eec5 as part of the move to Maven Central (#2305). --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index fa3ce0914..3ad8bf467 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,8 +28,8 @@ org.gradle.java.installations.auto-download=false # Db2, MySql, PostgreSQL, CockroachDB, SqlServer, Oracle #db = MSSQL -# Enable the SonatypeOS maven repository (mainly for Vert.x snapshots) when present (value ignored) -#enableSonatypeOpenSourceSnapshotsRep = true +# Enable the maven Central Snapshot repository, when set to any value (the value is ignored) +#enableCentralSonatypeSnapshotsRep = true # Enable the maven local repository (for local development when needed) when present (value ignored) #enableMavenLocalRepo = true From 1efb6482a8077a414d03dc3c98a4ad1a0bd540b0 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 8 Jul 2025 09:49:03 +0200 Subject: [PATCH 031/185] [#2333] Print the resolved Hibernate ORM and Vert.x version This is important to check what verison is actually resolved on the `wip/*` branches, where we test the snapshots, because we use a versions range. --- examples/native-sql-example/build.gradle | 33 ++++++++++++++++++++++++ examples/session-example/build.gradle | 32 +++++++++++++++++++++++ hibernate-reactive-core/build.gradle | 32 +++++++++++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle index 2cfd95a16..8695f9df7 100644 --- a/examples/native-sql-example/build.gradle +++ b/examples/native-sql-example/build.gradle @@ -75,3 +75,36 @@ tasks.register( "runAllExamples" ) { dependsOn = ["runAllExamplesOnPostgreSQL"] description = "Run all examples on ${dbs}" } + +// Optional: Task to print the resolved versions of Hibernate ORM and Vert.x +tasks.register( "printResolvedVersions" ) { + description = "Print the resolved hibernate-orm-core and vert.x versions" + doLast { + def hibernateCoreVersion = "n/a" + def vertxVersion = "n/a" + + // Resolve Hibernate Core and Vert.x versions from compile classpath + configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact -> + if (artifact.moduleVersion.id.name == 'hibernate-core') { + hibernateCoreVersion = artifact.moduleVersion.id.version + } + if (artifact.moduleVersion.id.group == 'io.vertx' && artifact.moduleVersion.id.name == 'vertx-sql-client') { + vertxVersion = artifact.moduleVersion.id.version + } + } + + // Print the resolved versions + println "Resolved Hibernate ORM Core Version: ${hibernateCoreVersion}" + println "Resolved Vert.x SQL client Version: ${vertxVersion}" + } +} + +// Make the version printing task run before tests and JavaExec tasks +tasks.withType( Test ).configureEach { + dependsOn printResolvedVersions +} + +tasks.withType( JavaExec ).configureEach { + dependsOn printResolvedVersions +} + diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle index 35fa10dcc..f436ccd27 100644 --- a/examples/session-example/build.gradle +++ b/examples/session-example/build.gradle @@ -81,3 +81,35 @@ tasks.register( "runAllExamples" ) { dependsOn = ["runAllExamplesOnPostgreSQL", "runAllExamplesOnMySQL"] description = "Run all examples on ${dbs}" } + +// Optional: Task to print the resolved versions of Hibernate ORM and Vert.x +tasks.register( "printResolvedVersions" ) { + description = "Print the resolved hibernate-orm-core and vert.x versions" + doLast { + def hibernateCoreVersion = "n/a" + def vertxVersion = "n/a" + + // Resolve Hibernate Core and Vert.x versions from compile classpath + configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact -> + if (artifact.moduleVersion.id.name == 'hibernate-core') { + hibernateCoreVersion = artifact.moduleVersion.id.version + } + if (artifact.moduleVersion.id.group == 'io.vertx' && artifact.moduleVersion.id.name == 'vertx-sql-client') { + vertxVersion = artifact.moduleVersion.id.version + } + } + + // Print the resolved versions + println "Resolved Hibernate ORM Core Version: ${hibernateCoreVersion}" + println "Resolved Vert.x SQL client Version: ${vertxVersion}" + } +} + +// Make the version printing task run before tests and JavaExec tasks +tasks.withType( Test ).configureEach { + dependsOn printResolvedVersions +} + +tasks.withType( JavaExec ).configureEach { + dependsOn printResolvedVersions +} diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index 01f593d80..36717d038 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -167,3 +167,35 @@ tasks.register( "testAll", Test ) { description = "Run tests for ${dbs}" dependsOn = dbs.collect( [] as HashSet ) { db -> "testDb${db}" } } + +// Task to print the resolved versions of Hibernate ORM and Vert.x +tasks.register( "printResolvedVersions" ) { + description = "Print the resolved hibernate-orm-core and vert.x versions" + doLast { + def hibernateCoreVersion = "n/a" + def vertxVersion = "n/a" + + // Resolve Hibernate Core and Vert.x versions from compile classpath + configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact -> + if (artifact.moduleVersion.id.name == 'hibernate-core') { + hibernateCoreVersion = artifact.moduleVersion.id.version + } + if (artifact.moduleVersion.id.group == 'io.vertx' && artifact.moduleVersion.id.name == 'vertx-sql-client') { + vertxVersion = artifact.moduleVersion.id.version + } + } + + // Print the resolved versions + println "Resolved Hibernate ORM Core Version: ${hibernateCoreVersion}" + println "Resolved Vert.x SQL client Version: ${vertxVersion}" + } +} + +// Make the version printing task run before tests and JavaExec tasks +tasks.withType( Test ).configureEach { + dependsOn printResolvedVersions +} + +tasks.withType( JavaExec ).configureEach { + dependsOn printResolvedVersions +} From f88da617192893b6e56655b197bd36d694241f65 Mon Sep 17 00:00:00 2001 From: exoego Date: Thu, 3 Jul 2025 18:10:19 +0900 Subject: [PATCH 032/185] [#1989] Migrate to Gradle version catalogs --- build.gradle | 31 +++------ documentation/build.gradle | 2 +- examples/native-sql-example/build.gradle | 14 ++-- examples/session-example/build.gradle | 16 ++--- gradle.properties | 10 +-- gradle/libs.versions.toml | 59 ++++++++++++++++ hibernate-reactive-core/build.gradle | 68 +++++++++---------- .../bytecode-enhancements-it/build.gradle | 23 +++---- .../build.gradle | 27 +++----- .../techempower-postgres-it/build.gradle | 30 +++----- .../verticle-postgres-it/build.gradle | 30 +++----- .../reactive/env/VersionsPlugin.java | 27 ++++++-- .../reactive/env/VersionsTomlParser.java | 68 +++++++++++++++++++ settings.gradle | 38 +++++++++++ 14 files changed, 291 insertions(+), 152 deletions(-) create mode 100644 gradle/libs.versions.toml create mode 100644 local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsTomlParser.java diff --git a/build.gradle b/build.gradle index c514dd743..532d932ef 100644 --- a/build.gradle +++ b/build.gradle @@ -3,33 +3,15 @@ plugins { id 'java-library' id 'maven-publish' - id 'com.diffplug.spotless' version '6.25.0' - id 'org.asciidoctor.jvm.convert' version '4.0.2' apply false - id 'io.github.gradle-nexus.publish-plugin' version '1.3.0' + alias(libs.plugins.com.diffplug.spotless) + alias(libs.plugins.org.asciidoctor.jvm.convert) apply false + alias(libs.plugins.io.github.gradle.nexus.publish.plugin) } group = "org.hibernate.reactive" // leverage the ProjectVersion which comes from the `local.versions` plugin version = project.projectVersion.fullName -// Versions which need to be aligned across modules; this also -// allows overriding the build using a parameter, which can be -// useful to monitor compatibility for upcoming versions on CI: -// -// ./gradlew clean build -PhibernateOrmVersion=5.6.15-SNAPSHOT -ext { - // Mainly, to allow CI to test the latest versions of Vert.X - // Example: - // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT - if ( !project.hasProperty( 'vertxSqlClientVersion' ) ) { - vertxSqlClientVersion = '4.5.16' - } - - testcontainersVersion = '1.21.0' - - logger.lifecycle "Vert.x SQL Client Version: " + project.vertxSqlClientVersion -} - subprojects { apply plugin: 'java-library' apply plugin: 'com.diffplug.spotless' @@ -135,3 +117,10 @@ subprojects { } } +rootProject.afterEvaluate { + // Workaround since "libs.versions.NAME" notation cannot be used here + def libs = project.extensions.getByType(VersionCatalogsExtension).named('libs') + logger.lifecycle "ORM version: ${libs.findVersion('hibernateOrmVersion').get().requiredVersion}" + logger.lifecycle "ORM Gradle plugin version: ${libs.findVersion('hibernateOrmGradlePluginVersion').get().requiredVersion}" + logger.lifecycle "Vert.x SQL Client version: ${libs.findVersion('vertxSqlClientVersion').get().requiredVersion}" +} diff --git a/documentation/build.gradle b/documentation/build.gradle index aa6a27622..95c3dcb2b 100644 --- a/documentation/build.gradle +++ b/documentation/build.gradle @@ -52,7 +52,7 @@ def aggregateJavadocsTask = tasks.register( 'aggregateJavadocs', Javadoc ) { use = true options.encoding = 'UTF-8' - def matcher = hibernateOrmVersion =~ /\d+\.\d+/ + def matcher = libs.versions.hibernateOrmVersion =~ /\d+\.\d+/ def ormMinorVersion = matcher.find() ? matcher.group() : "5.6"; links = [ diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle index 8695f9df7..50c5a4117 100644 --- a/examples/native-sql-example/build.gradle +++ b/examples/native-sql-example/build.gradle @@ -18,7 +18,7 @@ buildscript { plugins { // Optional: Hibernate Gradle plugin to enable bytecode enhancements - id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}" + alias(libs.plugins.org.hibernate.orm) } description = 'Hibernate Reactive native SQL Example' @@ -27,20 +27,20 @@ dependencies { implementation project( ':hibernate-reactive-core' ) // Hibernate Validator (optional) - implementation 'org.hibernate.validator:hibernate-validator:8.0.2.Final' - runtimeOnly 'org.glassfish.expressly:expressly:5.0.0' + implementation(libs.org.hibernate.validator.hibernate.validator) + runtimeOnly(libs.org.glassfish.expressly.expressly) // JPA metamodel generation for criteria queries (optional) - annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}" + annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen) // database driver for PostgreSQL - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // logging (optional) - runtimeOnly "org.apache.logging.log4j:log4j-core:2.20.0" + runtimeOnly(libs.org.apache.logging.log4j.log4j.core) // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly(libs.com.ongres.scram.client) } // Optional: enable the bytecode enhancements diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle index f436ccd27..41f9135a5 100644 --- a/examples/session-example/build.gradle +++ b/examples/session-example/build.gradle @@ -18,7 +18,7 @@ buildscript { plugins { // Optional: Hibernate Gradle plugin to enable bytecode enhancements - id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}" + alias(libs.plugins.org.hibernate.orm) } description = 'Hibernate Reactive Session Examples' @@ -27,21 +27,21 @@ dependencies { implementation project( ':hibernate-reactive-core' ) // Hibernate Validator (optional) - implementation 'org.hibernate.validator:hibernate-validator:8.0.2.Final' - runtimeOnly 'org.glassfish.expressly:expressly:5.0.0' + implementation(libs.org.hibernate.validator.hibernate.validator) + runtimeOnly(libs.org.glassfish.expressly.expressly) // JPA metamodel generation for criteria queries (optional) - annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}" + annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen) // database drivers for PostgreSQL and MySQL - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" - runtimeOnly "io.vertx:vertx-mysql-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) + runtimeOnly(libs.io.vertx.vertx.mysql.client) // logging (optional) - runtimeOnly "org.apache.logging.log4j:log4j-core:2.20.0" + runtimeOnly(libs.org.apache.logging.log4j.log4j.core) // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly(libs.com.ongres.scram.client) } // Optional: enable the bytecode enhancements diff --git a/gradle.properties b/gradle.properties index 3ad8bf467..346e9fa42 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,7 +18,9 @@ org.gradle.java.installations.auto-download=false ######################################################################### # Additional custom gradle build properties. -# Please, leave these properties commented upstream +# Please, leave these properties commented upstream. +# They are meant to be used for local builds or WIP branches. +# The same properties can be set from the command line. ########################################################################## # Enable Testcontainers + Docker when present (value ignored) @@ -34,12 +36,12 @@ org.gradle.java.installations.auto-download=false # Enable the maven local repository (for local development when needed) when present (value ignored) #enableMavenLocalRepo = true +### Settings the following properties will override the version defined in gradle/libs.versions.toml + # The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) -hibernateOrmVersion = 7.0.2.Final +#hibernateOrmVersion = 7.0.2.Final # Override default Hibernate ORM Gradle plugin version -# Using the stable version because I don't know how to configure the build to download the snapshot version from -# a remote repository #hibernateOrmGradlePluginVersion = 7.0.2.Final # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 000000000..816a2efb5 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,59 @@ +[versions] +assertjVersion = "3.27.3" +hibernateOrmVersion = "7.0.2.Final" +hibernateOrmGradlePluginVersion = "7.0.2.Final" +jacksonDatabindVersion = "2.15.2" +jbossLoggingAnnotationVersion = "3.0.4.Final" +jbossLoggingVersion = "3.6.1.Final" +junitVersion = "5.11.3" +log4jVersion = "2.20.0" +testcontainersVersion = "1.21.0" +vertxSqlClientVersion = "4.5.16" +vertxWebVersion= "4.5.16" +vertxWebClientVersion = "4.5.16" + +[libraries] +com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } +com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.0.0" } +com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "12.10.0.jre11" } +com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.3.0" } +com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" } +io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.0" } +io-vertx-vertx-db2-client = { group = "io.vertx", name = "vertx-db2-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-junit5 = { group = "io.vertx", name = "vertx-junit5", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-micrometer-metrics = { group = "io.vertx", name = "vertx-micrometer-metrics", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-mssql-client = { group = "io.vertx", name = "vertx-mssql-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-mysql-client = { group = "io.vertx", name = "vertx-mysql-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-oracle-client = { group = "io.vertx", name = "vertx-oracle-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-pg-client = { group = "io.vertx", name = "vertx-pg-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-sql-client = { group = "io.vertx", name = "vertx-sql-client", version.ref = "vertxSqlClientVersion" } +io-vertx-vertx-web = { group = "io.vertx", name = "vertx-web", version.ref = "vertxWebVersion" } +io-vertx-vertx-web-client = { group = "io.vertx", name = "vertx-web-client", version.ref = "vertxWebClientVersion" } +org-apache-logging-log4j-log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4jVersion" } +org-assertj-assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertjVersion" } +org-ehcache-ehcache = { group = "org.ehcache", name = "ehcache", version = "3.10.8" } +org-glassfish-expressly-expressly = { group = "org.glassfish.expressly", name = "expressly", version = "5.0.0" } +org-hibernate-orm-hibernate-core = { group = "org.hibernate.orm", name = "hibernate-core", version.ref = "hibernateOrmVersion" } +org-hibernate-orm-hibernate-jcache = { group = "org.hibernate.orm", name = "hibernate-jcache", version.ref = "hibernateOrmVersion" } +org-hibernate-orm-hibernate-jpamodelgen = { group = "org.hibernate.orm", name = "hibernate-jpamodelgen", version.ref = "hibernateOrmVersion" } +org-hibernate-validator-hibernate-validator = { group = "org.hibernate.validator", name = "hibernate-validator", version = "8.0.2.Final" } +org-jboss-logging-jboss-logging = { group = "org.jboss.logging", name = "jboss-logging", version.ref = "jbossLoggingVersion" } +org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", name = "jboss-logging-annotations", version.ref = "jbossLoggingAnnotationVersion" } +org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" } +org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" } +org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" } +org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.3" } +org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.5" } +org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" } +org-testcontainers-db2 = { group = "org.testcontainers", name = "db2", version.ref = "testcontainersVersion" } +org-testcontainers-mariadb = { group = "org.testcontainers", name = "mariadb", version.ref = "testcontainersVersion" } +org-testcontainers-mssqlserver = { group = "org.testcontainers", name = "mssqlserver", version.ref = "testcontainersVersion" } +org-testcontainers-mysql = { group = "org.testcontainers", name = "mysql", version.ref = "testcontainersVersion" } +org-testcontainers-oracle-xe = { group = "org.testcontainers", name = "oracle-xe", version.ref = "testcontainersVersion" } +org-testcontainers-postgresql = { group = "org.testcontainers", name = "postgresql", version.ref = "testcontainersVersion" } + +[plugins] +com-diffplug-spotless = { id = "com.diffplug.spotless", version = "6.25.0" } +io-github-gradle-nexus-publish-plugin = { id = "io.github.gradle-nexus.publish-plugin", version = "1.3.0" } +org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.2" } +org-hibernate-orm = { id = "org.hibernate.orm", version.ref = "hibernateOrmGradlePluginVersion" } diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index 36717d038..04f35e985 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -8,77 +8,77 @@ apply from: publishScript dependencies { - api "org.hibernate.orm:hibernate-core:${hibernateOrmVersion}" + api(libs.org.hibernate.orm.hibernate.core) - api 'io.smallrye.reactive:mutiny:2.9.0' + api(libs.io.smallrye.reactive.mutiny) //Logging - implementation 'org.jboss.logging:jboss-logging:3.6.1.Final' - annotationProcessor 'org.jboss.logging:jboss-logging:3.6.1.Final' + implementation(libs.org.jboss.logging.jboss.logging) + annotationProcessor(libs.org.jboss.logging.jboss.logging) - compileOnly 'org.jboss.logging:jboss-logging-annotations:3.0.4.Final' - annotationProcessor 'org.jboss.logging:jboss-logging-annotations:3.0.4.Final' - annotationProcessor 'org.jboss.logging:jboss-logging-processor:3.0.4.Final' + compileOnly(libs.org.jboss.logging.jboss.logging.annotations) + annotationProcessor(libs.org.jboss.logging.jboss.logging.annotations) + annotationProcessor(libs.org.jboss.logging.jboss.logging.processor) //Specific implementation details of Hibernate Reactive: - implementation "io.vertx:vertx-sql-client:${vertxSqlClientVersion}" + implementation(libs.io.vertx.vertx.sql.client) // Testing - testImplementation 'org.assertj:assertj-core:3.27.3' - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) // Drivers - testImplementation "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" - testImplementation "io.vertx:vertx-mysql-client:${vertxSqlClientVersion}" - testImplementation "io.vertx:vertx-db2-client:${vertxSqlClientVersion}" - testImplementation "io.vertx:vertx-mssql-client:${vertxSqlClientVersion}" - testImplementation "io.vertx:vertx-oracle-client:${vertxSqlClientVersion}" + testImplementation(libs.io.vertx.vertx.pg.client) + testImplementation(libs.io.vertx.vertx.mysql.client) + testImplementation(libs.io.vertx.vertx.db2.client) + testImplementation(libs.io.vertx.vertx.mssql.client) + testImplementation(libs.io.vertx.vertx.oracle.client) // Metrics - testImplementation "io.vertx:vertx-micrometer-metrics:${vertxSqlClientVersion}" + testImplementation(libs.io.vertx.vertx.micrometer.metrics) // Optional dependency of vertx-pg-client, essential when connecting via SASL SCRAM - testImplementation 'com.ongres.scram:client:2.1' + testImplementation(libs.com.ongres.scram.client) // JUnit Jupiter - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.11.3' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.11.3' + testImplementation(libs.org.junit.jupiter.junit.jupiter.api) + testRuntimeOnly(libs.org.junit.jupiter.junit.jupiter.engine) // JDBC driver to test with ORM and PostgreSQL - testRuntimeOnly "org.postgresql:postgresql:42.7.5" + testRuntimeOnly(libs.org.postgresql.postgresql) // JDBC driver for Testcontainers with MS SQL Server - testRuntimeOnly "com.microsoft.sqlserver:mssql-jdbc:12.10.0.jre11" + testRuntimeOnly(libs.com.microsoft.sqlserver.mssql.jdbc) // JDBC driver for Testcontainers with MariaDB Server - testRuntimeOnly "org.mariadb.jdbc:mariadb-java-client:3.5.3" + testRuntimeOnly(libs.org.mariadb.jdbc.mariadb.java.client) // JDBC driver for Testcontainers with MYSQL Server - testRuntimeOnly "com.mysql:mysql-connector-j:9.3.0" + testRuntimeOnly(libs.com.mysql.mysql.connector.j) // JDBC driver for Db2 server, for testing - testRuntimeOnly "com.ibm.db2:jcc:12.1.0.0" + testRuntimeOnly(libs.com.ibm.db2.jcc) // EHCache - testRuntimeOnly ("org.ehcache:ehcache:3.10.8") { + testRuntimeOnly(libs.org.ehcache.ehcache) { capabilities { requireCapability 'org.ehcache.modules:ehcache-xml-jakarta' } } - testRuntimeOnly ("org.hibernate.orm:hibernate-jcache:${hibernateOrmVersion}") + testRuntimeOnly(libs.org.hibernate.orm.hibernate.jcache) // log4j - testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.20.0' + testRuntimeOnly(libs.org.apache.logging.log4j.log4j.core) // Testcontainers - testImplementation "org.testcontainers:postgresql:${testcontainersVersion}" - testImplementation "org.testcontainers:mysql:${testcontainersVersion}" - testImplementation "org.testcontainers:mariadb:${testcontainersVersion}" - testImplementation "org.testcontainers:db2:${testcontainersVersion}" - testImplementation "org.testcontainers:cockroachdb:${testcontainersVersion}" - testImplementation "org.testcontainers:mssqlserver:${testcontainersVersion}" - testImplementation "org.testcontainers:oracle-xe:${testcontainersVersion}" + testImplementation(libs.org.testcontainers.postgresql) + testImplementation(libs.org.testcontainers.mysql) + testImplementation(libs.org.testcontainers.mariadb) + testImplementation(libs.org.testcontainers.db2) + testImplementation(libs.org.testcontainers.cockroachdb) + testImplementation(libs.org.testcontainers.mssqlserver) + testImplementation(libs.org.testcontainers.oracle.xe) } // Reproducible Builds diff --git a/integration-tests/bytecode-enhancements-it/build.gradle b/integration-tests/bytecode-enhancements-it/build.gradle index 17ffdf10e..cd9a23e52 100644 --- a/integration-tests/bytecode-enhancements-it/build.gradle +++ b/integration-tests/bytecode-enhancements-it/build.gradle @@ -10,37 +10,32 @@ buildscript { } plugins { - id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}" + alias(libs.plugins.org.hibernate.orm) } description = 'Bytecode enhancements integration tests' -ext { - log4jVersion = '2.20.0' - assertjVersion = '3.27.3' -} - dependencies { implementation project(':hibernate-reactive-core') // JPA metamodel generation for criteria queries (optional) - annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}" + annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen) // Testing on one database should be enough - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly(libs.com.ongres.scram.client) // logging - runtimeOnly "org.apache.logging.log4j:log4j-core:${log4jVersion}" + runtimeOnly(libs.org.apache.logging.log4j.log4j.core) // Testcontainers - testImplementation "org.testcontainers:postgresql:${testcontainersVersion}" + testImplementation(libs.org.testcontainers.postgresql) // Testing - testImplementation "org.assertj:assertj-core:${assertjVersion}" - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) } // Optional: enable the bytecode enhancements @@ -113,4 +108,4 @@ tasks.addRule( "Pattern testDb" ) { String taskName -> } } -} \ No newline at end of file +} diff --git a/integration-tests/hibernate-validator-postgres-it/build.gradle b/integration-tests/hibernate-validator-postgres-it/build.gradle index 27689b24d..61500ef81 100644 --- a/integration-tests/hibernate-validator-postgres-it/build.gradle +++ b/integration-tests/hibernate-validator-postgres-it/build.gradle @@ -10,39 +10,34 @@ buildscript { } plugins { - id "org.hibernate.orm" version "${hibernateOrmGradlePluginVersion}" + alias(libs.plugins.org.hibernate.orm) } description = 'Quarkus QE integration tests' -ext { - log4jVersion = '2.20.0' - assertjVersion = '3.27.3' -} - dependencies { implementation project(':hibernate-reactive-core') - implementation "org.hibernate.validator:hibernate-validator:8.0.2.Final" - runtimeOnly 'org.glassfish.expressly:expressly:5.0.0' + implementation(libs.org.hibernate.validator.hibernate.validator) + runtimeOnly(libs.org.glassfish.expressly.expressly) // JPA metamodel generation for criteria queries (optional) - annotationProcessor "org.hibernate.orm:hibernate-jpamodelgen:${hibernateOrmVersion}" + annotationProcessor(libs.org.hibernate.orm.hibernate.jpamodelgen) // Testing on one database should be enough - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // Allow authentication to PostgreSQL using SCRAM: - runtimeOnly 'com.ongres.scram:client:2.1' + runtimeOnly(libs.com.ongres.scram.client) // logging - runtimeOnly "org.apache.logging.log4j:log4j-core:${log4jVersion}" + runtimeOnly(libs.org.apache.logging.log4j.log4j.core) // Testcontainers - testImplementation "org.testcontainers:postgresql:${testcontainersVersion}" + testImplementation(libs.org.testcontainers.postgresql) // Testing - testImplementation "org.assertj:assertj-core:${assertjVersion}" - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) } // Optional: enable the bytecode enhancements @@ -116,4 +111,4 @@ tasks.addRule( "Pattern testDb" ) { String taskName -> } } -} \ No newline at end of file +} diff --git a/integration-tests/techempower-postgres-it/build.gradle b/integration-tests/techempower-postgres-it/build.gradle index 5c4ecda47..edfc4ae4d 100644 --- a/integration-tests/techempower-postgres-it/build.gradle +++ b/integration-tests/techempower-postgres-it/build.gradle @@ -11,37 +11,25 @@ buildscript { description = 'TechEmpower integration tests' -ext { - jacksonDatabindVersion = '2.15.2' - jbossLoggingVersion = '3.5.0.Final' - assertjVersion = '3.27.3' - vertxWebVersion = project.hasProperty( 'vertxWebVersion' ) - ? project.property( 'vertxWebVersion' ) - : vertxSqlClientVersion - vertxWebClientVersion = project.hasProperty( 'vertxWebClientVersion' ) - ? project.property( 'vertxWebClientVersion' ) - : vertxSqlClientVersion -} - dependencies { implementation project( ':hibernate-reactive-core' ) - implementation "io.vertx:vertx-web:${vertxWebVersion}" - implementation "io.vertx:vertx-web-client:${vertxWebClientVersion}" + implementation(libs.io.vertx.vertx.web) + implementation(libs.io.vertx.vertx.web.client) - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // The Pg client requires this dependency - runtimeOnly "com.ongres.scram:client:2.1" - runtimeOnly "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}" + runtimeOnly(libs.com.ongres.scram.client) + runtimeOnly(libs.com.fasterxml.jackson.core.jackson.databind) // logging - implementation "org.jboss.logging:jboss-logging:${jbossLoggingVersion}" + implementation(libs.org.jboss.logging.jboss.logging) // Testcontainers - implementation "org.testcontainers:postgresql:${testcontainersVersion}" + implementation(libs.org.testcontainers.postgresql) // Testing - testImplementation "org.assertj:assertj-core:${assertjVersion}" - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) } // Configuration for the tests diff --git a/integration-tests/verticle-postgres-it/build.gradle b/integration-tests/verticle-postgres-it/build.gradle index 0be234620..2cd6edfec 100644 --- a/integration-tests/verticle-postgres-it/build.gradle +++ b/integration-tests/verticle-postgres-it/build.gradle @@ -11,37 +11,25 @@ buildscript { description = 'Bytecode enhancements integration tests' -ext { - jacksonDatabindVersion = '2.15.2' - jbossLoggingVersion = '3.5.0.Final' - assertjVersion = '3.27.3' - vertxWebVersion = project.hasProperty( 'vertxWebVersion' ) - ? project.property( 'vertxWebVersion' ) - : vertxSqlClientVersion - vertxWebClientVersion = project.hasProperty( 'vertxWebClientVersion' ) - ? project.property( 'vertxWebClientVersion' ) - : vertxSqlClientVersion -} - dependencies { implementation project(':hibernate-reactive-core') - implementation "io.vertx:vertx-web:${vertxWebVersion}" - implementation "io.vertx:vertx-web-client:${vertxWebClientVersion}" + implementation(libs.io.vertx.vertx.web) + implementation(libs.io.vertx.vertx.web.client) - runtimeOnly "io.vertx:vertx-pg-client:${vertxSqlClientVersion}" + runtimeOnly(libs.io.vertx.vertx.pg.client) // The Pg client requires this dependency - runtimeOnly "com.ongres.scram:client:2.1" - runtimeOnly "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}" + runtimeOnly(libs.com.ongres.scram.client) + runtimeOnly(libs.com.fasterxml.jackson.core.jackson.databind) // logging - implementation "org.jboss.logging:jboss-logging:${jbossLoggingVersion}" + implementation(libs.org.jboss.logging.jboss.logging) // Testcontainers - implementation "org.testcontainers:postgresql:${testcontainersVersion}" + implementation(libs.org.testcontainers.postgresql) // Testing - testImplementation "org.assertj:assertj-core:${assertjVersion}" - testImplementation "io.vertx:vertx-junit5:${vertxSqlClientVersion}" + testImplementation(libs.org.assertj.assertj.core) + testImplementation(libs.io.vertx.vertx.junit5) } // Configuration for the tests diff --git a/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java index 36b5ae10a..c34a4b479 100644 --- a/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java +++ b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsPlugin.java @@ -28,6 +28,7 @@ public class VersionsPlugin implements Plugin { public static final String SKIP_ORM_VERSION_PARSING = "skipOrmVersionParsing"; public static final String RELATIVE_FILE = "gradle/version.properties"; + public static final String RELATIVE_CATALOG = "gradle/libs.versions.toml"; @Override public void apply(Project project) { @@ -57,12 +58,13 @@ public void apply(Project project) { project.getLogger().lifecycle( "Development version: n/a" ); } - final String ormVersionString = determineOrmVersion( project ); + final VersionsTomlParser tomlParser = new VersionsTomlParser( RELATIVE_CATALOG ); + final String ormVersionString = determineOrmVersion( project, tomlParser ); final Object ormVersion = resolveOrmVersion( ormVersionString, project ); project.getLogger().lifecycle( "ORM version: {}", ormVersion ); project.getExtensions().add( ORM_VERSION, ormVersion ); - final Object ormPluginVersion = determineOrmPluginVersion( ormVersion, project ); + final Object ormPluginVersion = determineOrmPluginVersion( ormVersion, project, tomlParser ); project.getLogger().lifecycle( "ORM Gradle plugin version: {}", ormPluginVersion ); project.getExtensions().add( ORM_PLUGIN_VERSION, ormPluginVersion ); } @@ -123,10 +125,17 @@ private static void withInputStream(File file, Consumer action) { } } - private String determineOrmVersion(Project project) { + private String determineOrmVersion(Project project, VersionsTomlParser parser) { + // Check if it has been set in the project if ( project.hasProperty( ORM_VERSION ) ) { return (String) project.property( ORM_VERSION ); } + + // Check in the catalog + final String version = parser.read( ORM_VERSION ); + if ( version != null ) { + return version; + } throw new IllegalStateException( "Hibernate ORM version not specified on project" ); } @@ -138,10 +147,18 @@ private Object resolveOrmVersion(String stringForm, Project project) { return new ProjectVersion( stringForm ); } - private Object determineOrmPluginVersion(Object ormVersion, Project project) { + private Object determineOrmPluginVersion(Object ormVersion, Project project, VersionsTomlParser parser) { + // Check if it has been set in the project if ( project.hasProperty( ORM_PLUGIN_VERSION ) ) { return project.property( ORM_PLUGIN_VERSION ); } - return ormVersion; + + // Check in the catalog + final String version = parser.read( ORM_PLUGIN_VERSION ); + if ( version != null ) { + return version; + } + + throw new IllegalStateException( "Hibernate ORM Gradle plugin version not specified on project" ); } } diff --git a/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsTomlParser.java b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsTomlParser.java new file mode 100644 index 000000000..b0adafd6d --- /dev/null +++ b/local-build-plugins/src/main/java/org/hibernate/reactive/env/VersionsTomlParser.java @@ -0,0 +1,68 @@ +package org.hibernate.reactive.env; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Read the versions section of the library catalog + */ +public class VersionsTomlParser { + + private final Map data = new HashMap<>(); + + public VersionsTomlParser(String filePath) { + parse( filePath ); + } + + private void parse(String filePath) { + try ( BufferedReader reader = new BufferedReader( new FileReader( filePath ) ) ) { + String line; + String currentSection = null; + while ( ( line = reader.readLine() ) != null ) { + line = line.trim(); + + // Skip comments and blank lines + if ( line.isEmpty() || line.startsWith( "#" ) ) { + continue; + } + + // Handle [section] + if ( line.startsWith( "[" ) && line.endsWith( "]" ) ) { + currentSection = line.substring( 1, line.length() - 1 ).trim(); + continue; + } + + if ( "versions".equalsIgnoreCase( currentSection ) ) { + // Handle key = value + int equalsIndex = line.indexOf( '=' ); + if ( equalsIndex == -1 ) { + continue; + } + + String key = line.substring( 0, equalsIndex ).trim(); + String value = line.substring( equalsIndex + 1 ).trim(); + + // Remove optional quotes around string values + if ( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) { + value = value.substring( 1, value.length() - 1 ); + } + + data.put( key, value ); + } + } + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + + /** + * Read the value of the property in the versions section of a toml file + */ + public String read(String property) { + return data.get( property ); + } +} diff --git a/settings.gradle b/settings.gradle index e997f3e73..159d91e20 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,6 +19,44 @@ gradle.ext.baselineJavaVersion = JavaLanguageVersion.of( 17 ) // You can't use bytecode higher than what Gradle supports, even with toolchains. def GRADLE_MAX_SUPPORTED_BYTECODE_VERSION = 23 +// This overrides the default version catalog in gradle/libs.versions.toml, which can be +// useful to monitor compatibility for upcoming versions on CI: +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + // ./gradlew build -PhibernateOrmVersion=7.0.2.Final + def hibernateOrmVersion = settings.ext.find("hibernateOrmVersion") ?: "" + if ( hibernateOrmVersion != "" ) { + version("hibernateOrmVersion", hibernateOrmVersion) + } + + // ./gradlew build -PhibernateOrmGradlePluginVersion=7.0.2.Final + def hibernateOrmGradlePluginVersion = settings.ext.find("hibernateOrmGradlePluginVersion") ?: "" + if ( hibernateOrmGradlePluginVersion ) { + version("hibernateOrmGradlePluginVersion", hibernateOrmGradlePluginVersion) + } + + // ./gradlew build -PvertxSqlClientVersion=4.0.0-SNAPSHOT + def vertxSqlClientVersion = settings.ext.find("vertxSqlClientVersion") ?: "" + if ( vertxSqlClientVersion ) { + version("vertxSqlClientVersion", vertxSqlClientVersion) + } + + // ./gradlew build -PvertxWebVersion=4.0.0-SNAPSHOT + def vertxWebVersion = settings.ext.find("vertxWebVersion") ?: vertxSqlClientVersion + if ( vertxWebVersion ) { + version("vertxWebVersion", vertxWebVersion) + } + + // ./gradlew build -PvertxWebClientVersion=4.0.0-SNAPSHOT + def vertxWebClientVersion = settings.ext.find("vertxWebClientVersion") ?: vertxSqlClientVersion + if ( vertxWebClientVersion ) { + version("vertxWebClientVersion", vertxWebClientVersion) + } + } + } +} + // If either 'main.jdk.version' or 'test.jdk.version' is set, enable the toolchain and use the selected jdk. // If only one property is set, the other defaults to the baseline Java version (8). // Note that when toolchain is enabled, you also need to specify From 9c2f75724afdd252c43fd1904eb9f4e4124fa996 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 13 Jul 2025 01:02:45 +0000 Subject: [PATCH 033/185] Update project version to : `3.0.4.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 4fc6321a8..88292cf88 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.4-SNAPSHOT \ No newline at end of file +projectVersion=3.0.4.Final \ No newline at end of file From 6adb5c8d582d692714fcb829c59ba101000a3799 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 13 Jul 2025 01:03:41 +0000 Subject: [PATCH 034/185] Update project version to : `3.0.5-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 88292cf88..1b8ea940d 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.4.Final \ No newline at end of file +projectVersion=3.0.5-SNAPSHOT \ No newline at end of file From c4054a94602b396e7434ef418ce566c8f6714980 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 10 Jul 2025 14:05:15 +0200 Subject: [PATCH 035/185] [#2269] chore: Fix imports in MutinySessionTest --- .../java/org/hibernate/reactive/MutinySessionTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java index bf46f835a..31bcf2380 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java @@ -27,7 +27,13 @@ import jakarta.persistence.metamodel.EntityType; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; @Timeout(value = 10, timeUnit = MINUTES) From 54671cdb5d94eb464df4a03834c00b54d3c96be9 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 9 Jul 2025 15:10:08 +0200 Subject: [PATCH 036/185] [#2269] chore: Upgrade Testcontainers to 1.21.3 --- gradle/libs.versions.toml | 2 +- tooling/jbang/CockroachDBReactiveTest.java.qute | 2 +- tooling/jbang/Db2ReactiveTest.java.qute | 2 +- tooling/jbang/MariaDBReactiveTest.java.qute | 2 +- tooling/jbang/MySQLReactiveTest.java.qute | 2 +- tooling/jbang/PostgreSQLReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 10 +++++----- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 816a2efb5..99a98132a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" junitVersion = "5.11.3" log4jVersion = "2.20.0" -testcontainersVersion = "1.21.0" +testcontainersVersion = "1.21.3" vertxSqlClientVersion = "4.5.16" vertxWebVersion= "4.5.16" vertxWebClientVersion = "4.5.16" diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute index 337916470..54996e38d 100755 --- a/tooling/jbang/CockroachDBReactiveTest.java.qute +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:cockroachdb:1.21.0 +//DEPS org.testcontainers:cockroachdb:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute index cff1b8101..ed3c993d1 100755 --- a/tooling/jbang/Db2ReactiveTest.java.qute +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:db2:1.21.0 +//DEPS org.testcontainers:db2:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 import jakarta.persistence.Entity; diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute index 3a9698fa1..611f142c0 100755 --- a/tooling/jbang/MariaDBReactiveTest.java.qute +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:mariadb:1.21.0 +//DEPS org.testcontainers:mariadb:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index 1e6debefc..473fad721 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:mysql:1.21.0 +//DEPS org.testcontainers:mysql:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 //// Testcontainer needs the JDBC drivers to start the container diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute index ec4ecee00..1e6e094d5 100755 --- a/tooling/jbang/PostgreSQLReactiveTest.java.qute +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -10,7 +10,7 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:postgresql:1.21.0 +//DEPS org.testcontainers:postgresql:1.21.3 //DEPS org.slf4j:slf4j-simple:2.0.7 //DESCRIPTION Allow authentication to PostgreSQL using SCRAM: //DEPS com.ongres.scram:client:2.1 diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index c0db48f2b..2d66ca368 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -13,11 +13,11 @@ //DEPS org.hibernate.reactive:hibernate-reactive-core:${hibernate-reactive.version:3.0.0.Final} //DEPS org.assertj:assertj-core:3.27.3 //DEPS junit:junit:4.13.2 -//DEPS org.testcontainers:postgresql:1.21.0 -//DEPS org.testcontainers:mysql:1.21.0 -//DEPS org.testcontainers:db2:1.21.0 -//DEPS org.testcontainers:mariadb:1.21.0 -//DEPS org.testcontainers:cockroachdb:1.21.0 +//DEPS org.testcontainers:postgresql:1.21.3 +//DEPS org.testcontainers:mysql:1.21.3 +//DEPS org.testcontainers:db2:1.21.3 +//DEPS org.testcontainers:mariadb:1.21.3 +//DEPS org.testcontainers:cockroachdb:1.21.3 // //// Testcontainer needs the JDBC drivers to start the containers //// Hibernate Reactive doesn't use them From 71d60d11dc447d01c8a1a77c804378890eb99c44 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 10 Jul 2025 17:00:11 +0200 Subject: [PATCH 037/185] [#2269] Keep containers image and version in docker files We are trying to achieve two things: * Make it possible for dependabot to upgrade the containers automatically * Collect the image and version of the containers we use for testing in one place Note that the test suite will still create and start the containers programmatically, but it will read the first FROM line in each Dockerfile to extract the image and version to use. It will ignore everything else. My initial plan was to configure each container using the Dockerfile directly, but I prefer to reuse the exsisting Testcontainers classes for each database (for example, PostgreSQLContainer) because they contain out-of-the-box configuration that I would need to copy somewhere else. In any case, this is a good starting point and we can improve it later. --- build.gradle | 6 + .../containers/CockroachDBDatabase.java | 4 +- .../reactive/containers/DB2Database.java | 4 +- .../reactive/containers/DockerImage.java | 103 ++++++++++++++- .../containers/MSSQLServerDatabase.java | 4 +- .../reactive/containers/MariaDatabase.java | 4 +- .../reactive/containers/MySQLDatabase.java | 4 +- .../reactive/containers/OracleDatabase.java | 6 +- .../containers/PostgreSQLDatabase.java | 8 +- .../hibernate/reactive/it/BaseReactiveIT.java | 4 +- .../hibernate/reactive/it/DockerImage.java | 125 ++++++++++++++++++ .../quarkus/qe/database/BaseReactiveIT.java | 4 +- .../it/quarkus/qe/database/DockerImage.java | 125 ++++++++++++++++++ .../reactive/it/verticle/DockerImage.java | 125 ++++++++++++++++++ .../reactive/it/verticle/VertxServer.java | 4 +- tooling/docker/README.md | 6 + tooling/docker/cockroachdb.Dockerfile | 3 + tooling/docker/db2.Dockerfile | 3 + tooling/docker/maria.Dockerfile | 3 + tooling/docker/mysql.Dockerfile | 3 + tooling/docker/oracle.Dockerfile | 3 + tooling/docker/postgresql.Dockerfile | 3 + tooling/docker/sqlserver.Dockerfile | 3 + 23 files changed, 528 insertions(+), 29 deletions(-) create mode 100644 integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java create mode 100644 integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java create mode 100644 integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java create mode 100644 tooling/docker/README.md create mode 100644 tooling/docker/cockroachdb.Dockerfile create mode 100644 tooling/docker/db2.Dockerfile create mode 100644 tooling/docker/maria.Dockerfile create mode 100644 tooling/docker/mysql.Dockerfile create mode 100644 tooling/docker/oracle.Dockerfile create mode 100644 tooling/docker/postgresql.Dockerfile create mode 100644 tooling/docker/sqlserver.Dockerfile diff --git a/build.gradle b/build.gradle index 532d932ef..2615b335d 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,12 @@ subprojects { options.encoding = 'UTF-8' } + // Configure test tasks for all subprojects + tasks.withType( Test ).configureEach { + // Set the project root for finding Docker files - available to all modules + systemProperty 'hibernate.reactive.project.root', rootProject.projectDir.absolutePath + } + if ( !gradle.ext.javaToolchainEnabled ) { sourceCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion ) targetCompatibility = JavaVersion.toVersion( gradle.ext.baselineJavaVersion ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java index e1a0928d1..172ace2bc 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/CockroachDBDatabase.java @@ -12,7 +12,7 @@ import org.testcontainers.containers.CockroachContainer; import org.testcontainers.containers.Container; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; class CockroachDBDatabase extends PostgreSQLDatabase { @@ -25,7 +25,7 @@ class CockroachDBDatabase extends PostgreSQLDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final CockroachContainer cockroachDb = new CockroachContainer( imageName( "cockroachdb/cockroach", "v24.3.13" ) ) + public static final CockroachContainer cockroachDb = new CockroachContainer( fromDockerfile( "cockroachdb" ) ) // Username, password and database are not supported by test container at the moment // Testcontainers will use a database named 'postgres' and the 'root' user .withReuse( true ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java index dfeeaf15c..c2b60e3d6 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DB2Database.java @@ -28,7 +28,7 @@ import org.testcontainers.containers.Db2Container; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; class DB2Database implements TestableDatabase { @@ -87,7 +87,7 @@ class DB2Database implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - static final Db2Container db2 = new Db2Container( imageName( "icr.io", "db2_community/db2", "12.1.0.0" ) ) + static final Db2Container db2 = new Db2Container( fromDockerfile( "db2" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java index 1b2f3f6a7..e8f40f34d 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/DockerImage.java @@ -5,10 +5,17 @@ */ package org.hibernate.reactive.containers; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + import org.testcontainers.utility.DockerImageName; + /** - * A utility class with methods to generate {@link DockerImageName} for testcontainers. + * A utility class with methods to generate a {@link DockerImageName} for Testcontainers. *

* Testcontainers might not work if the image required is available in multiple different registries (for example when * using podman instead of docker). @@ -17,10 +24,28 @@ */ public final class DockerImage { - public static final String DEFAULT_REGISTRY = "docker.io"; + /** + * The absolute path of the project root that we have set in Gradle. + */ + private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" ); + + /** + * The path to the directory containing all the Dockerfile files + */ + private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" ); - public static DockerImageName imageName(String image, String version) { - return imageName( DEFAULT_REGISTRY, image, version ); + /** + * Extract the image name and version from the first FROM instruction in the Dockerfile. + * Note that everything else is ignored. + */ + public static DockerImageName fromDockerfile(String databaseName) { + try { + final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() ); + return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } } public static DockerImageName imageName(String registry, String image, String version) { @@ -28,4 +53,74 @@ public static DockerImageName imageName(String registry, String image, String ve .parse( registry + "/" + image + ":" + version ) .asCompatibleSubstituteFor( image ); } + + private static class ImageInformation { + private final String registry; + private final String image; + private final String version; + + public ImageInformation(String fullImageInfo) { + // FullImageInfo pattern: /: + // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13" + final int registryEndPos = fullImageInfo.indexOf( '/' ); + final int imageEndPos = fullImageInfo.lastIndexOf( ':' ); + this.registry = fullImageInfo.substring( 0, registryEndPos ); + this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos ); + this.version = fullImageInfo.substring( imageEndPos + 1 ); + } + + public String getRegistry() { + return registry; + } + + public String getImage() { + return image; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return registry + "/" + image + ":" + version; + } + } + + private static Path dockerFilePath(String database) { + // Get project root from system property set by Gradle, with fallback + return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" ); + } + + private static ImageInformation readFromInstruction(String database) throws IOException { + return readFromInstruction( dockerFilePath( database ) ); + } + + /** + * Read a Dockerfile and extract the first FROM instruction. + * + * @param dockerfilePath path to the Dockerfile + * @return the first FROM instruction found, or empty if none found + * @throws IOException if the file cannot be read + */ + private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException { + if ( !Files.exists( dockerfilePath ) ) { + throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath ); + } + + List lines = Files.readAllLines( dockerfilePath ); + for ( String line : lines ) { + // Skip comments and empty lines + String trimmedLine = line.trim(); + if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) { + continue; + } + + if ( trimmedLine.startsWith( "FROM " ) ) { + return new ImageInformation( trimmedLine.substring( "FROM ".length() ) ); + } + } + + throw new IOException( " Missing FROM instruction in " + dockerfilePath ); + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java index aeb1b8fb0..eaab2e8fb 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MSSQLServerDatabase.java @@ -28,7 +28,7 @@ import org.testcontainers.containers.MSSQLServerContainer; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; /** * The JDBC driver syntax is: @@ -96,7 +96,7 @@ class MSSQLServerDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MSSQLServerContainer mssqlserver = new MSSQLServerContainer<>( imageName( "mcr.microsoft.com", "mssql/server", "2022-latest" ) ) + public static final MSSQLServerContainer mssqlserver = new MSSQLServerContainer<>( fromDockerfile( "sqlserver" ) ) .acceptLicense() .withPassword( PASSWORD ) .withReuse( true ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java index 16a5f97ff..14d60c264 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MariaDatabase.java @@ -13,7 +13,7 @@ import org.testcontainers.containers.MariaDBContainer; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; class MariaDatabase extends MySQLDatabase { @@ -36,7 +36,7 @@ class MariaDatabase extends MySQLDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MariaDBContainer maria = new MariaDBContainer<>( imageName( "mariadb", "11.7.2" ) ) + public static final MariaDBContainer maria = new MariaDBContainer<>( fromDockerfile( "maria" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java index e0aa9ef3a..f5cd8f7c7 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java @@ -5,7 +5,7 @@ */ package org.hibernate.reactive.containers; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; import java.io.Serializable; import java.math.BigDecimal; @@ -87,7 +87,7 @@ class MySQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MySQLContainer mysql = new MySQLContainer<>( imageName( "mysql", "9.2.0") ) + public static final MySQLContainer mysql = new MySQLContainer<>( fromDockerfile( "mysql" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java index d57f9103a..bbde95e6c 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java @@ -29,7 +29,7 @@ import org.testcontainers.containers.OracleContainer; -import static org.hibernate.reactive.containers.DockerImage.imageName; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; /** * Connection string for Oracle thin should be something like: @@ -88,9 +88,7 @@ class OracleDatabase implements TestableDatabase { } } - public static final OracleContainer oracle = new OracleContainer( - imageName( "gvenzl/oracle-free", "23-slim-faststart" ) - .asCompatibleSubstituteFor( "gvenzl/oracle-xe" ) ) + public static final OracleContainer oracle = new OracleContainer( fromDockerfile( "oracle" ).asCompatibleSubstituteFor( "gvenzl/oracle-xe" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java index 56ef4f878..f6a974ba2 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/PostgreSQLDatabase.java @@ -5,8 +5,6 @@ */ package org.hibernate.reactive.containers; -import static org.hibernate.reactive.containers.DockerImage.imageName; - import java.io.Serializable; import java.math.BigDecimal; import java.math.BigInteger; @@ -30,9 +28,11 @@ import org.testcontainers.containers.PostgreSQLContainer; +import static org.hibernate.reactive.containers.DockerImage.fromDockerfile; + class PostgreSQLDatabase implements TestableDatabase { - public static PostgreSQLDatabase INSTANCE = new PostgreSQLDatabase(); + public static final PostgreSQLDatabase INSTANCE = new PostgreSQLDatabase(); private static Map, String> expectedDBTypeForClass = new HashMap<>(); @@ -87,7 +87,7 @@ class PostgreSQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( imageName( "postgres", "17.5" ) ) + public static final PostgreSQLContainer postgresql = new PostgreSQLContainer<>( fromDockerfile( "postgresql" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java index 2388cb421..8f6081a8a 100644 --- a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/BaseReactiveIT.java @@ -52,9 +52,7 @@ public abstract class BaseReactiveIT { // These properties are in DatabaseConfiguration in core public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); - public static final DockerImageName IMAGE_NAME = DockerImageName - .parse( "docker.io/postgres:17.5" ) - .asCompatibleSubstituteFor( "postgres" ); + public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" ); public static final String USERNAME = "hreact"; public static final String PASSWORD = "hreact"; diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java new file mode 100644 index 000000000..b5621249d --- /dev/null +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/DockerImage.java @@ -0,0 +1,125 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.it; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.testcontainers.utility.DockerImageName; + +/** + * A utility class with methods to generate a {@link DockerImageName} for Testcontainers. + *

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

+ */ +public final class DockerImage { + + /** + * The absolute path of the project root that we have set in Gradle. + */ + private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" ); + + /** + * The path to the directory containing all the Dockerfile files + */ + private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" ); + + /** + * Extract the image name and version from the first FROM instruction in the Dockerfile. + * Note that everything else is ignored. + */ + public static DockerImageName fromDockerfile(String databaseName) { + try { + final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() ); + return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + + public static DockerImageName imageName(String registry, String image, String version) { + return DockerImageName + .parse( registry + "/" + image + ":" + version ) + .asCompatibleSubstituteFor( image ); + } + + private static class ImageInformation { + private final String registry; + private final String image; + private final String version; + + public ImageInformation(String fullImageInfo) { + // FullImageInfo pattern: /: + // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13" + final int registryEndPos = fullImageInfo.indexOf( '/' ); + final int imageEndPos = fullImageInfo.lastIndexOf( ':' ); + this.registry = fullImageInfo.substring( 0, registryEndPos ); + this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos ); + this.version = fullImageInfo.substring( imageEndPos + 1 ); + } + + public String getRegistry() { + return registry; + } + + public String getImage() { + return image; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return registry + "/" + image + ":" + version; + } + } + + private static Path dockerFilePath(String database) { + // Get project root from system property set by Gradle, with fallback + return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" ); + } + + private static ImageInformation readFromInstruction(String database) throws IOException { + return readFromInstruction( dockerFilePath( database ) ); + } + + /** + * Read a Dockerfile and extract the first FROM instruction. + * + * @param dockerfilePath path to the Dockerfile + * @return the first FROM instruction found, or empty if none found + * @throws IOException if the file cannot be read + */ + private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException { + if ( !Files.exists( dockerfilePath ) ) { + throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath ); + } + + List lines = Files.readAllLines( dockerfilePath ); + for ( String line : lines ) { + // Skip comments and empty lines + String trimmedLine = line.trim(); + if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) { + continue; + } + + if ( trimmedLine.startsWith( "FROM " ) ) { + return new ImageInformation( trimmedLine.substring( "FROM ".length() ) ); + } + } + + throw new IOException( " Missing FROM instruction in " + dockerfilePath ); + } +} diff --git a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java index 7debdbb2c..fcefdaa84 100644 --- a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java +++ b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/BaseReactiveIT.java @@ -52,9 +52,7 @@ public abstract class BaseReactiveIT { // These properties are in DatabaseConfiguration in core public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); - public static final DockerImageName IMAGE_NAME = DockerImageName - .parse( "docker.io/postgres:17.5" ) - .asCompatibleSubstituteFor( "postgres" ); + public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" ); public static final String USERNAME = "hreact"; public static final String PASSWORD = "hreact"; diff --git a/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java new file mode 100644 index 000000000..7770da76c --- /dev/null +++ b/integration-tests/hibernate-validator-postgres-it/src/test/java/org/hibernate/reactive/it/quarkus/qe/database/DockerImage.java @@ -0,0 +1,125 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.it.quarkus.qe.database; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.testcontainers.utility.DockerImageName; + +/** + * A utility class with methods to generate a {@link DockerImageName} for Testcontainers. + *

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

+ */ +public final class DockerImage { + + /** + * The absolute path of the project root that we have set in Gradle. + */ + private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" ); + + /** + * The path to the directory containing all the Dockerfile files + */ + private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" ); + + /** + * Extract the image name and version from the first FROM instruction in the Dockerfile. + * Note that everything else is ignored. + */ + public static DockerImageName fromDockerfile(String databaseName) { + try { + final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() ); + return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + + public static DockerImageName imageName(String registry, String image, String version) { + return DockerImageName + .parse( registry + "/" + image + ":" + version ) + .asCompatibleSubstituteFor( image ); + } + + private static class ImageInformation { + private final String registry; + private final String image; + private final String version; + + public ImageInformation(String fullImageInfo) { + // FullImageInfo pattern: /: + // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13" + final int registryEndPos = fullImageInfo.indexOf( '/' ); + final int imageEndPos = fullImageInfo.lastIndexOf( ':' ); + this.registry = fullImageInfo.substring( 0, registryEndPos ); + this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos ); + this.version = fullImageInfo.substring( imageEndPos + 1 ); + } + + public String getRegistry() { + return registry; + } + + public String getImage() { + return image; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return registry + "/" + image + ":" + version; + } + } + + private static Path dockerFilePath(String database) { + // Get project root from system property set by Gradle, with fallback + return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" ); + } + + private static ImageInformation readFromInstruction(String database) throws IOException { + return readFromInstruction( dockerFilePath( database ) ); + } + + /** + * Read a Dockerfile and extract the first FROM instruction. + * + * @param dockerfilePath path to the Dockerfile + * @return the first FROM instruction found, or empty if none found + * @throws IOException if the file cannot be read + */ + private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException { + if ( !Files.exists( dockerfilePath ) ) { + throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath ); + } + + List lines = Files.readAllLines( dockerfilePath ); + for ( String line : lines ) { + // Skip comments and empty lines + String trimmedLine = line.trim(); + if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) { + continue; + } + + if ( trimmedLine.startsWith( "FROM " ) ) { + return new ImageInformation( trimmedLine.substring( "FROM ".length() ) ); + } + } + + throw new IOException( " Missing FROM instruction in " + dockerfilePath ); + } +} diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java new file mode 100644 index 000000000..10026d020 --- /dev/null +++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/DockerImage.java @@ -0,0 +1,125 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.it.verticle; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.testcontainers.utility.DockerImageName; + +/** + * A utility class with methods to generate a {@link DockerImageName} for Testcontainers. + *

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

+ */ +public final class DockerImage { + + /** + * The absolute path of the project root that we have set in Gradle. + */ + private static final String PROJECT_ROOT = System.getProperty( "hibernate.reactive.project.root" ); + + /** + * The path to the directory containing all the Dockerfile files + */ + private static final Path DOCKERFILE_DIR_PATH = Path.of( PROJECT_ROOT ).resolve( "tooling" ).resolve( "docker" ); + + /** + * Extract the image name and version from the first FROM instruction in the Dockerfile. + * Note that everything else is ignored. + */ + public static DockerImageName fromDockerfile(String databaseName) { + try { + final ImageInformation imageInformation = readFromInstruction( databaseName.toLowerCase() ); + return imageName( imageInformation.getRegistry(), imageInformation.getImage(), imageInformation.getVersion() ); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + + public static DockerImageName imageName(String registry, String image, String version) { + return DockerImageName + .parse( registry + "/" + image + ":" + version ) + .asCompatibleSubstituteFor( image ); + } + + private static class ImageInformation { + private final String registry; + private final String image; + private final String version; + + public ImageInformation(String fullImageInfo) { + // FullImageInfo pattern: /: + // For example: "docker.io/cockroachdb/cockroach:v24.3.13" becomes registry = "docker.io", image = "cockroachdb/cockroach", version = "v24.3.13" + final int registryEndPos = fullImageInfo.indexOf( '/' ); + final int imageEndPos = fullImageInfo.lastIndexOf( ':' ); + this.registry = fullImageInfo.substring( 0, registryEndPos ); + this.image = fullImageInfo.substring( registryEndPos + 1, imageEndPos ); + this.version = fullImageInfo.substring( imageEndPos + 1 ); + } + + public String getRegistry() { + return registry; + } + + public String getImage() { + return image; + } + + public String getVersion() { + return version; + } + + @Override + public String toString() { + return registry + "/" + image + ":" + version; + } + } + + private static Path dockerFilePath(String database) { + // Get project root from system property set by Gradle, with fallback + return DOCKERFILE_DIR_PATH.resolve( database.toLowerCase() + ".Dockerfile" ); + } + + private static ImageInformation readFromInstruction(String database) throws IOException { + return readFromInstruction( dockerFilePath( database ) ); + } + + /** + * Read a Dockerfile and extract the first FROM instruction. + * + * @param dockerfilePath path to the Dockerfile + * @return the first FROM instruction found, or empty if none found + * @throws IOException if the file cannot be read + */ + private static ImageInformation readFromInstruction(Path dockerfilePath) throws IOException { + if ( !Files.exists( dockerfilePath ) ) { + throw new FileNotFoundException( "Dockerfile not found: " + dockerfilePath ); + } + + List lines = Files.readAllLines( dockerfilePath ); + for ( String line : lines ) { + // Skip comments and empty lines + String trimmedLine = line.trim(); + if ( trimmedLine.isEmpty() || trimmedLine.startsWith( "#" ) ) { + continue; + } + + if ( trimmedLine.startsWith( "FROM " ) ) { + return new ImageInformation( trimmedLine.substring( "FROM ".length() ) ); + } + } + + throw new IOException( " Missing FROM instruction in " + dockerfilePath ); + } +} diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java index 990fef754..f11254c6d 100644 --- a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java +++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/VertxServer.java @@ -21,6 +21,7 @@ import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; @@ -36,7 +37,8 @@ public class VertxServer { // These properties are in DatabaseConfiguration in core public static final boolean USE_DOCKER = Boolean.getBoolean( "docker" ); - public static final String IMAGE_NAME = "postgres:17.5"; + public static final DockerImageName IMAGE_NAME = DockerImage.fromDockerfile( "postgresql" ); + public static final String USERNAME = "hreact"; public static final String PASSWORD = "hreact"; public static final String DB_NAME = "hreact"; diff --git a/tooling/docker/README.md b/tooling/docker/README.md new file mode 100644 index 000000000..4de2451e0 --- /dev/null +++ b/tooling/docker/README.md @@ -0,0 +1,6 @@ +Our test suite will only read the first FROM instruction from each Dockerfile to extract the base image and the version +of the container to run. It will ignore everything else. + +The reason we have these files is that we want to automate the upgrade of the containers using dependabot. + +See the class `DockerImage`. diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile new file mode 100644 index 000000000..a4710ad5b --- /dev/null +++ b/tooling/docker/cockroachdb.Dockerfile @@ -0,0 +1,3 @@ +# CockroachDB +# See https://hub.docker.com/r/cockroachdb/cockroach +FROM docker.io/cockroachdb/cockroach:v24.3.13 diff --git a/tooling/docker/db2.Dockerfile b/tooling/docker/db2.Dockerfile new file mode 100644 index 000000000..1ccb34906 --- /dev/null +++ b/tooling/docker/db2.Dockerfile @@ -0,0 +1,3 @@ +# IBM DB2 +# See https://hub.docker.com/r/ibmcom/db2 +FROM icr.io/db2_community/db2:12.1.0.0 diff --git a/tooling/docker/maria.Dockerfile b/tooling/docker/maria.Dockerfile new file mode 100644 index 000000000..4ccb397c8 --- /dev/null +++ b/tooling/docker/maria.Dockerfile @@ -0,0 +1,3 @@ +# MariaDB +# See https://hub.docker.com/_/mariadb +FROM docker.io/mariadb:11.7.2 diff --git a/tooling/docker/mysql.Dockerfile b/tooling/docker/mysql.Dockerfile new file mode 100644 index 000000000..966350a87 --- /dev/null +++ b/tooling/docker/mysql.Dockerfile @@ -0,0 +1,3 @@ +# MySQL +# See https://hub.docker.com/_/mysql +FROM docker.io/mysql:9.2.0 diff --git a/tooling/docker/oracle.Dockerfile b/tooling/docker/oracle.Dockerfile new file mode 100644 index 000000000..7dfc6447d --- /dev/null +++ b/tooling/docker/oracle.Dockerfile @@ -0,0 +1,3 @@ +# Oracle Database Free +# See https://hub.docker.com/r/gvenzl/oracle-free +FROM docker.io/gvenzl/oracle-free:23-slim-faststart diff --git a/tooling/docker/postgresql.Dockerfile b/tooling/docker/postgresql.Dockerfile new file mode 100644 index 000000000..fb36f48e8 --- /dev/null +++ b/tooling/docker/postgresql.Dockerfile @@ -0,0 +1,3 @@ +# PostgreSQL +# See https://hub.docker.com/_/postgres +FROM docker.io/postgres:17.5 diff --git a/tooling/docker/sqlserver.Dockerfile b/tooling/docker/sqlserver.Dockerfile new file mode 100644 index 000000000..b4fb34f2f --- /dev/null +++ b/tooling/docker/sqlserver.Dockerfile @@ -0,0 +1,3 @@ +# Microsoft SQL Server +# See https://hub.docker.com/_/microsoft-mssql-server +FROM mcr.microsoft.com/mssql/server:2022-latest From a1d6e7e2f298822c5ffd5d62f31ae167ddf15d91 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 11 Jul 2025 16:29:19 +0200 Subject: [PATCH 038/185] [#1136] Update dependabot configuration Includes: * Update project dependencies * Update images in Dockerfile files under tooling/docker/ * Update images we use as services in the GitHub workflow (mysql and postgres to test the examples) --- .github/dependabot.yml | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4d1c7e327..0d4bc0776 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,3 +16,46 @@ updates: allow: - dependency-name: "actions/*" - dependency-name: "redhat-actions/*" + + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "weekly" + day: "tuesday" + open-pull-requests-limit: 20 + groups: + hibernate: + patterns: + - "org.hibernate*" + vertx: + patterns: + - "io.vertx*" + mutiny: + patterns: + - "io.smallrye.reactive*" + # Testcontainers plus the JDBC driver we need for testing + testcontainers: + patterns: + - "org.testcontainers*" + - "com.ibm.db2*" + - "com.microsoft.sqlserver*" + - "org.postgresql*" + - "con.ongres.scram*" + - "com.fasterxml.jackson.core*" + - "com.mysql*" + - "org.mariadb.jdbc*" + + ignore: + # Only patches for Hibernate ORM and Vert.x + - dependency-name: "org.hibernate*" + update-types: ["version-update:semver-major", "version-update:semver-minor"] + - dependency-name: "io.vertx*" + update-types: ["version-update:semver-major", "version-update:semver-minor"] + + # Dockerfiles in tooling/docker/, and database services we use for examples (MySQL and PostgreSQL) + - package-ecosystem: "docker" + directory: "/tooling/docker" + schedule: + interval: "weekly" + allow: + - dependency-type: "all" From d9aab01a5dfe57bb8aaf09871df7dc68d400fca0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:59:23 +0000 Subject: [PATCH 039/185] Bump org.asciidoctor.jvm.convert from 4.0.2 to 4.0.4 Bumps org.asciidoctor.jvm.convert from 4.0.2 to 4.0.4. --- updated-dependencies: - dependency-name: org.asciidoctor.jvm.convert dependency-version: 4.0.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 99a98132a..deae4abc6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,5 +55,5 @@ org-testcontainers-postgresql = { group = "org.testcontainers", name = "postgres [plugins] com-diffplug-spotless = { id = "com.diffplug.spotless", version = "6.25.0" } io-github-gradle-nexus-publish-plugin = { id = "io.github.gradle-nexus.publish-plugin", version = "1.3.0" } -org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.2" } +org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.4" } org-hibernate-orm = { id = "org.hibernate.orm", version.ref = "hibernateOrmGradlePluginVersion" } From 8749a0088e1b0e9a14dff6cad57c811eb62b4724 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:08:18 +0000 Subject: [PATCH 040/185] Bump the testcontainers group with 5 updates Bumps the testcontainers group with 5 updates: | Package | From | To | | --- | --- | --- | | [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson) | `2.15.2` | `2.19.1` | | com.ibm.db2:jcc | `12.1.0.0` | `12.1.2.0` | | [com.microsoft.sqlserver:mssql-jdbc](https://github.com/Microsoft/mssql-jdbc) | `12.10.0.jre11` | `13.1.0.jre11-preview` | | [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) | `3.5.3` | `3.5.4` | | [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) | `42.7.5` | `42.7.7` | Updates `com.fasterxml.jackson.core:jackson-databind` from 2.15.2 to 2.19.1 - [Commits](https://github.com/FasterXML/jackson/commits) Updates `com.ibm.db2:jcc` from 12.1.0.0 to 12.1.2.0 Updates `com.microsoft.sqlserver:mssql-jdbc` from 12.10.0.jre11 to 13.1.0.jre11-preview - [Release notes](https://github.com/Microsoft/mssql-jdbc/releases) - [Changelog](https://github.com/microsoft/mssql-jdbc/blob/main/CHANGELOG.md) - [Commits](https://github.com/Microsoft/mssql-jdbc/commits) Updates `org.mariadb.jdbc:mariadb-java-client` from 3.5.3 to 3.5.4 - [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases) - [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/main/CHANGELOG.md) - [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.5.3...3.5.4) Updates `org.postgresql:postgresql` from 42.7.5 to 42.7.7 - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.5...REL42.7.7) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.19.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: testcontainers - dependency-name: com.ibm.db2:jcc dependency-version: 12.1.2.0 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers - dependency-name: com.microsoft.sqlserver:mssql-jdbc dependency-version: 13.1.0.jre11-preview dependency-type: direct:production update-type: version-update:semver-major dependency-group: testcontainers - dependency-name: org.mariadb.jdbc:mariadb-java-client dependency-version: 3.5.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers - dependency-name: org.postgresql:postgresql dependency-version: 42.7.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index deae4abc6..c8b9b78a7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ assertjVersion = "3.27.3" hibernateOrmVersion = "7.0.2.Final" hibernateOrmGradlePluginVersion = "7.0.2.Final" -jacksonDatabindVersion = "2.15.2" +jacksonDatabindVersion = "2.19.1" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" junitVersion = "5.11.3" @@ -14,8 +14,8 @@ vertxWebClientVersion = "4.5.16" [libraries] com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } -com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.0.0" } -com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "12.10.0.jre11" } +com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" } +com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.1.0.jre11-preview" } com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.3.0" } com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" } io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.0" } @@ -42,8 +42,8 @@ org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", nam org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" } org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" } org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" } -org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.3" } -org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.5" } +org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.4" } +org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.7" } org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" } org-testcontainers-db2 = { group = "org.testcontainers", name = "db2", version.ref = "testcontainersVersion" } org-testcontainers-mariadb = { group = "org.testcontainers", name = "mariadb", version.ref = "testcontainersVersion" } From 6e5b7ad43119e547afd59b4fec299a9e94b42d9f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:59:32 +0000 Subject: [PATCH 041/185] Bump org.apache.logging.log4j:log4j-core from 2.20.0 to 2.25.1 Bumps org.apache.logging.log4j:log4j-core from 2.20.0 to 2.25.1. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8b9b78a7..e40e21405 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ jacksonDatabindVersion = "2.19.1" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" junitVersion = "5.11.3" -log4jVersion = "2.20.0" +log4jVersion = "2.25.1" testcontainersVersion = "1.21.3" vertxSqlClientVersion = "4.5.16" vertxWebVersion= "4.5.16" From 5964143cbe7b677485817a215ac42cd61b35604b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:17:47 +0000 Subject: [PATCH 042/185] Bump cockroachdb/cockroach from v24.3.13 to v25.2.2 in /tooling/docker Bumps cockroachdb/cockroach from v24.3.13 to v25.2.2. --- updated-dependencies: - dependency-name: cockroachdb/cockroach dependency-version: v25.2.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- tooling/docker/cockroachdb.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile index a4710ad5b..5f92dd720 100644 --- a/tooling/docker/cockroachdb.Dockerfile +++ b/tooling/docker/cockroachdb.Dockerfile @@ -1,3 +1,3 @@ # CockroachDB # See https://hub.docker.com/r/cockroachdb/cockroach -FROM docker.io/cockroachdb/cockroach:v24.3.13 +FROM docker.io/cockroachdb/cockroach:v25.2.2 From 7ebf15f4cdd0a6ba72a1410fffb3ea42f985f534 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:18:48 +0000 Subject: [PATCH 043/185] Bump db2_community/db2 from 12.1.0.0 to 12.1.2.0 in /tooling/docker Bumps db2_community/db2 from 12.1.0.0 to 12.1.2.0. --- updated-dependencies: - dependency-name: db2_community/db2 dependency-version: 12.1.2.0 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tooling/docker/db2.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/db2.Dockerfile b/tooling/docker/db2.Dockerfile index 1ccb34906..b70ff8a80 100644 --- a/tooling/docker/db2.Dockerfile +++ b/tooling/docker/db2.Dockerfile @@ -1,3 +1,3 @@ # IBM DB2 # See https://hub.docker.com/r/ibmcom/db2 -FROM icr.io/db2_community/db2:12.1.0.0 +FROM icr.io/db2_community/db2:12.1.2.0 From 3a6507fbc68d2cb241be828d3a12ac9818006d0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:34:30 +0000 Subject: [PATCH 044/185] Bump com.diffplug.spotless from 6.25.0 to 7.1.0 Bumps com.diffplug.spotless from 6.25.0 to 7.1.0. --- updated-dependencies: - dependency-name: com.diffplug.spotless dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e40e21405..f5e37470f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,7 +53,7 @@ org-testcontainers-oracle-xe = { group = "org.testcontainers", name = "oracle-xe org-testcontainers-postgresql = { group = "org.testcontainers", name = "postgresql", version.ref = "testcontainersVersion" } [plugins] -com-diffplug-spotless = { id = "com.diffplug.spotless", version = "6.25.0" } +com-diffplug-spotless = { id = "com.diffplug.spotless", version = "7.1.0" } io-github-gradle-nexus-publish-plugin = { id = "io.github.gradle-nexus.publish-plugin", version = "1.3.0" } org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.4" } org-hibernate-orm = { id = "org.hibernate.orm", version.ref = "hibernateOrmGradlePluginVersion" } From ec8d8a1f9ab88906acfac3d23f55542185a741bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:34:42 +0000 Subject: [PATCH 045/185] Bump junitVersion from 5.11.3 to 5.13.3 Requires extra dependency org.junit.platform:junit-platform-launcher:1.13.3 --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter-api dependency-version: 5.13.3 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: org.junit.jupiter:junit-jupiter-engine dependency-version: 5.13.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 4 +++- hibernate-reactive-core/build.gradle | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f5e37470f..46e8d0b14 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,8 @@ hibernateOrmGradlePluginVersion = "7.0.2.Final" jacksonDatabindVersion = "2.19.1" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" -junitVersion = "5.11.3" +junitVersion = "5.13.3" +junitPlatformVersion = "1.13.3" log4jVersion = "2.25.1" testcontainersVersion = "1.21.3" vertxSqlClientVersion = "4.5.16" @@ -42,6 +43,7 @@ org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", nam org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" } org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" } org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" } +org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "junitPlatformVersion" } org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.4" } org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.7" } org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" } diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index 04f35e985..1a6ad9efe 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -44,6 +44,7 @@ dependencies { // JUnit Jupiter testImplementation(libs.org.junit.jupiter.junit.jupiter.api) testRuntimeOnly(libs.org.junit.jupiter.junit.jupiter.engine) + testRuntimeOnly(libs.org.junit.platform.junit.platform.launcher) // JDBC driver to test with ORM and PostgreSQL testRuntimeOnly(libs.org.postgresql.postgresql) From 68b1d5ed57045a31d880f25ab1d635d31764c0f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 15:36:28 +0000 Subject: [PATCH 046/185] Bump the hibernate group with 4 updates Bumps the hibernate group with 4 updates: [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm), [org.hibernate.orm:hibernate-jcache](https://github.com/hibernate/hibernate-orm), [org.hibernate.orm:hibernate-jpamodelgen](https://github.com/hibernate/hibernate-orm) and org.hibernate.orm. Updates `org.hibernate.orm:hibernate-core` from 7.0.2.Final to 7.0.6.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.0.6/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.2...7.0.6) Updates `org.hibernate.orm:hibernate-jcache` from 7.0.2.Final to 7.0.6.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.0.6/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.2...7.0.6) Updates `org.hibernate.orm:hibernate-jpamodelgen` from 7.0.2.Final to 7.0.6.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.0.6/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.2...7.0.6) Updates `org.hibernate.orm:hibernate-jcache` from 7.0.2.Final to 7.0.6.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.0.6/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.2...7.0.6) Updates `org.hibernate.orm:hibernate-jpamodelgen` from 7.0.2.Final to 7.0.6.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.0.6/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.0.2...7.0.6) Updates `org.hibernate.orm` from 7.0.2.Final to 7.0.6.Final --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 7.0.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jcache dependency-version: 7.0.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jpamodelgen dependency-version: 7.0.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jcache dependency-version: 7.0.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jpamodelgen dependency-version: 7.0.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm dependency-version: 7.0.6.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 46e8d0b14..20154ce5e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.3" -hibernateOrmVersion = "7.0.2.Final" -hibernateOrmGradlePluginVersion = "7.0.2.Final" +hibernateOrmVersion = "7.0.6.Final" +hibernateOrmGradlePluginVersion = "7.0.6.Final" jacksonDatabindVersion = "2.19.1" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" From e67da11e4d5f407d3e602ff4899a242bf9002a07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 03:37:48 +0000 Subject: [PATCH 047/185] Bump io.smallrye.reactive:mutiny from 2.9.0 to 2.9.3 in the mutiny group Bumps the mutiny group with 1 update: [io.smallrye.reactive:mutiny](https://github.com/smallrye/smallrye-mutiny). Updates `io.smallrye.reactive:mutiny` from 2.9.0 to 2.9.3 - [Release notes](https://github.com/smallrye/smallrye-mutiny/releases) - [Commits](https://github.com/smallrye/smallrye-mutiny/compare/2.9.0...2.9.3) --- updated-dependencies: - dependency-name: io.smallrye.reactive:mutiny dependency-version: 2.9.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: mutiny ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 20154ce5e..beb2bb6b7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" } com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.1.0.jre11-preview" } com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.3.0" } com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" } -io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.0" } +io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.3" } io-vertx-vertx-db2-client = { group = "io.vertx", name = "vertx-db2-client", version.ref = "vertxSqlClientVersion" } io-vertx-vertx-junit5 = { group = "io.vertx", name = "vertx-junit5", version.ref = "vertxSqlClientVersion" } io-vertx-vertx-micrometer-metrics = { group = "io.vertx", name = "vertx-micrometer-metrics", version.ref = "vertxSqlClientVersion" } From 752b36a0885ab003dcf1e7f03b01087c4e6a6ef8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 06:18:18 +0000 Subject: [PATCH 048/185] Bump mariadb from 11.7.2 to 11.8.2 in /tooling/docker Bumps mariadb from 11.7.2 to 11.8.2. --- updated-dependencies: - dependency-name: mariadb dependency-version: 11.8.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tooling/docker/maria.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/maria.Dockerfile b/tooling/docker/maria.Dockerfile index 4ccb397c8..fc954df6e 100644 --- a/tooling/docker/maria.Dockerfile +++ b/tooling/docker/maria.Dockerfile @@ -1,3 +1,3 @@ # MariaDB # See https://hub.docker.com/_/mariadb -FROM docker.io/mariadb:11.7.2 +FROM docker.io/mariadb:11.8.2 From 77bbb4fcbf93cd7111922b70f4ebb3601e410c25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:38:54 +0000 Subject: [PATCH 049/185] Bump mssql/server from 2022-latest to 2025-latest in /tooling/docker Bumps mssql/server from 2022-latest to 2025-latest. --- updated-dependencies: - dependency-name: mssql/server dependency-version: 2025-latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- tooling/docker/sqlserver.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/sqlserver.Dockerfile b/tooling/docker/sqlserver.Dockerfile index b4fb34f2f..94fae1429 100644 --- a/tooling/docker/sqlserver.Dockerfile +++ b/tooling/docker/sqlserver.Dockerfile @@ -1,3 +1,3 @@ # Microsoft SQL Server # See https://hub.docker.com/_/microsoft-mssql-server -FROM mcr.microsoft.com/mssql/server:2022-latest +FROM mcr.microsoft.com/mssql/server:2025-latest From 0e97b9b372310efbfca9a992033134d1f0a158b6 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 15 Jul 2025 11:17:04 +0200 Subject: [PATCH 050/185] [#1136] Keep Hibernate Validator and expressively consistent Expressly 5 for Hibernate Validator 8 Expressly 6 for Hibernate Validator 9 I've decided to keep it simple and just ignore the upgrades for majors. I will update manually as needed for now, and think of something else if it becomes too much of a chore. We could remove the dependency to expressly by adding an extra configuration file for Hibernate Validator, but then I would have to explain why we need the extra configuraiton file somewhere in the examples. --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0d4bc0776..fbc73fcb5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,6 +24,10 @@ updates: day: "tuesday" open-pull-requests-limit: 20 groups: + hibernate-validator: + patterns: + - "org.hibernate.validator*" + - "org.glassfish.expressly*" hibernate: patterns: - "org.hibernate*" @@ -46,6 +50,9 @@ updates: - "org.mariadb.jdbc*" ignore: + # For Hibernate Validator, we will need to update major version manually as needed (but we only use it in tests) + - dependency-name: "org.glassfish.expressly*" + update-types: ["version-update-:semver-major"] # Only patches for Hibernate ORM and Vert.x - dependency-name: "org.hibernate*" update-types: ["version-update:semver-major", "version-update:semver-minor"] From 1489d7ab556d772ae45291f9c4b27cd639cd1211 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:45:45 +0000 Subject: [PATCH 051/185] [#2270] Change docker image to container-registry.oracle.com/mysql/community-server:9.3.0 from `docker.io/mysql:9.2.0` to `container-registry.oracle.com/mysql/community-server:9.3.0` I found the registry in the official MySQL documentation: https://dev.mysql.com/doc/refman/9.3/en/docker-mysql-getting-started.html#docker-download-image The one from docker.io doesn't seem to work with testcontainers. --- .github/workflows/build.yml | 3 +-- .../java/org/hibernate/reactive/containers/MySQLDatabase.java | 2 +- tooling/docker/mysql.Dockerfile | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af832654c..e81eae258 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,8 +49,7 @@ jobs: services: # Label used to access the service container mysql: - # Docker Hub image - image: mysql:9.2.0 + image: container-registry.oracle.com/mysql/community-server:9.3.0 env: MYSQL_ROOT_PASSWORD: hreact MYSQL_DATABASE: hreact diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java index f5cd8f7c7..5b5ad5d2c 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/MySQLDatabase.java @@ -87,7 +87,7 @@ class MySQLDatabase implements TestableDatabase { * TIP: To reuse the same containers across multiple runs, set `testcontainers.reuse.enable=true` in a file located * at `$HOME/.testcontainers.properties` (create the file if it does not exist). */ - public static final MySQLContainer mysql = new MySQLContainer<>( fromDockerfile( "mysql" ) ) + public static final MySQLContainer mysql = new MySQLContainer<>( fromDockerfile( "mysql" ).asCompatibleSubstituteFor( "mysql" ) ) .withUsername( DatabaseConfiguration.USERNAME ) .withPassword( DatabaseConfiguration.PASSWORD ) .withDatabaseName( DatabaseConfiguration.DB_NAME ) diff --git a/tooling/docker/mysql.Dockerfile b/tooling/docker/mysql.Dockerfile index 966350a87..69b12ca62 100644 --- a/tooling/docker/mysql.Dockerfile +++ b/tooling/docker/mysql.Dockerfile @@ -1,3 +1,3 @@ # MySQL # See https://hub.docker.com/_/mysql -FROM docker.io/mysql:9.2.0 +FROM container-registry.oracle.com/mysql/community-server:9.3.0 From 31ab28697501e3a065c6e6a28a9b8550c0d4734f Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 15 Jul 2025 10:14:05 +0200 Subject: [PATCH 052/185] [#2270] Update JBang templates MySQL docker image to `container-registry.oracle.com/mysql/community-server:9.3.0` --- tooling/jbang/MySQLReactiveTest.java.qute | 2 +- tooling/jbang/ReactiveTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute index 473fad721..c258612ae 100755 --- a/tooling/jbang/MySQLReactiveTest.java.qute +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -72,7 +72,7 @@ public class {baseName} { } @ClassRule - public final static MySQLContainer database = new MySQLContainer<>( imageName( "docker.io", "mysql", "9.2.0" ) ); + public final static MySQLContainer database = new MySQLContainer<>( imageName( "container-registry.oracle.com", "mysql/community-server", "9.3.0" ).asCompatibleSubstituteFor( "mysql" ) ); private Mutiny.SessionFactory sessionFactory; diff --git a/tooling/jbang/ReactiveTest.java b/tooling/jbang/ReactiveTest.java index 2d66ca368..ed6011433 100755 --- a/tooling/jbang/ReactiveTest.java +++ b/tooling/jbang/ReactiveTest.java @@ -229,7 +229,7 @@ public String toString() { */ enum Database { POSTGRESQL( () -> new PostgreSQLContainer( "postgres:17.5" ) ), - MYSQL( () -> new MySQLContainer( "mysql:9.2.0" ) ), + MYSQL( () -> new MySQLContainer( "container-registry.oracle.com/mysql/community-server:9.3.0" ) ), DB2( () -> new Db2Container( "docker.io/icr.io/db2_community/db2:12.1.0.0" ).acceptLicense() ), MARIADB( () -> new MariaDBContainer( "mariadb:11.7.2" ) ), COCKROACHDB( () -> new CockroachContainer( "cockroachdb/cockroach:v24.3.13" ) ); From 256bc5e658cdede5164928a6bbdf3da3268034e0 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 16 Jul 2025 09:46:50 +0200 Subject: [PATCH 053/185] [#2368] Remove gradle-nexus:publish-plugin from the catalog We don't use it anymore since the switch to JRelease. This was just a left over. --- build.gradle | 1 - ci/snapshot-publish.Jenkinsfile | 1 - gradle/libs.versions.toml | 1 - 3 files changed, 3 deletions(-) diff --git a/build.gradle b/build.gradle index 2615b335d..d6a172b6f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,6 @@ plugins { id 'maven-publish' alias(libs.plugins.com.diffplug.spotless) alias(libs.plugins.org.asciidoctor.jvm.convert) apply false - alias(libs.plugins.io.github.gradle.nexus.publish.plugin) } group = "org.hibernate.reactive" diff --git a/ci/snapshot-publish.Jenkinsfile b/ci/snapshot-publish.Jenkinsfile index e50eb4cac..7fb8f9444 100644 --- a/ci/snapshot-publish.Jenkinsfile +++ b/ci/snapshot-publish.Jenkinsfile @@ -40,7 +40,6 @@ pipeline { steps { script { withCredentials([ - // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh // TODO: Once we switch to maven-central publishing (from nexus2) we need to update credentialsId: // https://docs.gradle.org/current/samples/sample_publishing_credentials.html#:~:text=via%20environment%20variables usernamePassword(credentialsId: 'central.sonatype.com', passwordVariable: 'ORG_GRADLE_PROJECT_snapshotsPassword', usernameVariable: 'ORG_GRADLE_PROJECT_snapshotsUsername'), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index beb2bb6b7..9967083b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -56,6 +56,5 @@ org-testcontainers-postgresql = { group = "org.testcontainers", name = "postgres [plugins] com-diffplug-spotless = { id = "com.diffplug.spotless", version = "7.1.0" } -io-github-gradle-nexus-publish-plugin = { id = "io.github.gradle-nexus.publish-plugin", version = "1.3.0" } org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.4" } org-hibernate-orm = { id = "org.hibernate.orm", version.ref = "hibernateOrmGradlePluginVersion" } From f320bbcee4dd46e0030fa33018746df4ceea9c8b Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 20 Jul 2025 01:02:43 +0000 Subject: [PATCH 054/185] Update project version to : `3.0.5.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 1b8ea940d..8145f2a96 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.5-SNAPSHOT \ No newline at end of file +projectVersion=3.0.5.Final \ No newline at end of file From 382af2edf58931bf37b0c771caabf59bcfabc59d Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 20 Jul 2025 01:03:36 +0000 Subject: [PATCH 055/185] Update project version to : `3.0.6-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 8145f2a96..df247491e 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.5.Final \ No newline at end of file +projectVersion=3.0.6-SNAPSHOT \ No newline at end of file From 3c7f2f414efc21ed1fb9337879f54c5c16cd7c16 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 22 Jul 2025 16:47:05 +0200 Subject: [PATCH 056/185] [#2381] Fix chaining in ReactiveEmbeddableInitializerImpl#reactiveResolveInstance The code was using .thenAccept instead of .thenCompose --- .../embeddable/internal/ReactiveEmbeddableInitializerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java index 3df56721f..02b3a76d1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -85,7 +85,7 @@ public CompletionStage reactiveResolveInstance(EmbeddableInitializerData d data.setState( State.RESOLVED ); return extractRowState( (ReactiveEmbeddableInitializerData) data ) - .thenAccept( unused -> prepareCompositeInstance( (ReactiveEmbeddableInitializerData) data ) ); + .thenCompose( unused -> prepareCompositeInstance( (ReactiveEmbeddableInitializerData) data ) ); } private CompletionStage extractRowState(ReactiveEmbeddableInitializerData data) { From 5b8ab41b99aa3be477a5dfb58b4ce8ac231e2d94 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 27 Jul 2025 01:02:14 +0000 Subject: [PATCH 057/185] Update project version to : `3.0.6.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index df247491e..521ba41da 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.6-SNAPSHOT \ No newline at end of file +projectVersion=3.0.6.Final \ No newline at end of file From 4ee94906f9626e06dc0903c21fb91c64cdb0f495 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 27 Jul 2025 01:03:11 +0000 Subject: [PATCH 058/185] Update project version to : `3.0.7-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 521ba41da..a11b0a717 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.6.Final \ No newline at end of file +projectVersion=3.0.7-SNAPSHOT \ No newline at end of file From 5b3bce91bf783eb6a5880dad5e1070e4d5077e64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 03:14:24 +0000 Subject: [PATCH 059/185] Bump com.fasterxml.jackson.core:jackson-databind --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.19.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9967083b5..b6951eac8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ assertjVersion = "3.27.3" hibernateOrmVersion = "7.0.6.Final" hibernateOrmGradlePluginVersion = "7.0.6.Final" -jacksonDatabindVersion = "2.19.1" +jacksonDatabindVersion = "2.19.2" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" junitVersion = "5.13.3" From 56457555c072b494b04635f20e925afd52190126 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 30 Jul 2025 09:55:06 +0200 Subject: [PATCH 060/185] Fix db2 podman script in the documentation (podman.md) * Remove docker.io from the image source (it's wrong) * Upgrade the image to 12.1.2.0 --- podman.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/podman.md b/podman.md index bc4d96ba7..9b9a71af2 100644 --- a/podman.md +++ b/podman.md @@ -158,7 +158,7 @@ and schema to run the tests: podman run --rm -e LICENSE=accept --privileged=true --group-add keep-groups \ --name HibernateTestingDB2 -e DBNAME=hreact -e DB2INSTANCE=hreact \ -e DB2INST1_PASSWORD=hreact -e PERSISTENT_HOME=false -e ARCHIVE_LOGS=false \ - -e AUTOCONFIG=false -p 50000:50000 docker.io/icr.io/db2_community/db2:12.1.0.0 + -e AUTOCONFIG=false -p 50000:50000 icr.io/db2_community/db2:12.1.2.0 ``` When the database has started, you can run the tests on Db2 with: From 2a11b54c044ebe49b0fc8d06267695d45e2dcfc5 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 30 Jul 2025 15:23:12 +0200 Subject: [PATCH 061/185] [#2387] Upgrade Hibernate ORM to 7.1.0.CR1 --- README.md | 8 +- gradle.properties | 4 +- gradle/libs.versions.toml | 4 +- gradle/version.properties | 2 +- .../ReactiveInsertReturningDelegate.java | 2 +- .../impl/ReactiveAbstractEntityPersister.java | 27 +- ...ReactiveJoinedSubclassEntityPersister.java | 4 +- .../ReactiveSingleTableEntityPersister.java | 4 +- .../ReactiveUnionSubclassEntityPersister.java | 4 +- .../impl/ReactiveServiceInitiators.java | 4 + .../sql/internal/ReactiveNativeQueryImpl.java | 11 +- .../query/sqm/ReactiveSqmSelectionQuery.java | 2 +- .../sqm/internal/ReactiveQuerySqmImpl.java | 6 +- .../cte/ReactiveCteInsertHandler.java | 43 +-- .../cte/ReactiveInsertExecutionDelegate.java | 10 +- ...activeExecuteWithTemporaryTableHelper.java | 61 ++-- ...iveGlobalTemporaryTableInsertStrategy.java | 3 +- ...eGlobalTemporaryTableMutationStrategy.java | 6 +- ...tiveLocalTemporaryTableInsertStrategy.java | 3 +- ...veLocalTemporaryTableMutationStrategy.java | 6 +- ...ReactivePersistentTableInsertStrategy.java | 3 +- ...activePersistentTableMutationStrategy.java | 6 +- ...tiveRestrictedDeleteExecutionDelegate.java | 149 ++++---- .../ReactiveSoftDeleteExecutionDelegate.java | 320 ++++++++++++++++++ .../ReactiveTableBasedDeleteHandler.java | 40 ++- .../ReactiveTableBasedInsertHandler.java | 15 +- .../ReactiveTableBasedUpdateHandler.java | 13 +- .../ReactiveTemporaryTableHelper.java | 22 +- .../ReactiveUpdateExecutionDelegate.java | 9 +- .../impl/ReactiveStatelessSessionImpl.java | 115 ++++--- .../sql/results/ReactiveResultSetMapping.java | 7 +- .../internal/ReactiveEmbeddableFetchImpl.java | 6 +- ...eactiveEmbeddableForeignKeyResultImpl.java | 4 +- .../ReactiveEmbeddableInitializerImpl.java | 4 +- .../ReactiveDeferredResultSetAccess.java | 28 +- .../ReactiveDirectResultSetAccess.java | 18 +- .../internal/ReactiveResultSetAccess.java | 49 +-- .../jdbc/ReactiveJsonArrayJdbcType.java | 46 +-- .../reactive/ImplicitSoftDeleteTests.java | 225 ++++++++++++ .../hibernate/reactive/LazyPropertyTest.java | 4 +- .../org/hibernate/reactive/UpsertTest.java | 12 +- .../bytecode-enhancements-it/build.gradle | 7 +- .../reactive/it/verticle/ProductVerticle.java | 21 +- .../reactive/it/LocalContextTest.java | 7 +- settings.gradle | 4 +- 45 files changed, 951 insertions(+), 397 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveSoftDeleteExecutionDelegate.java create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java diff --git a/README.md b/README.md index 12904bd7b..ad38e4b2c 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,14 @@ Learn more at . Hibernate Reactive has been tested with: - Java 17, 21, 24 -- PostgreSQL 16 +- PostgreSQL 17 - MySQL 9 - MariaDB 11 - Db2 12 -- CockroachDB v24 -- MS SQL Server 2022 +- CockroachDB v25 +- MS SQL Server 2025 - Oracle 23 -- [Hibernate ORM][] 7.0.2.Final +- [Hibernate ORM][] 7.1.0.Final - [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.16 - [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.16 - [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.16 diff --git a/gradle.properties b/gradle.properties index 346e9fa42..80dfe6f33 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,10 +39,10 @@ org.gradle.java.installations.auto-download=false ### Settings the following properties will override the version defined in gradle/libs.versions.toml # The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) -#hibernateOrmVersion = 7.0.2.Final +#hibernateOrmVersion = 7.1.0.CR1 # Override default Hibernate ORM Gradle plugin version -#hibernateOrmGradlePluginVersion = 7.0.2.Final +#hibernateOrmGradlePluginVersion = 7.1.0.CR1 # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) # this is required when using intervals or weird versions or the build will fail diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b6951eac8..4da2de31f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.3" -hibernateOrmVersion = "7.0.6.Final" -hibernateOrmGradlePluginVersion = "7.0.6.Final" +hibernateOrmVersion = "7.1.0.CR1" +hibernateOrmGradlePluginVersion = "7.1.0.CR1" jacksonDatabindVersion = "2.19.2" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" diff --git a/gradle/version.properties b/gradle/version.properties index a11b0a717..3cb2681d8 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.0.7-SNAPSHOT \ No newline at end of file +projectVersion=3.1.0-SNAPSHOT diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java index 2b4063173..90b098652 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/insert/ReactiveInsertReturningDelegate.java @@ -91,7 +91,7 @@ public TableMutationBuilder createTableMutationBuilder( return new TableInsertReturningBuilder( persister, tableReference, generatedColumns, sessionFactory ); } else { - return new TableUpdateReturningBuilder<>( persister, tableReference, generatedColumns, sessionFactory ); + return new TableUpdateReturningBuilder( persister, tableReference, generatedColumns, sessionFactory ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java index 0ebebe603..8e96291eb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java @@ -474,13 +474,9 @@ default CompletionStage reactiveInitializeEnhancedEntityUsedAsProxy( Object entity, String nameOfAttributeBeingAccessed, SharedSessionContractImplementor session) { - final BytecodeEnhancementMetadata enhancementMetadata = getEntityPersister().getBytecodeEnhancementMetadata(); final BytecodeLazyAttributeInterceptor currentInterceptor = enhancementMetadata.extractLazyInterceptor( entity ); - if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - final EnhancementAsProxyLazinessInterceptor proxyInterceptor = - (EnhancementAsProxyLazinessInterceptor) currentInterceptor; - + if ( currentInterceptor instanceof EnhancementAsProxyLazinessInterceptor proxyInterceptor ) { final EntityKey entityKey = proxyInterceptor.getEntityKey(); final Object identifier = entityKey.getIdentifier(); @@ -494,19 +490,17 @@ default CompletionStage reactiveInitializeEnhancedEntityUsedAsProxy( .handleEntityNotFound( entityKey.getEntityName(), identifier ); } + final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata + .injectInterceptor( entity, identifier, session ); + if ( nameOfAttributeBeingAccessed == null ) { return null; } else { - final LazyAttributeLoadingInterceptor interceptor = enhancementMetadata - .injectInterceptor( entity, identifier, session ); return interceptor.readObject( - entity, - nameOfAttributeBeingAccessed, - interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) - ? getPropertyValue( entity, nameOfAttributeBeingAccessed ) - : ( (LazyPropertyInitializer) this ) - .initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ) + entity, nameOfAttributeBeingAccessed, interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) + ? getPropertyValue( entity, nameOfAttributeBeingAccessed ) + : ( (LazyPropertyInitializer) this ).initializeLazyProperty( nameOfAttributeBeingAccessed, entity, session ) ); } } ); @@ -528,11 +522,12 @@ private CompletionStage loadFromDatabaseOrCache( return completedFuture( loaded ); } } - return ( (ReactiveSingleIdEntityLoader) determineLoaderToUse( session ) ) - .load( identifier, entity, LockOptions.NONE, session ); + final LockOptions lockOptions = new LockOptions(); + return ( (ReactiveSingleIdEntityLoader) determineLoaderToUse( session, lockOptions ) ) + .load( identifier, entity, lockOptions, session ); } - SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session); + SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions); boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java index 0182ef403..2443d4d9b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java @@ -139,8 +139,8 @@ protected AttributeMapping buildPluralAttributeMapping( } @Override - public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { - return super.determineLoaderToUse( session ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions) { + return super.determineLoaderToUse( session, lockOptions ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java index 3006a0eae..2f5878737 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java @@ -86,8 +86,8 @@ public GeneratedValuesMutationDelegate createInsertDelegate() { } @Override - public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { - return super.determineLoaderToUse( session ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions) { + return super.determineLoaderToUse( session, lockOptions ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java index 52871e30f..bba3cf86d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java @@ -139,8 +139,8 @@ protected AttributeMapping buildPluralAttributeMapping( } @Override - public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session) { - return super.determineLoaderToUse( session ); + public SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions) { + return super.determineLoaderToUse( session, lockOptions ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java index 2343a9644..13b0ae6b8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/impl/ReactiveServiceInitiators.java @@ -22,6 +22,7 @@ import org.hibernate.engine.jdbc.internal.SqlStatementLoggerInitiator; import org.hibernate.engine.jndi.internal.JndiServiceInitiator; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; +import org.hibernate.internal.util.cache.InternalCacheFactoryInitiator; import org.hibernate.persister.internal.PersisterFactoryInitiator; import org.hibernate.property.access.internal.PropertyAccessStrategyResolverInitiator; import org.hibernate.reactive.context.impl.VertxContextInitiator; @@ -161,6 +162,9 @@ private static List> buildInitialServiceInitiatorLis // Custom for Hibernate Reactive: BatchLoaderFactory serviceInitiators.add( ReactiveBatchLoaderFactoryInitiator.INSTANCE ); + // [standard] InternalCacheFactoryService + serviceInitiators.add( InternalCacheFactoryInitiator.INSTANCE ); + // --- end of services defined by Hibernate ORM // --- custom ones follow: diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java index 6ca5b2024..d58ae57a9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java @@ -142,19 +142,14 @@ private ReactiveSelectQueryPlan reactiveSelectPlan() { private ReactiveNonSelectQueryPlan reactiveNonSelectPlan() { final QueryInterpretationCache.Key cacheKey = generateNonSelectInterpretationsKey(); if ( cacheKey != null ) { - NonSelectQueryPlan queryPlan = getSession().getFactory().getQueryEngine() - .getInterpretationCache().getNonSelectQueryPlan( cacheKey ); + NonSelectQueryPlan queryPlan = getSession().getFactory().getQueryEngine().getInterpretationCache().getNonSelectQueryPlan( cacheKey ); if ( queryPlan != null ) { return (ReactiveNonSelectQueryPlan) queryPlan; } } - final String sqlString = expandParameterLists(); - ReactiveNonSelectQueryPlan queryPlan = new ReactiveNativeNonSelectQueryPlan( - sqlString, - getQuerySpaces(), - getParameterOccurrences() - ); + final String sqlString = expandParameterLists( 1 ); + ReactiveNonSelectQueryPlan queryPlan = new ReactiveNativeNonSelectQueryPlan( sqlString, getQuerySpaces(), getParameterOccurrences() ); if ( cacheKey != null ) { getSession().getFactory().getQueryEngine().getInterpretationCache() .cacheNonSelectQueryPlan( cacheKey, queryPlan ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java index 0794ae435..a43d917e9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/ReactiveSqmSelectionQuery.java @@ -24,7 +24,7 @@ /** * @see org.hibernate.query.sqm.SqmSelectionQuery */ -public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery, SqmQuery { +public interface ReactiveSqmSelectionQuery extends ReactiveSelectionQuery, SqmQuery { @Override ReactiveSqmSelectionQuery setParameter(String name, Object value); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java index 5242ab747..755448cbd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java @@ -44,8 +44,8 @@ import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.sqm.internal.QuerySqmImpl; import org.hibernate.query.sqm.internal.SqmInterpretationsKey; +import org.hibernate.query.sqm.internal.SqmQueryImpl; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; @@ -71,9 +71,9 @@ import jakarta.persistence.metamodel.Type; /** - * A reactive {@link QuerySqmImpl} + * A reactive {@link SqmQueryImpl} */ -public class ReactiveQuerySqmImpl extends QuerySqmImpl implements ReactiveSqmQueryImplementor { +public class ReactiveQuerySqmImpl extends SqmQueryImpl implements ReactiveSqmQueryImplementor { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java index 603563e0f..e16ad0abd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java @@ -144,27 +144,10 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = sqmConverter.visitInsertionTargetPaths( (assignable, columnReferences) -> { final SqmPathInterpretation pathInterpretation = (SqmPathInterpretation) assignable; - final int offset = CteTable.determineModelPartStartIndex( - entityDescriptor, - pathInterpretation.getExpressionType() - ); - if ( offset == -1 ) { - throw new IllegalStateException( "Couldn't find matching cte column for: " + ( (Expression) assignable ).getExpressionType() ); - } - final int end = offset + pathInterpretation.getExpressionType().getJdbcTypeCount(); - // Find a matching cte table column and set that at the current index - final List columns = getCteTable().getCteColumns().subList( offset, end ); + final List columns = getCteTable().findCteColumns( pathInterpretation.getExpressionType() ); insertStatement.addTargetColumnReferences( columnReferences ); targetPathCteColumns.addAll( columns ); - targetPathColumns.add( - new AbstractMap.SimpleEntry<>( - columns, - new Assignment( - assignable, - (Expression) assignable - ) - ) - ); + targetPathColumns.add( new AbstractMap.SimpleEntry<>( columns, new Assignment( assignable, (Expression) assignable ) ) ); }, sqmInsertStatement, entityDescriptor, @@ -205,15 +188,8 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec ); } if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - SqmInsertStrategyHelper.createRowNumberingExpression( - querySpec, - sessionFactory - ) - ) - ); + querySpec.getSelectClause() + .addSqlSelection( new SqlSelectionImpl( 0, SqmInsertStrategyHelper.createRowNumberingExpression( querySpec, sessionFactory ) ) ); } } ); @@ -250,6 +226,17 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec ); } } + if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { + querySpec.getSelectClause().addSqlSelection( + new SqlSelectionImpl( + 0, + SqmInsertStrategyHelper.createRowNumberingExpression( + querySpec, + sessionFactory + ) + ) + ); + } final ValuesTableGroup valuesTableGroup = new ValuesTableGroup( navigablePath, entityDescriptor.getEntityPersister(), diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java index 33acc38f6..399e8b30a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java @@ -11,12 +11,12 @@ import java.util.function.Function; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.InsertExecutionDelegate; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTableBasedInsertHandler; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -34,12 +34,14 @@ public class ReactiveInsertExecutionDelegate extends InsertExecutionDelegate imp public ReactiveInsertExecutionDelegate( MultiTableSqmMutationConverter sqmConverter, TemporaryTable entityTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, DomainParameterXref domainParameterXref, TableGroup insertingTableGroup, Map tableReferenceByAlias, List assignments, + boolean assignedId, InsertSelectStatement insertStatement, ConflictClause conflictClause, JdbcParameter sessionUidParameter, @@ -47,12 +49,14 @@ public ReactiveInsertExecutionDelegate( super( sqmConverter, entityTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sessionUidAccess, domainParameterXref, insertingTableGroup, tableReferenceByAlias, assignments, + assignedId, insertStatement, conflictClause, sessionUidParameter, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java index e8f7bbea6..d936c590f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java @@ -17,6 +17,7 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableColumn; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -33,6 +34,7 @@ import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTemporaryTableHelper.TemporaryTableCreationWork; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstTranslatorFactory; @@ -52,6 +54,8 @@ import org.hibernate.sql.results.internal.SqlSelectionImpl; import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTemporaryTableHelper.cleanTemporaryTableRows; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -104,15 +108,11 @@ public static CompletionStage saveMatchingIdsIntoIdTable( mutatingEntityDescriptor.getIdentifierMapping().forEachSelectable( (selectionIndex, selection) -> { - final TableReference tableReference = mutatingTableGroup.resolveTableReference( - mutatingTableGroup.getNavigablePath(), - selection.getContainingTableExpression() - ); matchingIdSelection.getSelectClause().addSqlSelection( new SqlSelectionImpl( selectionIndex + 1, sqmConverter.getSqlExpressionResolver().resolveSqlExpression( - tableReference, + mutatingTableGroup.resolveTableReference( mutatingTableGroup.getNavigablePath(), selection.getContainingTableExpression() ), selection ) ) @@ -155,7 +155,8 @@ public static CompletionStage saveIntoTemporaryTable( temporaryTableInsert.getSourceSelectStatement().visitQuerySpecs( querySpec -> querySpec.getFromClause().visitTableJoins( tableJoin -> { - if ( tableJoin.getJoinType() != SqlAstJoinType.INNER ) { + if ( tableJoin.isInitialized() + && tableJoin.getJoinType() != SqlAstJoinType.INNER ) { lockOptions.setLockMode( lockMode ); } } @@ -210,7 +211,7 @@ public static QuerySpec createIdTableSelectQuerySpec( querySpec.getFromClause().addRoot( idTableGroup ); - applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart ); + applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart, entityDescriptor ); applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidAccess, executionContext ); return querySpec; @@ -221,9 +222,10 @@ private static void applyIdTableSelections( QuerySpec querySpec, TableReference tableReference, TemporaryTable idTable, - ModelPart fkModelPart) { + ModelPart fkModelPart, + EntityMappingType entityDescriptor) { if ( fkModelPart == null ) { - final int size = idTable.getEntityDescriptor().getIdentifierMapping().getJdbcTypeCount(); + final int size = entityDescriptor.getIdentifierMapping().getJdbcTypeCount(); for ( int i = 0; i < size; i++ ) { final TemporaryTableColumn temporaryTableColumn = idTable.getColumns().get( i ); if ( temporaryTableColumn != idTable.getSessionUidColumn() ) { @@ -285,12 +287,31 @@ private static void applyIdTableRestrictions( } } + @Deprecated(forRemoval = true, since = "3.1") public static CompletionStage performBeforeTemporaryTableUseActions( TemporaryTable temporaryTable, ExecutionContext executionContext) { + return performBeforeTemporaryTableUseActions( + temporaryTable, + executionContext.getSession().getDialect().getTemporaryTableBeforeUseAction(), + executionContext + ).thenCompose( CompletionStages::voidFuture ); + } + + public static CompletionStage performBeforeTemporaryTableUseActions( + TemporaryTable temporaryTable, + TemporaryTableStrategy temporaryTableStrategy, + ExecutionContext executionContext) { + return performBeforeTemporaryTableUseActions( temporaryTable, temporaryTableStrategy.getTemporaryTableBeforeUseAction(), executionContext ); + } + + public static CompletionStage performBeforeTemporaryTableUseActions( + TemporaryTable temporaryTable, + BeforeUseAction beforeUseAction, + ExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); - if ( dialect.getTemporaryTableBeforeUseAction() == BeforeUseAction.CREATE ) { + if ( beforeUseAction == BeforeUseAction.CREATE ) { final TemporaryTableCreationWork temporaryTableCreationWork = new TemporaryTableCreationWork( temporaryTable, factory ); final TempTableDdlTransactionHandling ddlTransactionHandling = dialect.getTemporaryTableDdlTransactionHandling(); if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) { @@ -298,7 +319,7 @@ public static CompletionStage performBeforeTemporaryTableUseActions( } throw LOG.notYetImplemented(); } - return voidFuture(); + return falseFuture(); } public static CompletionStage performAfterTemporaryTableUseActions( @@ -308,14 +329,11 @@ public static CompletionStage performAfterTemporaryTableUseActions( ExecutionContext executionContext) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); - switch ( afterUseAction ) { - case CLEAN: - return cleanTemporaryTableRows( temporaryTable, dialect.getTemporaryTableExporter(), sessionUidAccess, executionContext.getSession() ); - case DROP: - return dropAction( temporaryTable, executionContext, factory, dialect ); - default: - return voidFuture(); - } + return switch ( afterUseAction ) { + case CLEAN -> cleanTemporaryTableRows( temporaryTable, dialect.getTemporaryTableExporter(), sessionUidAccess, executionContext.getSession() ); + case DROP -> dropAction( temporaryTable, executionContext, factory, dialect ); + default -> voidFuture(); + }; } private static CompletionStage dropAction( @@ -327,10 +345,11 @@ private static CompletionStage dropAction( if ( ddlTransactionHandling == TempTableDdlTransactionHandling.NONE ) { return new ReactiveTemporaryTableHelper .TemporaryTableDropWork( temporaryTable, factory ) - .reactiveExecute( ( (ReactiveConnectionSupplier) executionContext.getSession() ).getReactiveConnection() ); + .reactiveExecute( ( (ReactiveConnectionSupplier) executionContext.getSession() ).getReactiveConnection() ) + .thenCompose( CompletionStages::voidFuture ); } - throw LOG.notYetImplemented(); + return failedFuture( LOG.notYetImplemented() ); } private static void doNothing(Integer integer, PreparedStatement preparedStatement) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java index 2f7f7bb37..708a7b072 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java @@ -56,7 +56,8 @@ public CompletionStage reactiveExecuteInsert( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, // generally a global temp table should already track a Connection-specific uid, // but just in case a particular env needs it... ReactiveGlobalTemporaryTableStrategy::sessionIdentifier, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java index 66b001520..baa4ed9a5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java @@ -59,7 +59,8 @@ public CompletionStage reactiveExecuteUpdate( sqmUpdateStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, ReactivePersistentTableStrategy::sessionIdentifier, getSessionFactory() ).reactiveExecute( context ) ); @@ -75,7 +76,8 @@ public CompletionStage reactiveExecuteDelete( sqmDeleteStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, ReactiveGlobalTemporaryTableStrategy::sessionIdentifier, getSessionFactory() ).reactiveExecute( context ) ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java index ae2298ddd..322127730 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java @@ -31,7 +31,8 @@ public CompletionStage reactiveExecuteInsert( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - afterUserAction(), + getTemporaryTableStrategy(), + isDropIdTables(), ReactiveLocalTemporaryTableInsertStrategy::throwUnexpectedCallToSessionUIDError, getSessionFactory() ).reactiveExecute( context ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java index fe07871eb..d22d50e5b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java @@ -37,7 +37,8 @@ public CompletionStage reactiveExecuteUpdate( sqmUpdate, domainParameterXref, getTemporaryTable(), - afterUseAction(), + getTemporaryTableStrategy(), + isDropIdTables(), ReactiveLocalTemporaryTableMutationStrategy::throwUnexpectedAccessToSessionUID, getSessionFactory() ).reactiveExecute( context ); @@ -52,7 +53,8 @@ public CompletionStage reactiveExecuteDelete( sqmDelete, domainParameterXref, getTemporaryTable(), - afterUseAction(), + getTemporaryTableStrategy(), + isDropIdTables(), ReactiveLocalTemporaryTableMutationStrategy::throwUnexpectedAccessToSessionUID, getSessionFactory() ).reactiveExecute( context ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java index 9a44a44cf..951cabce1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java @@ -53,7 +53,8 @@ public CompletionStage reactiveExecuteInsert( sqmInsertStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, ReactivePersistentTableStrategy::sessionIdentifier, getSessionFactory() ).reactiveExecute( context ) ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java index 57cd14f4b..8f23ce727 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java @@ -56,7 +56,8 @@ public CompletionStage reactiveExecuteUpdate( sqmUpdateStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, ReactivePersistentTableStrategy::sessionIdentifier, getSessionFactory() ).reactiveExecute( context ) ); @@ -72,7 +73,8 @@ public CompletionStage reactiveExecuteDelete( sqmDeleteStatement, domainParameterXref, getTemporaryTable(), - getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(), + getTemporaryTableStrategy(), + false, ReactivePersistentTableStrategy::sessionIdentifier, getSessionFactory() ).reactiveExecute( context ) ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java index 0e1097b63..c119e718a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -16,6 +15,7 @@ import java.util.function.Supplier; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -33,17 +33,15 @@ import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; +import org.hibernate.query.sqm.mutation.internal.temptable.AbstractDeleteExecutionDelegate; import org.hibernate.query.sqm.mutation.internal.temptable.ColumnReferenceCheckingSqlAstWalker; import org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithoutIdTableHelper; import org.hibernate.query.sqm.mutation.internal.temptable.RestrictedDeleteExecutionDelegate; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; import org.hibernate.reactive.util.impl.CompletionStages; @@ -68,67 +66,40 @@ import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * The reactive version of {@link RestrictedDeleteExecutionDelegate} */ -// Basically a copy of RestrictedDeleteExecutionDelegate, we will probably need to refactor this code to avoid -// duplication -public class ReactiveRestrictedDeleteExecutionDelegate +// Basically a copy of RestrictedDeleteExecutionDelegate, we will probably need to refactor this code to avoid duplication +public class ReactiveRestrictedDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate implements ReactiveTableBasedDeleteHandler.ReactiveExecutionDelegate { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - private final EntityMappingType entityDescriptor; - private final TemporaryTable idTable; - private final AfterUseAction afterUseAction; - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; - private final SessionFactoryImplementor sessionFactory; - - private final Function sessionUidAccess; - private final MultiTableSqmMutationConverter converter; + private static final Log LOG = make( Log.class, lookup() ); public ReactiveRestrictedDeleteExecutionDelegate( - EntityMappingType entityDescriptor, - TemporaryTable idTable, - AfterUseAction afterUseAction, - SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, - Function sessionUidAccess, - QueryOptions queryOptions, - LoadQueryInfluencers loadQueryInfluencers, - QueryParameterBindings queryParameterBindings, - SessionFactoryImplementor sessionFactory) { - this.entityDescriptor = entityDescriptor; - this.idTable = idTable; - this.afterUseAction = afterUseAction; - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; - this.sessionUidAccess = sessionUidAccess; - this.sessionFactory = sessionFactory; - this.converter = new MultiTableSqmMutationConverter( - entityDescriptor, - sqmDelete, - sqmDelete.getTarget(), - domainParameterXref, - queryOptions, - loadQueryInfluencers, - queryParameterBindings, - sessionFactory.getSqlTranslationEngine() + EntityMappingType entityDescriptor, TemporaryTable idTable, TemporaryTableStrategy temporaryTableStrategy, boolean forceDropAfterUse, SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, QueryOptions queryOptions, LoadQueryInfluencers loadQueryInfluencers, QueryParameterBindings queryParameterBindings, + Function sessionUidAccess, SessionFactoryImplementor sessionFactory + ) { + super( + entityDescriptor, idTable, temporaryTableStrategy, forceDropAfterUse, sqmDelete, domainParameterXref, queryOptions, loadQueryInfluencers, queryParameterBindings, sessionUidAccess, + sessionFactory ); } + @Override public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final EntityPersister entityDescriptor = sessionFactory.getRuntimeMetamodels() + final EntityPersister entityDescriptor = getSessionFactory().getRuntimeMetamodels() .getMappingMetamodel() - .getEntityDescriptor( sqmDelete.getTarget().getEntityName() ); + .getEntityDescriptor( getSqmDelete().getTarget().getEntityName() ); final String hierarchyRootTableName = entityDescriptor.getTableName(); - final TableGroup deletingTableGroup = converter.getMutatingTableGroup(); + final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup(); final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( deletingTableGroup.getNavigablePath(), @@ -141,7 +112,7 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec // 2) we also inspect each ColumnReference that is part of the where-clause to see which // table it comes from. If all the referenced columns (if any at all) are from the root table // we can perform all the deletes without using an id-table - final Predicate specifiedRestriction = converter.visitWhereClause( sqmDelete.getWhereClause() ); + final Predicate specifiedRestriction = getConverter().visitWhereClause( getSqmDelete().getWhereClause() ); final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); entityDescriptor.applyBaseRestrictions( @@ -151,10 +122,10 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), false, null, - converter + getConverter() ); - converter.pruneTableGroupJoins(); + getConverter().pruneTableGroupJoins(); final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( hierarchyRootTableReference.getIdentificationVariable() ); @@ -175,8 +146,8 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec if ( needsIdTable ) { return executeWithIdTable( predicateCollector.getPredicate(), - converter.getJdbcParamsBySqmParam(), - converter.getSqmParameterMappingModelExpressibleResolutions(), + getConverter().getJdbcParamsBySqmParam(), + getConverter().getSqmParameterMappingModelExpressibleResolutions(), executionContextAdapter ); } @@ -184,9 +155,9 @@ public CompletionStage reactiveExecute(DomainQueryExecutionContext exec return executeWithoutIdTable( predicateCollector.getPredicate(), deletingTableGroup, - converter.getJdbcParamsBySqmParam(), - converter.getSqmParameterMappingModelExpressibleResolutions(), - converter.getSqlExpressionResolver(), + getConverter().getJdbcParamsBySqmParam(), + getConverter().getSqmParameterMappingModelExpressibleResolutions(), + getConverter().getSqlExpressionResolver(), executionContextAdapter ); } @@ -199,9 +170,9 @@ private CompletionStage executeWithoutIdTable( Map, MappingModelExpressible> paramTypeResolutions, SqlExpressionResolver sqlExpressionResolver, ExecutionContext executionContext) { - assert entityDescriptor == entityDescriptor.getRootEntityDescriptor(); + assert getEntityDescriptor() == getEntityDescriptor().getRootEntityDescriptor(); - final EntityPersister rootEntityPersister = entityDescriptor.getEntityPersister(); + final EntityPersister rootEntityPersister = getEntityDescriptor().getEntityPersister(); final String rootTableName = rootEntityPersister.getTableName(); final NamedTableReference rootTableReference = (NamedTableReference) tableGroup.resolveTableReference( tableGroup.getNavigablePath(), @@ -214,14 +185,14 @@ private CompletionStage executeWithoutIdTable( suppliedPredicate, rootEntityPersister, sqlExpressionResolver, - sessionFactory + getSessionFactory() ); final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), - domainParameterXref, + getDomainParameterXref(), SqmUtil.generateJdbcParamsXref( - domainParameterXref, + getDomainParameterXref(), () -> restrictionSqmParameterResolutions ), new SqmParameterMappingModelResolutionAccess() { @@ -235,7 +206,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter cleanUpCollectionTablesStage = ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - entityDescriptor, + getEntityDescriptor(), (tableReference, attributeMapping) -> { // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup if ( suppliedPredicate == null ) { @@ -254,7 +225,7 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter MappingModelExpressible getResolvedMappingModelType(SqmParameter MappingModelExpressible getResolvedMappingModelType(SqmParameter rows.get() ); } else { - entityDescriptor.visitConstraintOrderedTables( + getEntityDescriptor().visitConstraintOrderedTables( (tableExpression, tableKeyColumnVisitationSupplier) -> { if ( !tableExpression.equals( rootTableName ) ) { final NamedTableReference tableReference = (NamedTableReference) tableGroup.getTableReference( @@ -354,7 +325,7 @@ private CompletionStage visitUnionTableReferences( JdbcParameterBindings jdbcParameterBindings, CompletionStage[] deleteFromNonRootStages, MutableInteger rows) { - entityDescriptor.visitConstraintOrderedTables( + getEntityDescriptor().visitConstraintOrderedTables( (tableExpression, tableKeyColumnVisitationSupplier) -> { final NamedTableReference tableReference = new NamedTableReference( tableExpression, @@ -449,7 +420,7 @@ private CompletionStage deleteFromNonRootTableWithoutIdTable( final Expression deletingTableColumnRefsExpression = deletingTableColumnRefs.size() == 1 ? deletingTableColumnRefs.get( 0 ) - : new SqlTuple( deletingTableColumnRefs, entityDescriptor.getIdentifierMapping() ); + : new SqlTuple( deletingTableColumnRefs, getEntityDescriptor().getIdentifierMapping() ); tableDeletePredicate = new InSubQueryPredicate( deletingTableColumnRefsExpression, @@ -502,9 +473,9 @@ private CompletionStage executeWithIdTable( ExecutionContext executionContext) { final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), - domainParameterXref, + getDomainParameterXref(), SqmUtil.generateJdbcParamsXref( - domainParameterXref, + getDomainParameterXref(), () -> restrictionSqmParameterResolutions ), new SqmParameterMappingModelResolutionAccess() { @@ -517,14 +488,14 @@ public MappingModelExpressible getResolvedMappingModelType(SqmParameter executeUsingIdTable( predicate, executionContext, jdbcParameterBindings ) .handle( CompletionStages::handle ) .thenCompose( resultHandler -> ReactiveExecuteWithTemporaryTableHelper .performAfterTemporaryTableUseActions( - idTable, - sessionUidAccess, - afterUseAction, + getIdTable(), + getSessionUidAccess(), + getAfterUseAction(), executionContext ) .thenCompose( resultHandler::getResultAsCompletionStage ) @@ -537,28 +508,27 @@ private CompletionStage executeUsingIdTable( ExecutionContext executionContext, JdbcParameterBindings jdbcParameterBindings) { return ReactiveExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable( - converter, + getConverter(), predicate, - idTable, - sessionUidAccess, + getIdTable(), + getSessionUidAccess(), jdbcParameterBindings, executionContext ) .thenCompose( rows -> { final QuerySpec idTableIdentifierSubQuery = createIdTableSelectQuerySpec( - idTable, - sessionUidAccess, - entityDescriptor, + getIdTable(), + getSessionUidAccess(), + getEntityDescriptor(), executionContext ); return ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - entityDescriptor, + getEntityDescriptor(), (tableReference, attributeMapping) -> { final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idTableFkSubQuery = fkDescriptor.getTargetPart() - .isEntityIdentifierMapping() - ? idTableIdentifierSubQuery - : createIdTableSelectQuerySpec( idTable, fkDescriptor.getTargetPart(), sessionUidAccess, entityDescriptor, executionContext ); + final QuerySpec idTableFkSubQuery = fkDescriptor.getTargetPart().isEntityIdentifierMapping() + ? idTableIdentifierSubQuery + : createIdTableSelectQuerySpec( getIdTable(), fkDescriptor.getTargetPart(), getSessionUidAccess(), getEntityDescriptor(), executionContext ); return new InSubQueryPredicate( MappingModelCreationHelper.buildColumnReferenceExpression( new MutatingTableReferenceGroupWrapper( @@ -568,7 +538,7 @@ private CompletionStage executeUsingIdTable( ), fkDescriptor, null, - sessionFactory + getSessionFactory() ), idTableFkSubQuery, false @@ -586,7 +556,7 @@ private CompletionStage visitConstraintOrderedTables( QuerySpec idTableIdentifierSubQuery, ExecutionContext executionContext) { final Completable completable = new Completable<>(); - entityDescriptor + getEntityDescriptor() .visitConstraintOrderedTables( (tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable( tableExpression, tableKeyColumnVisitationSupplier, @@ -605,12 +575,8 @@ private CompletionStage deleteFromTableUsingIdTable( ExecutionContext executionContext) { LOG.tracef( "deleteFromTableUsingIdTable - %s", tableExpression ); - final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( entityDescriptor ); - final NamedTableReference targetTable = new NamedTableReference( - tableExpression, - DeleteStatement.DEFAULT_ALIAS, - true - ); + final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() ); + final NamedTableReference targetTable = new NamedTableReference( tableExpression, DeleteStatement.DEFAULT_ALIAS, true ); tableKeyColumnVisitationSupplier.get().accept( (columnIndex, selection) -> { @@ -636,5 +602,4 @@ private CompletionStage deleteFromTableUsingIdTable( executionContext ); } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveSoftDeleteExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveSoftDeleteExecutionDelegate.java new file mode 100644 index 000000000..607812f93 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveSoftDeleteExecutionDelegate.java @@ -0,0 +1,320 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.mutation.internal.temptable; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; +import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.metamodel.mapping.TableDetails; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; +import org.hibernate.query.sqm.mutation.internal.temptable.AbstractDeleteExecutionDelegate; +import org.hibernate.query.sqm.mutation.internal.temptable.ColumnReferenceCheckingSqlAstWalker; +import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.ast.tree.delete.DeleteStatement; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; +import org.hibernate.sql.ast.tree.from.NamedTableReference; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.predicate.PredicateCollector; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.results.internal.SqlSelectionImpl; + +import static java.util.Collections.singletonList; +import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.omittingLockingAndPaging; +import static org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; + +public class ReactiveSoftDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate + implements ReactiveTableBasedDeleteHandler.ReactiveExecutionDelegate { + + public ReactiveSoftDeleteExecutionDelegate( + EntityMappingType entityDescriptor, TemporaryTable idTable, TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, + QueryOptions queryOptions, LoadQueryInfluencers loadQueryInfluencers, QueryParameterBindings queryParameterBindings, + Function sessionUidAccess, SessionFactoryImplementor sessionFactory + ) { + super( + entityDescriptor, idTable, temporaryTableStrategy, forceDropAfterUse, sqmDelete, domainParameterXref, queryOptions, loadQueryInfluencers, queryParameterBindings, sessionUidAccess, + sessionFactory + ); + } + + @Override + public CompletionStage reactiveExecute(DomainQueryExecutionContext domainQueryExecutionContext) { + final String targetEntityName = getSqmDelete().getTarget().getEntityName(); + final EntityPersister targetEntityDescriptor = getSessionFactory().getMappingMetamodel().getEntityDescriptor( targetEntityName ); + + final EntityMappingType rootEntityDescriptor = targetEntityDescriptor.getRootEntityDescriptor(); + + // determine if we need to use a sub-query for matching ids - + // 1. if the target is not the root we will + // 2. if the supplied predicate (if any) refers to columns from a table + // other than the identifier table we will + final SqmJdbcExecutionContextAdapter executionContext = omittingLockingAndPaging( domainQueryExecutionContext ); + + final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup(); + final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails(); + final NamedTableReference rootTableReference = (NamedTableReference) deletingTableGroup.resolveTableReference( + deletingTableGroup.getNavigablePath(), + softDeleteTable.getTableName() + ); + assert rootTableReference != null; + + // NOTE : `converter.visitWhereClause` already applies the soft-delete restriction + final Predicate specifiedRestriction = getConverter().visitWhereClause( getSqmDelete().getWhereClause() ); + + final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); + targetEntityDescriptor.applyBaseRestrictions( + predicateCollector, + deletingTableGroup, + true, + executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), + false, + null, + getConverter() + ); + + getConverter().pruneTableGroupJoins(); + final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( + rootTableReference.getIdentificationVariable() + ); + if ( predicateCollector.getPredicate() != null ) { + predicateCollector.getPredicate().accept( walker ); + } + + final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( + executionContext.getQueryParameterBindings(), + getDomainParameterXref(), + SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), getConverter() ), + new SqmParameterMappingModelResolutionAccess() { + @Override + @SuppressWarnings("unchecked") + public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { + return (MappingModelExpressible) getConverter().getSqmParameterMappingModelExpressibleResolutions().get( parameter ); + } + }, + executionContext.getSession() + ); + + final boolean needsSubQuery = !walker.isAllColumnReferencesFromIdentificationVariable() + || targetEntityDescriptor != rootEntityDescriptor; + + if ( needsSubQuery ) { + return getSessionFactory().getJdbcServices().getDialect().supportsSubqueryOnMutatingTable() + ? performDeleteWithSubQuery( rootEntityDescriptor, deletingTableGroup, rootTableReference, predicateCollector, jdbcParameterBindings, getConverter(), executionContext ) + : performDeleteWithIdTable( rootEntityDescriptor, rootTableReference, predicateCollector, jdbcParameterBindings, executionContext ); + } + return performDirectDelete( rootEntityDescriptor, rootTableReference, predicateCollector, jdbcParameterBindings, executionContext ); + } + + private CompletionStage performDeleteWithIdTable( + EntityMappingType rootEntityDescriptor, + NamedTableReference targetTableReference, + PredicateCollector predicateCollector, + JdbcParameterBindings jdbcParameterBindings, + SqmJdbcExecutionContextAdapter executionContext + ) { + ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( getIdTable(), getTemporaryTableStrategy(), executionContext ); + try { + return deleteUsingIdTable( rootEntityDescriptor, targetTableReference, predicateCollector, jdbcParameterBindings, executionContext ); + } + finally { + ReactiveExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( getIdTable(), getSessionUidAccess(), getAfterUseAction(), executionContext ); + } + } + + private CompletionStage deleteUsingIdTable( + EntityMappingType rootEntityDescriptor, + NamedTableReference targetTableReference, + PredicateCollector predicateCollector, + JdbcParameterBindings jdbcParameterBindings, + SqmJdbcExecutionContextAdapter executionContext + ) { + return ReactiveExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable( getConverter(), + predicateCollector.getPredicate(), + getIdTable(), + getSessionUidAccess(), + jdbcParameterBindings, + executionContext + ) + .thenCompose( rows -> { + final QuerySpec idTableIdentifierSubQuery = createIdTableSelectQuerySpec( + getIdTable(), + getSessionUidAccess(), + getEntityDescriptor(), + executionContext + ); + + return ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( + getEntityDescriptor(), + (tableReference, attributeMapping) -> { + final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); + final QuerySpec idTableFkSubQuery = fkDescriptor.getTargetPart().isEntityIdentifierMapping() + ? idTableIdentifierSubQuery + : createIdTableSelectQuerySpec( getIdTable(), fkDescriptor.getTargetPart(), getSessionUidAccess(), getEntityDescriptor(), executionContext ); + return new InSubQueryPredicate( + MappingModelCreationHelper.buildColumnReferenceExpression( + new MutatingTableReferenceGroupWrapper( + new NavigablePath( attributeMapping.getRootPathName() ), + attributeMapping, + (NamedTableReference) tableReference + ), + fkDescriptor, + null, + getSessionFactory() + ), + idTableFkSubQuery, + false + ); + + }, + JdbcParameterBindings.NO_BINDINGS, + executionContext + ).thenCompose( unused -> { + final Assignment softDeleteAssignment = rootEntityDescriptor + .getSoftDeleteMapping() + .createSoftDeleteAssignment( targetTableReference ); + final Expression idExpression = createIdExpression( rootEntityDescriptor, targetTableReference ); + final UpdateStatement updateStatement = new UpdateStatement( + targetTableReference, + singletonList( softDeleteAssignment ), + new InSubQueryPredicate( idExpression, idTableIdentifierSubQuery, false ) + ); + + return executeUpdate( updateStatement, jdbcParameterBindings, executionContext ); + } ) + .thenApply( v -> rows ); + } ); + } + + private static Expression createIdExpression(EntityMappingType rootEntityDescriptor, NamedTableReference targetTableReference) { + final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails(); + final TableDetails.KeyDetails keyDetails = softDeleteTable.getKeyDetails(); + final List idExpressions = new ArrayList<>( keyDetails.getColumnCount() ); + keyDetails.forEachKeyColumn( (position, column) -> idExpressions.add( new ColumnReference( targetTableReference, column ) ) ); + return idExpressions.size() == 1 + ? idExpressions.get( 0 ) + : new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() ); + } + + private CompletionStage performDeleteWithSubQuery( + EntityMappingType rootEntityDescriptor, + TableGroup deletingTableGroup, + NamedTableReference rootTableReference, + PredicateCollector predicateCollector, + JdbcParameterBindings jdbcParameterBindings, + MultiTableSqmMutationConverter converter, + SqmJdbcExecutionContextAdapter executionContext + ) { + final QuerySpec matchingIdSubQuery = new QuerySpec( false, 1 ); + matchingIdSubQuery.getFromClause().addRoot( deletingTableGroup ); + + final TableDetails identifierTableDetails = rootEntityDescriptor.getIdentifierTableDetails(); + final TableDetails.KeyDetails keyDetails = identifierTableDetails.getKeyDetails(); + + final NamedTableReference targetTable = new NamedTableReference( + identifierTableDetails.getTableName(), + DeleteStatement.DEFAULT_ALIAS, + false + ); + + final List idExpressions = new ArrayList<>( keyDetails.getColumnCount() ); + keyDetails.forEachKeyColumn( (position, column) -> { + final Expression columnReference = converter.getSqlExpressionResolver().resolveSqlExpression( + rootTableReference, + column + ); + matchingIdSubQuery.getSelectClause().addSqlSelection( + new SqlSelectionImpl( position, columnReference ) + ); + idExpressions.add( new ColumnReference( targetTable, column ) ); + } ); + + matchingIdSubQuery.applyPredicate( predicateCollector.getPredicate() ); + final Expression idExpression = idExpressions.size() == 1 + ? idExpressions.get( 0 ) + : new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() ); + + final Assignment softDeleteAssignment = rootEntityDescriptor + .getSoftDeleteMapping() + .createSoftDeleteAssignment( targetTable ); + + final UpdateStatement updateStatement = new UpdateStatement( + targetTable, + singletonList( softDeleteAssignment ), + new InSubQueryPredicate( idExpression, matchingIdSubQuery, false ) + ); + + return executeUpdate( updateStatement, jdbcParameterBindings, executionContext ); + } + + private CompletionStage performDirectDelete( + EntityMappingType rootEntityDescriptor, + NamedTableReference rootTableReference, + PredicateCollector predicateCollector, + JdbcParameterBindings jdbcParameterBindings, + SqmJdbcExecutionContextAdapter executionContext + ) { + + final Assignment softDeleteAssignment = rootEntityDescriptor.getSoftDeleteMapping().createSoftDeleteAssignment( rootTableReference ); + final UpdateStatement updateStatement = new UpdateStatement( rootTableReference, singletonList( softDeleteAssignment ), predicateCollector.getPredicate() ); + return executeUpdate( updateStatement, jdbcParameterBindings, executionContext ); + } + + private CompletionStage executeUpdate(UpdateStatement updateStatement, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); + final JdbcServices jdbcServices = factory.getJdbcServices(); + + final JdbcOperationQueryMutation jdbcUpdate = jdbcServices.getJdbcEnvironment() + .getSqlAstTranslatorFactory() + .buildMutationTranslator( factory, updateStatement ) + .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); + + return StandardReactiveJdbcMutationExecutor.INSTANCE + .executeReactive( + jdbcUpdate, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java index bb9ceff9c..d916e4910 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java @@ -10,12 +10,12 @@ import java.util.function.Function; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedDeleteHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; @@ -38,34 +38,56 @@ public ReactiveTableBasedDeleteHandler( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, SessionFactoryImplementor sessionFactory) { - super( sqmDeleteStatement, domainParameterXref, idTable, afterUseAction, sessionUidAccess, sessionFactory ); + super( + sqmDeleteStatement, + domainParameterXref, + idTable, + temporaryTableStrategy, + forceDropAfterUse, + sessionUidAccess, + sessionFactory + ); } @Override public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { if ( LOG.isTraceEnabled() ) { - LOG.tracef( - "Starting multi-table delete execution - %s", - getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() - ); + LOG.tracef( "Starting multi-table delete execution - %s", getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() ); } return resolveDelegate( executionContext ).reactiveExecute( executionContext ); } protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { + if ( getEntityDescriptor().getSoftDeleteMapping() != null ) { + return new ReactiveSoftDeleteExecutionDelegate( + getEntityDescriptor(), + getIdTable(), + getTemporaryTableStrategy(), + isForceDropAfterUse(), + getSqmDeleteOrUpdateStatement(), + getDomainParameterXref(), + executionContext.getQueryOptions(), + executionContext.getSession().getLoadQueryInfluencers(), + executionContext.getQueryParameterBindings(), + getSessionUidAccess(), + getSessionFactory() + ); + } return new ReactiveRestrictedDeleteExecutionDelegate( getEntityDescriptor(), getIdTable(), - getAfterUseAction(), + getTemporaryTableStrategy(), + isForceDropAfterUse(), getSqmDeleteOrUpdateStatement(), getDomainParameterXref(), - getSessionUidAccess(), executionContext.getQueryOptions(), executionContext.getSession().getLoadQueryInfluencers(), executionContext.getQueryParameterBindings(), + getSessionUidAccess(), getSessionFactory() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java index d697758ed..eb10dc8a4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java @@ -12,6 +12,7 @@ import java.util.function.Function; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.DomainQueryExecutionContext; @@ -19,7 +20,6 @@ import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedInsertHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; @@ -50,10 +50,11 @@ public ReactiveTableBasedInsertHandler( SqmInsertStatement sqmInsert, DomainParameterXref domainParameterXref, TemporaryTable entityTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, SessionFactoryImplementor sessionFactory) { - super( sqmInsert, domainParameterXref, entityTable, afterUseAction, sessionUidAccess, sessionFactory ); + super( sqmInsert, domainParameterXref, entityTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, sessionFactory ); } @Override @@ -81,12 +82,14 @@ protected ExecutionDelegate buildExecutionDelegate( SqmInsertStatement sqmInsert, MultiTableSqmMutationConverter sqmConverter, TemporaryTable entityTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, DomainParameterXref domainParameterXref, TableGroup insertingTableGroup, Map tableReferenceByAlias, List assignments, + boolean assignsId, InsertSelectStatement insertStatement, ConflictClause conflictClause, JdbcParameter sessionUidParameter, @@ -94,12 +97,14 @@ protected ExecutionDelegate buildExecutionDelegate( return new ReactiveInsertExecutionDelegate( sqmConverter, entityTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sessionUidAccess, domainParameterXref, insertingTableGroup, tableReferenceByAlias, assignments, + assignsId, insertStatement, conflictClause, sessionUidParameter, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java index 7379f962e..05a6d1511 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java @@ -12,6 +12,7 @@ import java.util.function.Function; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.spi.DomainQueryExecutionContext; @@ -19,7 +20,6 @@ import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedUpdateHandler; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; @@ -47,10 +47,11 @@ public ReactiveTableBasedUpdateHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, SessionFactoryImplementor sessionFactory) { - super( sqmUpdate, domainParameterXref, idTable, afterUseAction, sessionUidAccess, sessionFactory ); + super( sqmUpdate, domainParameterXref, idTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, sessionFactory ); } @Override @@ -80,7 +81,8 @@ protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext protected ReactiveUpdateExecutionDelegate buildExecutionDelegate( MultiTableSqmMutationConverter sqmConverter, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, DomainParameterXref domainParameterXref, TableGroup updatingTableGroup, @@ -91,7 +93,8 @@ protected ReactiveUpdateExecutionDelegate buildExecutionDelegate( return new ReactiveUpdateExecutionDelegate( sqmConverter, idTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sessionUidAccess, domainParameterXref, updatingTableGroup, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java index 126c168f8..2b3eda5f5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java @@ -23,7 +23,7 @@ import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.util.impl.CompletionStages; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; /** * @see org.hibernate.dialect.temptable.TemporaryTableHelper @@ -38,7 +38,7 @@ public class ReactiveTemporaryTableHelper { * @see org.hibernate.jdbc.Work */ public interface ReactiveWork { - CompletionStage reactiveExecute(ReactiveConnection connection); + CompletionStage reactiveExecute(ReactiveConnection connection); } public static class TemporaryTableCreationWork implements ReactiveWork { @@ -66,19 +66,22 @@ public TemporaryTableCreationWork( } @Override - public CompletionStage reactiveExecute(ReactiveConnection connection) { + public CompletionStage reactiveExecute(ReactiveConnection connection) { try { final String creationCommand = exporter.getSqlCreateCommand( temporaryTable ); return connection.executeUnprepared( creationCommand ) .handle( (integer, throwable) -> { + if ( throwable == null ) { + return true; + } logException( "create", creationCommand, temporaryTable, throwable ); - return null; + return false; } ); } catch (Exception e) { logException( "create", null, temporaryTable, e ); - return voidFuture(); + return falseFuture(); } } } @@ -111,19 +114,22 @@ public TemporaryTableDropWork( } @Override - public CompletionStage reactiveExecute(ReactiveConnection connection) { + public CompletionStage reactiveExecute(ReactiveConnection connection) { try { final String dropCommand = exporter.getSqlDropCommand( temporaryTable ); return connection.update( dropCommand ) .handle( (integer, throwable) -> { + if ( throwable == null ) { + return true; + } logException( "drop", dropCommand, temporaryTable, throwable ); - return null; + return false; } ); } catch (Exception e) { logException( "drop", null, temporaryTable, e ); - return voidFuture(); + return falseFuture(); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java index 927105dfa..454864b80 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java @@ -17,6 +17,7 @@ import java.util.function.Supplier; import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.SelectableConsumer; @@ -24,7 +25,6 @@ import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.UpdateExecutionDelegate; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; @@ -60,7 +60,8 @@ public class ReactiveUpdateExecutionDelegate extends UpdateExecutionDelegate imp public ReactiveUpdateExecutionDelegate( MultiTableSqmMutationConverter sqmConverter, TemporaryTable idTable, - AfterUseAction afterUseAction, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, Function sessionUidAccess, DomainParameterXref domainParameterXref, TableGroup updatingTableGroup, @@ -71,7 +72,8 @@ public ReactiveUpdateExecutionDelegate( super( sqmConverter, idTable, - afterUseAction, + temporaryTableStrategy, + forceDropAfterUse, sessionUidAccess, domainParameterXref, updatingTableGroup, @@ -94,6 +96,7 @@ public int execute(ExecutionContext executionContext) { public CompletionStage reactiveExecute(ExecutionContext executionContext) { return performBeforeTemporaryTableUseActions( getIdTable(), + getTemporaryTableStrategy(), executionContext ) .thenCompose( v -> saveMatchingIdsIntoIdTable( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index d67cc2018..63babefd0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -319,11 +319,12 @@ public void whenComplete(BiConsumer consumer) { private CompletionStage recreateCollections(Object entity, Object id, EntityPersister persister) { final Completable stage = new Completable<>(); + final String entityName = persister.getEntityName(); + final EventMonitor eventMonitor = getEventMonitor(); final Loop loop = new Loop(); forEachOwnedCollection( entity, id, persister, (descriptor, collection) -> { - firePreRecreate( collection, descriptor ); - final EventMonitor eventMonitor = getEventMonitor(); + firePreRecreate( collection, descriptor, entityName, entity ); final DiagnosticEvent event = eventMonitor.beginCollectionRecreateEvent(); loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) .reactiveRecreate( collection, id, this ) ) @@ -335,7 +336,7 @@ private CompletionStage recreateCollections(Object entity, Object id, Enti if ( statistics.isStatisticsEnabled() ) { statistics.recreateCollection( descriptor.getRole() ); } - firePostRecreate( collection, descriptor ); + firePostRecreate( collection, id, entityName, descriptor ); } ) ); } @@ -480,29 +481,35 @@ public CompletionStage reactiveDelete(Object entity) { } private CompletionStage removeCollections(Object entity, Object id, EntityPersister persister) { - final Completable stage = new Completable<>(); - final Loop loop = new Loop(); - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> { - firePreRemove( collection, entity, descriptor ); - final EventMonitor eventMonitor = getEventMonitor(); - final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); - loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) - .reactiveRemove( id, this ) ) - .whenComplete( (unused, throwable) -> eventMonitor - .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) - ) - .thenAccept( v -> { - firePostRemove( collection, entity, descriptor ); - final StatisticsImplementor statistics = getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.removeCollection( descriptor.getRole() ); - } - } ) - ); - } ); - loop.whenComplete( stage::complete ); - return stage.getStage(); + if ( persister.hasOwnedCollections() ) { + final Loop loop = new Loop(); + final Completable stage = new Completable<>(); + final String entityName = persister.getEntityName(); + forEachOwnedCollection( + entity, id, persister, + (descriptor, collection) -> { + firePreRemove( collection, id, entityName, entity ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); + loop.then( () -> supplyStage( () -> ( (ReactiveCollectionPersister) descriptor ) + .reactiveRemove( id, this ) ) + .whenComplete( (unused, throwable) -> eventMonitor + .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) + ) + .thenAccept( v -> { + firePostRemove( collection, id, entityName, entity ); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.removeCollection( descriptor.getRole() ); + } + } ) + ); + } + ); + loop.whenComplete( stage::complete ); + return stage.getStage(); + } + return voidFuture(); } @Override @@ -561,31 +568,37 @@ private CompletionStage executeReactiveUpdate(Object entity) { } private CompletionStage removeAndRecreateCollections(Object entity, Object id, EntityPersister persister) { - final Completable stage = new Completable<>(); - final Loop loop = new Loop(); - forEachOwnedCollection( entity, id, persister, - (descriptor, collection) -> { - firePreUpdate( collection, descriptor ); - final EventMonitor eventMonitor = getEventMonitor(); - final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); - ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) persister; - loop.then( () -> supplyStage( () -> reactivePersister - .reactiveRemove( id, this ) - .thenCompose( v -> reactivePersister.reactiveRecreate( collection, id, this ) ) ) - .whenComplete( (unused, throwable) -> eventMonitor - .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) - ) - .thenAccept( v -> { - firePostUpdate( collection, descriptor ); - final StatisticsImplementor statistics = getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.updateCollection( descriptor.getRole() ); - } - } ) - ); - } ); - loop.whenComplete( stage::complete ); - return stage.getStage(); + if ( persister.hasOwnedCollections() ) { + final String entityName = persister.getEntityName(); + final Completable stage = new Completable<>(); + final Loop loop = new Loop(); + forEachOwnedCollection( + entity, id, persister, + (descriptor, collection) -> { + firePreUpdate( collection, id, entityName, entity ); + final EventMonitor eventMonitor = getEventMonitor(); + final DiagnosticEvent event = eventMonitor.beginCollectionRemoveEvent(); + ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) persister; + loop.then( () -> supplyStage( () -> reactivePersister + .reactiveRemove( id, this ) + .thenCompose( v -> reactivePersister.reactiveRecreate( collection, id, this ) ) ) + .whenComplete( (unused, throwable) -> eventMonitor + .completeCollectionRemoveEvent( event, id, descriptor.getRole(), throwable != null, this ) + ) + .thenAccept( v -> { + firePostUpdate( collection, id, entityName, entity); + final StatisticsImplementor statistics = getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.updateCollection( descriptor.getRole() ); + } + } ) + ); + } + ); + loop.whenComplete( stage::complete ); + return stage.getStage(); + } + return voidFuture(); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java index a4b45101f..8b66a3bfb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/ReactiveResultSetMapping.java @@ -27,7 +27,7 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; /** - * @see org.hibernate.query.results.ResultSetMappingImpl + * @see org.hibernate.query.results.internal.ResultSetMappingImpl */ public class ReactiveResultSetMapping implements ResultSetMapping, ReactiveValuesMappingProducer { @@ -62,6 +62,11 @@ public CompletionStage reactiveResolve( .thenApply( columnCount -> delegate.resolve( jdbcResultsMetadata, loadQueryInfluencers, sessionFactory ) ); } + @Override + public ResultSetMapping cacheKeyInstance() { + return new ReactiveResultSetMapping( delegate.cacheKeyInstance() ); + } + @Override public String getMappingIdentifier() { return delegate.getMappingIdentifier(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java index 46a15c1e9..685717f35 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java @@ -38,10 +38,8 @@ public ReactiveEmbeddableFetchImpl(EmbeddableFetchImpl original) { } @Override - public EmbeddableInitializer createInitializer( - InitializerParent parent, - AssemblerCreationState creationState) { - return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), parent, creationState, true ); + public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), getNullIndicatorResult(), parent, creationState, true ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java index 1e3433320..426345ab5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java @@ -20,7 +20,7 @@ public ReactiveEmbeddableForeignKeyResultImpl(EmbeddableForeignKeyResultImpl @Override public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { return getReferencedModePart() instanceof NonAggregatedIdentifierMapping - ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) - : new ReactiveEmbeddableInitializerImpl( this, null, null, creationState, true ); + ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) + : new ReactiveEmbeddableInitializerImpl( this, null, null, null, creationState, true ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java index 02b3a76d1..6d5fbcd5e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -16,6 +16,7 @@ import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.InitializerData; @@ -66,10 +67,11 @@ public EmbeddableMappingType.ConcreteEmbeddableType getConcreteEmbeddableType() public ReactiveEmbeddableInitializerImpl( EmbeddableResultGraphNode resultDescriptor, BasicFetch discriminatorFetch, + DomainResult nullIndicatorResult, InitializerParent parent, AssemblerCreationState creationState, boolean isResultInitializer) { - super( resultDescriptor, discriminatorFetch, parent, creationState, isResultInitializer ); + super( resultDescriptor, discriminatorFetch, nullIndicatorResult, parent, creationState, isResultInitializer ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java index ac119a7d0..8de288c0e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java @@ -7,14 +7,15 @@ import java.lang.invoke.MethodHandles; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; +import org.hibernate.JDBCException; import org.hibernate.LockOptions; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -65,6 +66,17 @@ public ReactiveDeferredResultSetAccess( this.sqlStatementLogger = executionContext.getSession().getJdbcServices().getSqlStatementLogger(); } + @Override + public JdbcServices getJdbcServices() { + return getFactory().getJdbcServices(); + } + + @Override + public JDBCException convertSqlException(SQLException e, String message) { + return getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper() + .convert( e, message ); + } + /** * Reactive version of {@link org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess#registerAfterLoadAction(ExecutionContext, LockOptions)} * calling {@link ReactiveSession#reactiveLock(String, Object, LockOptions)} @@ -141,20 +153,6 @@ private JdbcValuesMetadata convertToMetadata(ResultSet resultSet) { return (JdbcValuesMetadata) resultSet; } - @Override - public CompletionStage getReactiveMetadata() { - return getReactiveResultSet().thenApply( this::reactiveMetadata ); - } - - private ResultSetMetaData reactiveMetadata(ResultSet resultSet) { - try { - return resultSet.getMetaData(); - } - catch (SQLException e) { - throw new RuntimeException( e ); - } - } - private static int columnCount(ResultSet resultSet) { try { return resultSet.getMetaData().getColumnCount(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java index 2412f3941..80d3217aa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java @@ -8,9 +8,11 @@ import java.lang.invoke.MethodHandles; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.ResultSetMetaData; +import java.sql.SQLException; import java.util.concurrent.CompletionStage; +import org.hibernate.JDBCException; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.reactive.logging.impl.Log; @@ -40,8 +42,13 @@ public ReactiveDirectResultSetAccess( } @Override - public SessionFactoryImplementor getFactory() { - return getPersistenceContext().getFactory(); + public JdbcServices getJdbcServices() { + return getFactory().getJdbcServices(); + } + + @Override + public JDBCException convertSqlException(SQLException e, String message) { + return getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( e, message ); } @Override @@ -73,11 +80,6 @@ public CompletionStage getReactiveResultSet() { return completedFuture( resultSet ); } - @Override - public CompletionStage getReactiveMetadata() { - return completedFuture( getMetaData() ); - } - @Override public CompletionStage getReactiveColumnCount() { return completedFuture( getColumnCount() ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java index 75a265ed5..ad9be5a6a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java @@ -10,9 +10,9 @@ import java.sql.SQLException; import java.util.concurrent.CompletionStage; +import org.hibernate.JDBCException; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.java.JavaType; @@ -27,14 +27,14 @@ */ public interface ReactiveResultSetAccess extends JdbcValuesMetadata { CompletionStage getReactiveResultSet(); - CompletionStage getReactiveMetadata(); CompletionStage getReactiveColumnCount(); CompletionStage resolveJdbcValueMetadata(); ResultSet getResultSet(); - SessionFactoryImplementor getFactory(); + JdbcServices getJdbcServices(); + void release(); /** @@ -51,43 +51,35 @@ default int getColumnCount() { return getResultSet().getMetaData().getColumnCount(); } catch (SQLException e) { - throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( - e, - "Unable to access ResultSet column count" - ); + throw convertSqlException( e, "Unable to access ResultSet column count" ); } } + JDBCException convertSqlException(SQLException e, String message); + default int resolveColumnPosition(String columnName) { try { return getResultSet().findColumn( columnName ); } catch (SQLException e) { - throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( - e, - "Unable to find column position by name" - ); + throw convertSqlException( e, "Unable to find column position by name" ); } } default String resolveColumnName(int position) { try { - return getFactory().getJdbcServices().getJdbcEnvironment() + return getJdbcServices().getJdbcEnvironment() .getDialect() .getColumnAliasExtractor() .extractColumnAlias( getResultSet().getMetaData(), position ); } catch (SQLException e) { - throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( - e, - "Unable to find column name by position" - ); + throw convertSqlException( e, "Unable to find column name by position" ); } } @Override default BasicType resolveType(int position, JavaType explicitJavaType, TypeConfiguration typeConfiguration) { - final JdbcServices jdbcServices = getFactory().getJdbcServices(); try { final ResultSetMetaData metaData = getResultSet().getMetaData(); final String columnTypeName = metaData.getColumnTypeName( position ); @@ -95,7 +87,7 @@ default BasicType resolveType(int position, JavaType explicitJavaType, final int scale = metaData.getScale( position ); final int precision = metaData.getPrecision( position ); final int displaySize = metaData.getColumnDisplaySize( position ); - final Dialect dialect = jdbcServices.getDialect(); + final Dialect dialect = getJdbcServices().getDialect(); final int length = dialect.resolveSqlTypeLength( columnTypeName, columnType, @@ -104,13 +96,7 @@ default BasicType resolveType(int position, JavaType explicitJavaType, displaySize ); final JdbcType resolvedJdbcType = dialect - .resolveSqlTypeDescriptor( - columnTypeName, - columnType, - length, - scale, - typeConfiguration.getJdbcTypeRegistry() - ); + .resolveSqlTypeDescriptor( columnTypeName, columnType, length, scale, typeConfiguration.getJdbcTypeRegistry() ); final JavaType javaType; final JdbcType jdbcType; // If there is an explicit JavaType, then prefer its recommended JDBC type @@ -145,26 +131,19 @@ public EnumType getEnumeratedType() { @Override public Dialect getDialect() { - return getFactory().getJdbcServices().getDialect(); + return getJdbcServices().getDialect(); } } ); } else { jdbcType = resolvedJdbcType; - javaType = jdbcType.getJdbcRecommendedJavaTypeMapping( - length, - scale, - typeConfiguration - ); + javaType = jdbcType.getJdbcRecommendedJavaTypeMapping( length, scale, typeConfiguration ); } return typeConfiguration.getBasicTypeRegistry().resolve( javaType, jdbcType ); } catch (SQLException e) { - throw jdbcServices.getSqlExceptionHelper().convert( - e, - "Unable to determine JDBC type code for ResultSet position " + position - ); + throw convertSqlException( e, "Unable to determine JDBC type code for ResultSet position " + position ); } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java index 81af936ba..ab84952ef 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveJsonArrayJdbcType.java @@ -5,26 +5,27 @@ */ package org.hibernate.reactive.type.descriptor.jdbc; -import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JsonHelper; -import org.hibernate.type.descriptor.jdbc.JsonJdbcType; +import org.hibernate.type.descriptor.jdbc.spi.JsonGeneratingVisitor; +import org.hibernate.type.format.StringJsonDocumentWriter; import io.vertx.core.json.JsonArray; -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; /** * @see org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType @@ -65,21 +66,24 @@ protected X fromString(String string, JavaType javaType, WrapperOptions o if ( string == null ) { return null; } - - return JsonHelper.arrayFromString( javaType, getElementJdbcType(), string, options ); + if ( ((BasicPluralJavaType) javaType).getElementJavaType() instanceof UnknownBasicJavaType ) { + return options.getJsonFormatMapper().fromString( string, javaType, options ); + } + else { + return JsonHelper.arrayFromString( javaType, this.getElementJdbcType(), string, options ); + } } protected String toString(X value, JavaType javaType, WrapperOptions options) { - final JdbcType elementJdbcType = getElementJdbcType(); - final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); - if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { - final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); - return JsonHelper.arrayToString( embeddableMappingType, domainObjects, options ); + final JavaType elementJavaType = ( (BasicPluralJavaType) javaType ).getElementJavaType(); + if ( elementJavaType instanceof UnknownBasicJavaType ) { + return options.getJsonFormatMapper().toString( value, javaType, options); } else { - assert !( elementJdbcType instanceof AggregateJdbcType ); - final JavaType elementJavaType = ( (BasicPluralJavaType) javaType ).getElementJavaType(); - return JsonHelper.arrayToString( elementJavaType, elementJdbcType, domainObjects, options ); + final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); + final StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); + JsonGeneratingVisitor.INSTANCE.visitArray( elementJavaType, getElementJdbcType(), domainObjects, options, writer ); + return writer.getJson(); } } @@ -121,12 +125,8 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o } private X getObject(Object array, WrapperOptions options) throws SQLException { - if ( array == null ) { - return null; - } - - return ( (ReactiveJsonArrayJdbcType) getJdbcType() ) - .fromString( ( (JsonArray) array ).encode(), getJavaType(), options ); + final String json = array == null ? null : ( (JsonArray) array ).encode(); + return ( (ReactiveJsonArrayJdbcType) getJdbcType() ).fromString( json, getJavaType(), options ); } }; } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java new file mode 100644 index 000000000..f1c08d4ce --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ImplicitSoftDeleteTests.java @@ -0,0 +1,225 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletionStage; + +import org.hibernate.ObjectNotFoundException; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.SoftDelete; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Tuple; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.common.Identifier.id; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +@Timeout(value = 10, timeUnit = MINUTES) +public class ImplicitSoftDeleteTests extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( ImplicitEntity.class ); + } + + @Override + protected CompletionStage cleanDb() { + return voidFuture(); + } + + @BeforeEach + void createTestData(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createNativeQuery( "delete from implicit_entities" ).executeUpdate() ) + .call( () -> getMutinySessionFactory().withTransaction( s -> s + .persistAll( new ImplicitEntity( 1, "first" ), new ImplicitEntity( 2, "second" ), new ImplicitEntity( 3, "third" ) ) + ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity first = s.getReference( ImplicitEntity.class, 1 ); + return s.remove( first ).call( s::flush ); + } ) ) + .call( () -> getMutinySessionFactory() + .withTransaction( s -> s.createNativeQuery( "select * from implicit_entities e order by id", Tuple.class ).getResultList() ) + .invoke( tuples -> assertThat( tuples ).hasSize( 3 ) ) + ) + ); + } + + @Test + void testSelectionQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createQuery( "from ImplicitEntity", ImplicitEntity.class ).getResultList() ) + .invoke( list -> assertThat( list ).hasSize( 2 ) ) + ); + } + + @Test + void testLoading(VertxTestContext context) { + test( + context, getMutinySessionFactory() + // Load + .withTransaction( s -> s + .find( ImplicitEntity.class, 1 ).invoke( entity -> assertThat( entity ).isNull() ) + .call( () -> s.find( ImplicitEntity.class, 2 ).invoke( entity -> assertThat( entity ).isNotNull() ) ) + .call( () -> s.find( ImplicitEntity.class, 3 ).invoke( entity -> assertThat( entity ).isNotNull() ) ) + ) + // Proxy + // We deleted the entity, so we expect an ObjectNotFoundException + .chain( () -> assertThrown( ObjectNotFoundException.class, getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 1 ); + return s.fetch( reference ).map( ImplicitEntity::getName ); + } ) ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 2 ); + return s.fetch( reference ).map( ImplicitEntity::getName ); + } ) ) + .invoke( name -> assertThat( name ).isEqualTo( "second" ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 3 ); + return s.fetch( reference ).map( ImplicitEntity::getName ); + } ) ) + .invoke( name -> assertThat( name ).isEqualTo( "third" ) ) + ); + } + + @Test + void testMultiLoading(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( ImplicitEntity.class, 1, 2, 3 ) ) + .invoke( list -> assertThat( list ) + .containsExactly( null, new ImplicitEntity( null, "second" ), new ImplicitEntity( null, "third" ) ) ) + ); + } + + @Test + void testNaturalIdLoading(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.find( ImplicitEntity.class, id( "name", "first" ) ) ) + .invoke( entity -> assertThat( entity ).isNull() ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s.find( ImplicitEntity.class, id( "name", "second" ) ) ) ) + .invoke( entity -> assertThat( entity ).extracting( ImplicitEntity::getId ).isEqualTo( 2 ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s.find( ImplicitEntity.class, id( "name", "third" ) ) ) ) + .invoke( entity -> assertThat( entity ).extracting( ImplicitEntity::getId ).isEqualTo( 3 ) ) + ); + } + + @Test + void testDeletion(VertxTestContext context) { + test( + context, getMutinySessionFactory().withTransaction( s -> { + final ImplicitEntity reference = s.getReference( ImplicitEntity.class, 2 ); + return s.remove( reference ).call( s::flush ) + .call( () -> s.createSelectionQuery( "from ImplicitEntity", ImplicitEntity.class ).getResultList() + // #1 was "deleted" up front and we just "deleted" #2... only #3 should be active + .invoke( list -> { + assertThat( list ).extracting( ImplicitEntity::getId ).containsExactly( 3 ); + assertThat( list ).extracting( ImplicitEntity::getName ).containsExactly( "third" ); + } ) + ); + } ) + ); + } + + @Test + void testFullUpdateMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "update ImplicitEntity set name = null" ).executeUpdate() ) + .invoke( affected -> assertThat( affected ).isEqualTo( 2 ) ) + ); + } + + @Test + void testRestrictedUpdateMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "update ImplicitEntity set name = null where name = 'second'" ).executeUpdate() ) + .invoke( affected -> assertThat( affected ).isEqualTo( 1 ) ) + ); + } + + @Test + void testFullDeleteMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "delete ImplicitEntity" ).executeUpdate() ) + // only #2 and #3 + .invoke( affected -> assertThat( affected ).isEqualTo( 2 ) ) + ); + } + + @Test + void testRestrictedDeleteMutationQuery(VertxTestContext context) { + test( + context, getMutinySessionFactory() + .withTransaction( s -> s.createMutationQuery( "delete ImplicitEntity where name = 'second'" ).executeUpdate() ) + // only #2 + .invoke( affected -> assertThat( affected ).isEqualTo( 1 ) ) + ); + } + + + @Entity(name = "ImplicitEntity") + @Table(name = "implicit_entities") + @SoftDelete + public static class ImplicitEntity { + @Id + private Integer id; + @NaturalId + private String name; + + public ImplicitEntity() { + } + + public ImplicitEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object object) { + if ( object == null || getClass() != object.getClass() ) { + return false; + } + ImplicitEntity that = (ImplicitEntity) object; + return Objects.equals( name, that.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + } +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java index 3d22d0e71..73233b5e9 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java @@ -146,14 +146,14 @@ private String getIsbn() { private Author author; public Book(String isbn, String title, Author author) { - super( "Book", 1, singleton( "isbn" ), null ); + super( new EntityRelatedState( "Book", singleton( "isbn" ) ), 1, null ); this.title = title; this.isbn = isbn; this.author = author; } public Book() { - super( "Book", 1, singleton( "isbn" ), null ); + super( new EntityRelatedState( "Book", singleton( "isbn" ) ), 1, null ); } @Override diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java index 8dcc07a6a..8cf8e6931 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UpsertTest.java @@ -144,14 +144,10 @@ private void assertQueries() { } private boolean hasMergeOperator() { - switch ( dbType() ) { - case SQLSERVER: - case ORACLE: - case POSTGRESQL: - return true; - default: - return false; - } + return switch ( dbType() ) { + case SQLSERVER, ORACLE, POSTGRESQL, DB2 -> true; + default -> false; + }; } @Entity(name = "Record") diff --git a/integration-tests/bytecode-enhancements-it/build.gradle b/integration-tests/bytecode-enhancements-it/build.gradle index cd9a23e52..c29c0f376 100644 --- a/integration-tests/bytecode-enhancements-it/build.gradle +++ b/integration-tests/bytecode-enhancements-it/build.gradle @@ -38,8 +38,11 @@ dependencies { testImplementation(libs.io.vertx.vertx.junit5) } -// Optional: enable the bytecode enhancements -hibernate { enhancement } +hibernate { + enhancement { + // We want everything enabled for the tests + } +} // Print a summary of the results of the tests (number of failures, successes and skipped) // This is the same as the one in hibernate-reactive-core diff --git a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java index 882231ad1..462db5f98 100644 --- a/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java +++ b/integration-tests/verticle-postgres-it/src/main/java/org/hibernate/reactive/it/verticle/ProductVerticle.java @@ -40,19 +40,10 @@ public ProductVerticle(Supplier emfSupplier) { this.emfSupplier = emfSupplier; } - private void startHibernate(Promise p) { - try { - this.emf = emfSupplier.get(); - p.complete(); - } - catch (Throwable t) { - p.fail( t ); - } - } - @Override public void start(Promise startPromise) { - final Future startHibernate = vertx.executeBlocking( this::startHibernate ) + final Future startHibernate = vertx + .executeBlocking( this::startHibernate ) .onSuccess( s -> LOG.infof( "✅ Hibernate Reactive is ready" ) ); Router router = Router.router( vertx ); @@ -74,9 +65,15 @@ public void start(Promise startPromise) { .onFailure( startPromise::fail ); } + private Mutiny.SessionFactory startHibernate() { + this.emf = emfSupplier.get(); + return this.emf; + } + @Override public void stop(Promise stopPromise) { - httpServer.close().onComplete( unused -> emf.close() ) + httpServer.close() + .onComplete( unused -> emf.close() ) .onSuccess( s -> stopPromise.complete() ) .onFailure( stopPromise::fail ); } diff --git a/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java b/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java index 0c819a393..4c332341b 100644 --- a/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java +++ b/integration-tests/verticle-postgres-it/src/test/java/org/hibernate/reactive/it/LocalContextTest.java @@ -31,7 +31,6 @@ import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; -import static io.vertx.core.CompositeFuture.all; import static io.vertx.core.Future.all; import static io.vertx.core.Future.failedFuture; import static io.vertx.core.Future.succeededFuture; @@ -50,7 +49,7 @@ * that's been close ahead of time by someone else. * Theoretically, everything could happen in the right order because of chance, * but it's unlikely and at the moment I don't have a better solution. - * See the the related issue + * See the related issue * for more details. *

*/ @@ -88,7 +87,7 @@ public void testProductsGeneration(VertxTestContext context) { .compose( this::findProducts ) .onSuccess( res -> context.completeNow() ) .onFailure( context::failNow ) - .eventually( unused -> vertx.close() ); + .eventually( () -> vertx.close() ); } /** @@ -97,7 +96,7 @@ public void testProductsGeneration(VertxTestContext context) { * @see #REQUEST_NUMBER */ private Future createProducts(WebClient webClient) { - List postRequests = new ArrayList<>(); + List> postRequests = new ArrayList<>(); for ( int i = 0; i < REQUEST_NUMBER; i++ ) { final Product product = new Product( i + 1 ); diff --git a/settings.gradle b/settings.gradle index 159d91e20..9e3962b53 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,13 +24,13 @@ def GRADLE_MAX_SUPPORTED_BYTECODE_VERSION = 23 dependencyResolutionManagement { versionCatalogs { create("libs") { - // ./gradlew build -PhibernateOrmVersion=7.0.2.Final + // ./gradlew build -PhibernateOrmVersion=7.1.0.CR1 def hibernateOrmVersion = settings.ext.find("hibernateOrmVersion") ?: "" if ( hibernateOrmVersion != "" ) { version("hibernateOrmVersion", hibernateOrmVersion) } - // ./gradlew build -PhibernateOrmGradlePluginVersion=7.0.2.Final + // ./gradlew build -PhibernateOrmGradlePluginVersion=7.1.0.CR1 def hibernateOrmGradlePluginVersion = settings.ext.find("hibernateOrmGradlePluginVersion") ?: "" if ( hibernateOrmGradlePluginVersion ) { version("hibernateOrmGradlePluginVersion", hibernateOrmGradlePluginVersion) From 8607230c9a9ae76308afe363dc76ace1317aa8c9 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 1 Aug 2025 09:53:27 +0200 Subject: [PATCH 062/185] [#2387] Downgrade Hibernate Gradle plugin to 7.0.8.Final The latest Hibernate Gradle plugin requires at list JVM 22. This downgrade won't affect the released artifact. --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4da2de31f..f6eea082a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.3" hibernateOrmVersion = "7.1.0.CR1" -hibernateOrmGradlePluginVersion = "7.1.0.CR1" +hibernateOrmGradlePluginVersion = "7.0.8.Final" jacksonDatabindVersion = "2.19.2" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" From 3d594e8a20d32e9a9bab1b7a624d49e926f003da Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Fri, 1 Aug 2025 08:59:38 +0000 Subject: [PATCH 063/185] Update project version to : `3.1.0.CR1` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 3cb2681d8..164707f40 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.0-SNAPSHOT +projectVersion=3.1.0.CR1 \ No newline at end of file From 3c0b87744954ce003676ee1602860a08444cee2f Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Fri, 1 Aug 2025 09:00:35 +0000 Subject: [PATCH 064/185] Update project version to : `3.1.0-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 164707f40..93f7caab8 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.0.CR1 \ No newline at end of file +projectVersion=3.1.0-SNAPSHOT \ No newline at end of file From 64211046b0ecd7df8014216b9b3b576340202eb5 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 4 Aug 2025 11:08:53 +0200 Subject: [PATCH 065/185] [#2404] Upgrade Hibernate ORM to 7.1.0.CR2 --- gradle/libs.versions.toml | 2 +- .../impl/ReactiveAbstractEntityPersister.java | 19 +- .../sql/spi/ReactiveNamedSqmQueryMemento.java | 6 +- .../ConcreteSqmSelectReactiveQueryPlan.java | 274 +++----- ...veAbstractMultiTableMutationQueryPlan.java | 35 + .../ReactiveMultiTableDeleteQueryPlan.java | 25 +- .../ReactiveMultiTableInsertQueryPlan.java | 25 +- .../ReactiveMultiTableUpdateQueryPlan.java | 28 +- .../ReactiveSimpleDeleteQueryPlan.java | 232 ++----- .../ReactiveSimpleInsertQueryPlan.java | 121 ---- .../ReactiveSimpleNonSelectQueryPlan.java | 56 ++ .../ReactiveSimpleUpdateQueryPlan.java | 117 ---- ...SqmImpl.java => ReactiveSqmQueryImpl.java} | 169 ++--- .../mutation/internal/ReactiveHandler.java | 32 +- .../ReactiveSqmMutationStrategyHelper.java | 161 ----- .../ReactiveAbstractCteMutationHandler.java | 180 +----- .../cte/ReactiveCteDeleteHandler.java | 41 +- .../cte/ReactiveCteInsertHandler.java | 534 +--------------- .../cte/ReactiveCteInsertStrategy.java | 23 +- .../cte/ReactiveCteMutationStrategy.java | 67 +- .../cte/ReactiveCteSoftDeleteHandler.java | 43 ++ .../cte/ReactiveCteUpdateHandler.java | 42 +- .../cte/ReactiveInsertExecutionDelegate.java | 72 --- ...activeExecuteWithTemporaryTableHelper.java | 249 ++----- ...iveGlobalTemporaryTableInsertStrategy.java | 22 +- ...eGlobalTemporaryTableMutationStrategy.java | 95 ++- ...tiveLocalTemporaryTableInsertStrategy.java | 35 +- ...veLocalTemporaryTableMutationStrategy.java | 79 ++- ...ReactivePersistentTableInsertStrategy.java | 20 +- ...activePersistentTableMutationStrategy.java | 83 ++- ...tiveRestrictedDeleteExecutionDelegate.java | 605 ------------------ .../ReactiveSoftDeleteExecutionDelegate.java | 320 --------- .../ReactiveTableBasedDeleteHandler.java | 142 ++-- .../ReactiveTableBasedInsertHandler.java | 422 ++++++++++-- .../ReactiveTableBasedSoftDeleteHandler.java | 129 ++++ .../ReactiveTableBasedUpdateHandler.java | 125 ++-- .../ReactiveTemporaryTableHelper.java | 2 - .../ReactiveUpdateExecutionDelegate.java | 295 --------- .../spi/ReactiveAbstractMutationHandler.java | 3 - .../ReactiveSqmMultiTableInsertStrategy.java | 16 +- ...ReactiveSqmMultiTableMutationStrategy.java | 32 +- .../session/impl/ReactiveSessionImpl.java | 18 +- .../impl/ReactiveStatelessSessionImpl.java | 14 +- 43 files changed, 1543 insertions(+), 3467 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java rename hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/{ReactiveQuerySqmImpl.java => ReactiveSqmQueryImpl.java} (80%) delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveSoftDeleteExecutionDelegate.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f6eea082a..9840d6432 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] assertjVersion = "3.27.3" -hibernateOrmVersion = "7.1.0.CR1" +hibernateOrmVersion = "7.1.0.CR2" hibernateOrmGradlePluginVersion = "7.0.8.Final" jacksonDatabindVersion = "2.19.2" jbossLoggingAnnotationVersion = "3.0.4.Final" diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java index 8e96291eb..129b9e236 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java @@ -80,7 +80,6 @@ import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.logSqlException; import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -303,25 +302,25 @@ default CompletionStage reactiveGetCurrentVersion(Object id, SharedSessi return getReactiveConnection( session ) .selectJdbc( delegate().getVersionSelectString(), params ) - .thenCompose( resultSet -> currentVersion( session, resultSet ) ); + .thenApply( resultSet -> currentVersion( session, resultSet ) ); } - private CompletionStage currentVersion(SharedSessionContractImplementor session, ResultSet resultSet) { + private Object currentVersion(SharedSessionContractImplementor session, ResultSet resultSet) { try { if ( !resultSet.next() ) { - return nullFuture(); + return null; } if ( !isVersioned() ) { - return completedFuture( this ); + return this; } - return completedFuture( getVersionType() - .getJdbcMapping() - .getJdbcValueExtractor() - .extract( resultSet, 1, session ) ); + return getVersionType() + .getJdbcMapping() + .getJdbcValueExtractor() + .extract( resultSet, 1, session ); } catch (SQLException sqle) { //can never happen - return failedFuture( new JDBCException( "error reading version", sqle ) ); + throw new JDBCException( "error reading version", sqle ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java index 388488b26..aae4316a3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/spi/ReactiveNamedSqmQueryMemento.java @@ -20,7 +20,7 @@ import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; +import org.hibernate.reactive.query.sqm.internal.ReactiveSqmQueryImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; /** @@ -49,10 +49,10 @@ public SqmQueryImplementor toQuery(SharedSessionContractImplementor session) public SqmQueryImplementor toQuery(SharedSessionContractImplementor session, Class resultType) { // A bit of a hack, I'm sure that if we have a better look at this we can avoid the instanceof if ( delegate instanceof NamedHqlQueryMementoImpl ) { - return new ReactiveQuerySqmImpl<>( (NamedHqlQueryMementoImpl) delegate, resultType, session ); + return new ReactiveSqmQueryImpl<>( (NamedHqlQueryMementoImpl) delegate, resultType, session ); } if ( delegate instanceof NamedCriteriaQueryMementoImpl ) { - return new ReactiveQuerySqmImpl<>( (NamedCriteriaQueryMementoImpl) delegate, resultType, session ); + return new ReactiveSqmQueryImpl<>( (NamedCriteriaQueryMementoImpl) delegate, resultType, session ); } else { throw new UnsupportedOperationException( "NamedSqmQueryMemento not recognized: " + delegate.getClass() ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java index 7c7cfd062..81f1addf5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java @@ -6,40 +6,26 @@ package org.hibernate.reactive.query.sqm.internal; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; import org.hibernate.ScrollMode; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; -import org.hibernate.metamodel.mapping.MappingModelExpressible; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.Query; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.spi.ScrollableResultsImplementor; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.sql.SqmTranslator; -import org.hibernate.query.sqm.sql.SqmTranslatorFactory; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.query.sqm.spi.ReactiveSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; @@ -68,7 +54,7 @@ public class ConcreteSqmSelectReactiveQueryPlan extends ConcreteSqmSelectQuer private final SqmSelectStatement sqm; private final DomainParameterXref domainParameterXref; - private volatile CacheableSqmInterpretation cacheableSqmInterpretation; + private volatile CacheableSqmInterpretation cacheableSqmInterpretation; public ConcreteSqmSelectReactiveQueryPlan( SqmSelectStatement sqm, @@ -91,27 +77,46 @@ private static CompletionStage> listInterpreter( String hql, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, RowTransformer rowTransformer) { final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); - // I'm using a supplier so that the whenComplete at the end will catch any errors, like a finally-block - Supplier fetchHandlerSupplier = () -> SubselectFetch - .createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.selectStatement, JdbcParametersList.empty(), jdbcParameterBindings ); - return completedFuture( fetchHandlerSupplier ) - .thenApply( Supplier::get ) - .thenCompose( subSelectFetchKeyHandler -> session + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); + + return CompletionStages + .supplyStage( () -> { + final var subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( + session.getPersistenceContext() + .getBatchFetchQueue(), + sqmInterpretation.statement(), + JdbcParametersList.empty(), + jdbcParameterBindings + ); + return session .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) - .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE - .list( jdbcSelect, - jdbcParameterBindings, - ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), - rowTransformer, - ReactiveListResultsConsumer.UniqueSemantic.ALLOW - ) - ) - ) + .thenCompose( required -> { + final Expression fetchExpression = sqmInterpretation.statement() + .getQueryPart() + .getFetchClauseExpression(); + final int resultCountEstimate = fetchExpression != null + ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) + : -1; + return StandardReactiveSelectExecutor.INSTANCE.list( + jdbcSelect, + jdbcParameterBindings, + ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( + hql, + executionContext, + jdbcSelect, + subSelectFetchKeyHandler + ), + rowTransformer, + (Class) executionContext.getResultType(), + ReactiveListResultsConsumer.UniqueSemantic.ALLOW, + resultCountEstimate + ); + } ); + } ) .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } @@ -119,43 +124,50 @@ private static CompletionStage executeQueryInterpreter( String hql, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, RowTransformer rowTransformer, ReactiveResultsConsumer resultsConsumer) { final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect(); + final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); // I'm using a supplier so that the whenComplete at the end will catch any errors, like a finally-block - Supplier fetchHandlerSupplier = () -> SubselectFetch - .createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.selectStatement, JdbcParametersList.empty(), jdbcParameterBindings ); - return completedFuture( fetchHandlerSupplier ) - .thenApply( Supplier::get ) - .thenCompose( subSelectFetchKeyHandler -> session - .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) - .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE - .executeQuery( - jdbcSelect, - jdbcParameterBindings, - ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( - hql, - executionContext, - jdbcSelect, - subSelectFetchKeyHandler - ), - rowTransformer, - null, - resultCountEstimate( sqmInterpretation, jdbcParameterBindings ), - resultsConsumer - ) - ) - ) + final Supplier fetchHandlerSupplier = () -> SubselectFetch + .createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.statement(), JdbcParametersList.empty(), jdbcParameterBindings ); + return CompletionStages + .supplyStage( () -> { + final var subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( + session.getPersistenceContext() + .getBatchFetchQueue(), + sqmInterpretation.statement(), + JdbcParametersList.empty(), + jdbcParameterBindings + ); + return session + .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) + .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE + .executeQuery( + jdbcSelect, + jdbcParameterBindings, + ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( + hql, + executionContext, + jdbcSelect, + subSelectFetchKeyHandler + ), + rowTransformer, + null, + resultCountEstimate( sqmInterpretation, jdbcParameterBindings ), + resultsConsumer + ) + ); + }) .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } private static int resultCountEstimate( - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings) { - final Expression fetchExpression = sqmInterpretation.selectStatement.getQueryPart() + final Expression fetchExpression = sqmInterpretation.statement().getQueryPart() .getFetchClauseExpression(); return fetchExpression != null ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) @@ -191,16 +203,16 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec // to protect access. However, synchronized is much simpler here. We will verify // during throughput testing whether this is an issue and consider changes then - CacheableSqmInterpretation localCopy = cacheableSqmInterpretation; + CacheableSqmInterpretation localCopy = cacheableSqmInterpretation; JdbcParameterBindings jdbcParameterBindings = null; if ( localCopy == null ) { synchronized ( this ) { localCopy = cacheableSqmInterpretation; if ( localCopy == null ) { - localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext ); - jdbcParameterBindings = localCopy.firstParameterBindings; - localCopy.firstParameterBindings = null; + final MutableObject mutableValue = new MutableObject<>(); + localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue ); + jdbcParameterBindings = mutableValue.get(); cacheableSqmInterpretation = localCopy; } } @@ -208,15 +220,15 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec else { // If the translation depends on parameter bindings, or it isn't compatible with the current query options, // we have to rebuild the JdbcSelect, which is still better than having to translate from SQM to SQL AST again - if ( localCopy.jdbcSelect.dependsOnParameterBindings() ) { + if ( localCopy.jdbcOperation().dependsOnParameterBindings() ) { jdbcParameterBindings = createJdbcParameterBindings( localCopy, executionContext ); } // If the translation depends on the limit or lock options, we have to rebuild the JdbcSelect // We could avoid this by putting the lock options into the cache key - if ( !localCopy.jdbcSelect.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - localCopy = buildCacheableSqmInterpretation( sqm, domainParameterXref, executionContext ); - jdbcParameterBindings = localCopy.firstParameterBindings; - localCopy.firstParameterBindings = null; + if ( !localCopy.jdbcOperation().isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { + final MutableObject mutableValue = new MutableObject<>(); + localCopy = buildInterpretation( sqm, domainParameterXref, executionContext, mutableValue ); + jdbcParameterBindings = mutableValue.get(); cacheableSqmInterpretation = localCopy; } } @@ -228,130 +240,12 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec return interpreter.interpret( context, executionContext, localCopy, jdbcParameterBindings ); } - // Copy and paste from ORM - private JdbcParameterBindings createJdbcParameterBindings(CacheableSqmInterpretation sqmInterpretation, DomainQueryExecutionContext executionContext) { - return SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - sqmInterpretation.getJdbcParamsXref(), - new SqmParameterMappingModelResolutionAccess() { - //this is pretty ugly! - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypes().get(parameter); - } - }, - executionContext.getSession() - ); - } - - private static CacheableSqmInterpretation buildCacheableSqmInterpretation( - SqmSelectStatement sqm, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext executionContext) { - final SharedSessionContractImplementor session = executionContext.getSession(); - final SessionFactoryImplementor sessionFactory = session.getFactory(); - final QueryEngine queryEngine = sessionFactory.getQueryEngine(); - - final SqmTranslatorFactory sqmTranslatorFactory = queryEngine.getSqmTranslatorFactory(); - - final SqmTranslator sqmConverter = sqmTranslatorFactory.createSelectTranslator( - sqm, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - sessionFactory.getSqlTranslationEngine(), - true - ); - -// tableGroupAccess = sqmConverter.getFromClauseAccess(); - final SqmTranslation sqmInterpretation = sqmConverter.translate(); - final FromClauseAccess tableGroupAccess = sqmConverter.getFromClauseAccess(); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final SqlAstTranslator selectTranslator = sqlAstTranslatorFactory - .buildSelectTranslator( sessionFactory, sqmInterpretation.getSqlAst() ); - final Map, Map, List>> jdbcParamsXref = SqmUtil - .generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam ); - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter); - } - }, - session - ); - - final JdbcOperationQuerySelect jdbcSelect = selectTranslator.translate( - jdbcParameterBindings, - executionContext.getQueryOptions() - ); - - return new CacheableSqmInterpretation( - sqmInterpretation.getSqlAst(), - jdbcSelect, - tableGroupAccess, - jdbcParamsXref, - sqmInterpretation.getSqmParameterMappingModelTypeResolutions(), - jdbcParameterBindings - ); - } - private interface SqmInterpreter { CompletionStage interpret( X context, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings); } - private static class CacheableSqmInterpretation { - private final SelectStatement selectStatement; - private final JdbcOperationQuerySelect jdbcSelect; - private final FromClauseAccess tableGroupAccess; - private final Map, Map, List>> jdbcParamsXref; - private final Map, MappingModelExpressible> sqmParameterMappingModelTypes; - private transient JdbcParameterBindings firstParameterBindings; - - CacheableSqmInterpretation( - SelectStatement selectStatement, - JdbcOperationQuerySelect jdbcSelect, - FromClauseAccess tableGroupAccess, - Map, Map, List>> jdbcParamsXref, - Map, MappingModelExpressible> sqmParameterMappingModelTypes, - JdbcParameterBindings firstParameterBindings) { - this.selectStatement = selectStatement; - this.jdbcSelect = jdbcSelect; - this.tableGroupAccess = tableGroupAccess; - this.jdbcParamsXref = jdbcParamsXref; - this.sqmParameterMappingModelTypes = sqmParameterMappingModelTypes; - this.firstParameterBindings = firstParameterBindings; - } - - SelectStatement getSelectStatement() { - return selectStatement; - } - - JdbcOperationQuerySelect getJdbcSelect() { - return jdbcSelect; - } - - FromClauseAccess getTableGroupAccess() { - return tableGroupAccess; - } - - Map, Map, List>> getJdbcParamsXref() { - return jdbcParamsXref; - } - - public Map, MappingModelExpressible> getSqmParameterMappingModelTypes() { - return sqmParameterMappingModelTypes; - } - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java new file mode 100644 index 000000000..a1759ccbc --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveAbstractMultiTableMutationQueryPlan.java @@ -0,0 +1,35 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.internal; + +import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.AbstractMultiTableMutationQueryPlan; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.tree.SqmDmlStatement; +import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; + +import java.util.concurrent.CompletionStage; + +public abstract class ReactiveAbstractMultiTableMutationQueryPlan, F> + extends AbstractMultiTableMutationQueryPlan implements ReactiveNonSelectQueryPlan { + + public ReactiveAbstractMultiTableMutationQueryPlan( + S statement, + DomainParameterXref domainParameterXref, + F strategy) { + super( statement, domainParameterXref, strategy ); + } + + @Override + public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), getStatement() ); + final Interpretation interpretation = getInterpretation( context ); + return ((ReactiveHandler)interpretation.handler()).reactiveExecute( interpretation.jdbcParameterBindings(), context ); + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java index 90bdf6c49..30a77ba2d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableDeleteQueryPlan.java @@ -5,35 +5,32 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; /** * @see org.hibernate.query.sqm.internal.MultiTableDeleteQueryPlan */ -public class ReactiveMultiTableDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; - private final ReactiveSqmMultiTableMutationStrategy deleteStrategy; +public class ReactiveMultiTableDeleteQueryPlan + extends ReactiveAbstractMultiTableMutationQueryPlan, SqmMultiTableMutationStrategy> { public ReactiveMultiTableDeleteQueryPlan( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, ReactiveSqmMultiTableMutationStrategy deleteStrategy) { - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; - this.deleteStrategy = deleteStrategy; + super( sqmDelete, domainParameterXref, deleteStrategy ); } @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete ); - return deleteStrategy.reactiveExecuteDelete( sqmDelete, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmDeleteStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java index ed0e68b01..502f4f53c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableInsertQueryPlan.java @@ -5,35 +5,32 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; /** * @see org.hibernate.query.sqm.internal.MultiTableInsertQueryPlan */ -public class ReactiveMultiTableInsertQueryPlan implements ReactiveNonSelectQueryPlan { - private final SqmInsertStatement sqmInsert; - private final DomainParameterXref domainParameterXref; - private final ReactiveSqmMultiTableInsertStrategy mutationStrategy; +public class ReactiveMultiTableInsertQueryPlan + extends ReactiveAbstractMultiTableMutationQueryPlan, SqmMultiTableInsertStrategy> { public ReactiveMultiTableInsertQueryPlan( SqmInsertStatement sqmInsert, DomainParameterXref domainParameterXref, ReactiveSqmMultiTableInsertStrategy mutationStrategy) { - this.sqmInsert = sqmInsert; - this.domainParameterXref = domainParameterXref; - this.mutationStrategy = mutationStrategy; + super( sqmInsert, domainParameterXref, mutationStrategy ); } @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmInsert ); - return mutationStrategy.reactiveExecuteInsert( sqmInsert, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmInsertStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableInsertStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java index 605186079..0715e06b5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveMultiTableUpdateQueryPlan.java @@ -5,35 +5,31 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; /** * @see org.hibernate.query.sqm.internal.MultiTableUpdateQueryPlan */ -public class ReactiveMultiTableUpdateQueryPlan implements ReactiveNonSelectQueryPlan { - private final SqmUpdateStatement sqmUpdate; - private final DomainParameterXref domainParameterXref; - private final ReactiveSqmMultiTableMutationStrategy mutationStrategy; +public class ReactiveMultiTableUpdateQueryPlan + extends ReactiveAbstractMultiTableMutationQueryPlan, SqmMultiTableMutationStrategy> { public ReactiveMultiTableUpdateQueryPlan( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - ReactiveSqmMultiTableMutationStrategy mutationStrategy) { - this.sqmUpdate = sqmUpdate; - this.domainParameterXref = domainParameterXref; - this.mutationStrategy = mutationStrategy; + SqmMultiTableMutationStrategy mutationStrategy) { + super( sqmUpdate, domainParameterXref, mutationStrategy ); } @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmUpdate ); - return mutationStrategy.reactiveExecuteUpdate( sqmUpdate, domainParameterXref, executionContext ); + protected MultiTableHandlerBuildResult buildHandler( + SqmUpdateStatement statement, + DomainParameterXref domainParameterXref, + SqmMultiTableMutationStrategy strategy, + DomainQueryExecutionContext context) { + return strategy.buildHandler( statement, domainParameterXref, context ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java index eb055a38a..83113911b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java @@ -5,215 +5,89 @@ */ package org.hibernate.reactive.query.sqm.internal; -import java.sql.PreparedStatement; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - import org.hibernate.action.internal.BulkOperationCleanupAction; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SoftDeleteMapping; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryParameterImplementor; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SimpleDeleteQueryPlan; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.sql.SqmTranslator; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcParametersList; -import org.hibernate.sql.results.internal.SqlSelectionImpl; -import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.usingLockingAndPaging; - -public class ReactiveSimpleDeleteQueryPlan extends SimpleDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - private final EntityMappingType entityDescriptor; - private final SqmDeleteStatement sqmDelete; - private final DomainParameterXref domainParameterXref; +import java.lang.invoke.MethodHandles; +import java.util.concurrent.CompletionStage; - private JdbcOperationQueryMutation jdbcOperation; +public class ReactiveSimpleDeleteQueryPlan + extends SimpleDeleteQueryPlan implements ReactiveNonSelectQueryPlan { - private SqmTranslation sqmInterpretation; - private Map, Map, List>> jdbcParamsXref; + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); public ReactiveSimpleDeleteQueryPlan( - EntityMappingType entityDescriptor, + EntityPersister entityDescriptor, SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref) { super( entityDescriptor, sqmDelete, domainParameterXref ); - this.entityDescriptor = entityDescriptor; - this.sqmDelete = sqmDelete; - this.domainParameterXref = domainParameterXref; } @Override - protected SqlAstTranslator createTranslator(DomainQueryExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final SqmTranslator translator = factory.getQueryEngine().getSqmTranslatorFactory().createMutationTranslator( - sqmDelete, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ); - - sqmInterpretation = (SqmTranslation) translator.translate(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - return factory.getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, createDeleteAst() ); + protected int execute(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + throw LOG.nonReactiveMethodCall( "executeReactiveUpdate" ); } - // Copy and paste from superclass - private MutationStatement createDeleteAst() { - final MutationStatement ast; - if ( entityDescriptor.getSoftDeleteMapping() == null ) { - ast = sqmInterpretation.getSqlAst(); - } - else { - final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); - final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); - final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); - final Assignment assignment = columnMapping.createSoftDeleteAssignment( targetTable ); - - ast = new UpdateStatement( - targetTable, - Collections.singletonList( assignment ), - sqlDeleteAst.getRestriction() - ); - } - return ast; + @Override + public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), getStatement() ); + final Interpretation interpretation = getInterpretation( context ); + final ExecutionContext executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return executeReactive( interpretation.interpretation(), interpretation.jdbcParameterBindings(), executionContext ); } - @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmDelete ); + protected CompletionStage executeReactive(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { final SharedSessionContractImplementor session = executionContext.getSession(); - final SessionFactoryImplementor factory = session.getFactory(); - SqlAstTranslator sqlAstTranslator = null; - if ( jdbcOperation == null ) { - sqlAstTranslator = createTranslator( executionContext ); - } - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmInterpretation.getSqmParameterMappingModelTypeResolutions().get(parameter); - } - }, - session + return CompletionStages.loop(getCollectionTableDeletes(), delete -> + executeReactive( delete, jdbcParameterBindings, executionContext.getSession(), executionContext ) + ).thenCompose( v -> CompletionStages.loop( + getCollectionTableDeletes(), delete -> + executeReactive( + delete, + jdbcParameterBindings, + executionContext.getSession(), + executionContext + ) + ) + .thenCompose( unused -> executeReactive( + sqmInterpretation.jdbcOperation(), + jdbcParameterBindings, + session, + executionContext + ) ) ); - - if ( jdbcOperation != null - && !jdbcOperation.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - sqlAstTranslator = createTranslator( executionContext ); - } - - if ( sqlAstTranslator != null ) { - jdbcOperation = sqlAstTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - final boolean missingRestriction = sqmDelete.getWhereClause() == null - || sqmDelete.getWhereClause().getPredicate() == null; - if ( missingRestriction ) { - assert domainParameterXref.getSqmParameterCount() == 0; - assert jdbcParamsXref.isEmpty(); - } - - final SqmJdbcExecutionContextAdapter executionContextAdapter = usingLockingAndPaging( executionContext ); - - return ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - entityDescriptor, - (tableReference, attributeMapping) -> { - if ( missingRestriction ) { - return null; - } - - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final Expression fkColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor.getKeyPart(), - null, - factory - ); - - final QuerySpec matchingIdSubQuery = new QuerySpec( false ); - - final MutatingTableReferenceGroupWrapper tableGroup = new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - sqmInterpretation.getSqlAst().getTargetTable() - ); - final Expression fkTargetColumnExpression = MappingModelCreationHelper.buildColumnReferenceExpression( - tableGroup, - fkDescriptor.getTargetPart(), - sqmInterpretation.getSqlExpressionResolver(), - factory - ); - matchingIdSubQuery.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( 0, fkTargetColumnExpression ) ); - - matchingIdSubQuery.getFromClause().addRoot( - tableGroup - ); - - matchingIdSubQuery.applyPredicate( sqmInterpretation.getSqlAst().getRestriction() ); - - return new InSubQueryPredicate( fkColumnExpression, matchingIdSubQuery, false ); - }, - ( missingRestriction ? JdbcParameterBindings.NO_BINDINGS : jdbcParameterBindings ), - executionContextAdapter - ) - .thenCompose( unused -> StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcOperation, - jdbcParameterBindings, - session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, - ReactiveSimpleDeleteQueryPlan::doNothing, - executionContextAdapter ) - ); } - private static void doNothing(Integer i, PreparedStatement ps) { + private static CompletionStage executeReactive( + JdbcOperationQueryMutation delete, + JdbcParameterBindings jdbcParameterBindings, + SharedSessionContractImplementor executionContext, + ExecutionContext executionContext1) { + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + delete, + jdbcParameterBindings, + sql -> executionContext + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext1 + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java deleted file mode 100644 index bf67d16ea..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleInsertQueryPlan.java +++ /dev/null @@ -1,121 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.internal; - - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcParametersList; - -/** - * @see org.hibernate.query.sqm.internal.SimpleInsertQueryPlan - */ -public class ReactiveSimpleInsertQueryPlan implements ReactiveNonSelectQueryPlan { - - private final SqmInsertStatement sqmInsert; - - private final DomainParameterXref domainParameterXref; - - private Map, MappingModelExpressible> paramTypeResolutions; - - private JdbcOperationQueryMutation jdbcInsert; - private Map, Map, List>> jdbcParamsXref; - - public ReactiveSimpleInsertQueryPlan( - SqmInsertStatement sqmInsert, - DomainParameterXref domainParameterXref) { - this.sqmInsert = sqmInsert; - this.domainParameterXref = domainParameterXref; - } - - @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmInsert ); - final SharedSessionContractImplementor session = executionContext.getSession(); - SqlAstTranslator insertTranslator = jdbcInsert == null - ? createInsertTranslator( executionContext ) - : null; - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - session - ); - - if ( jdbcInsert != null && !jdbcInsert.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - insertTranslator = createInsertTranslator( executionContext ); - } - - if ( insertTranslator != null ) { - jdbcInsert = insertTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcInsert, - jdbcParameterBindings, - session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, - (i, ps) -> {}, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ) - ); - } - - // Copied from Hibernate ORM SimpleInsertQueryPlan#createInsertTranslator - private SqlAstTranslator createInsertTranslator(DomainQueryExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - - final SqmTranslation sqmInterpretation = factory.getQueryEngine().getSqmTranslatorFactory() - .createMutationTranslator( - sqmInsert, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ) - .translate(); - - this.jdbcParamsXref = SqmUtil.generateJdbcParamsXref( - domainParameterXref, - sqmInterpretation::getJdbcParamsBySqmParam - ); - - this.paramTypeResolutions = sqmInterpretation.getSqmParameterMappingModelTypeResolutions(); - - return factory.getJdbcServices() - .getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqmInterpretation.getSqlAst() ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java new file mode 100644 index 000000000..1a2a942f5 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleNonSelectQueryPlan.java @@ -0,0 +1,56 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.internal; + +import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SimpleNonSelectQueryPlan; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.tree.SqmDmlStatement; +import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.sql.ast.tree.MutationStatement; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import java.util.concurrent.CompletionStage; + +public class ReactiveSimpleNonSelectQueryPlan extends + SimpleNonSelectQueryPlan implements ReactiveNonSelectQueryPlan { + + public ReactiveSimpleNonSelectQueryPlan( + SqmDmlStatement statement, + DomainParameterXref domainParameterXref) { + super( statement, domainParameterXref ); + } + + @Override + public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext context) { + BulkOperationCleanupAction.schedule( context.getSession(), getStatement() ); + final Interpretation interpretation = getInterpretation( context ); + final ExecutionContext executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return executeReactive( interpretation.interpretation(), interpretation.jdbcParameterBindings(), executionContext ); + } + + protected CompletionStage executeReactive(CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + final SharedSessionContractImplementor session = executionContext.getSession(); + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + sqmInterpretation.jdbcOperation(), + jdbcParameterBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java deleted file mode 100644 index f49f848f5..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleUpdateQueryPlan.java +++ /dev/null @@ -1,117 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.internal; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - -import org.hibernate.action.internal.BulkOperationCleanupAction; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryEngine; -import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.SqmTranslation; -import org.hibernate.query.sqm.sql.SqmTranslator; -import org.hibernate.query.sqm.sql.SqmTranslatorFactory; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.spi.FromClauseAccess; -import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.exec.spi.JdbcParametersList; - - -/** - * @see org.hibernate.query.sqm.internal.SimpleUpdateQueryPlan - */ -public class ReactiveSimpleUpdateQueryPlan implements ReactiveNonSelectQueryPlan { - - private final SqmUpdateStatement sqmUpdate; - private final DomainParameterXref domainParameterXref; - - private JdbcOperationQueryMutation jdbcUpdate; - private FromClauseAccess tableGroupAccess; - private Map, Map, List>> jdbcParamsXref; - private Map, MappingModelExpressible> sqmParamMappingTypeResolutions; - - public ReactiveSimpleUpdateQueryPlan(SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref) { - this.sqmUpdate = sqmUpdate; - this.domainParameterXref = domainParameterXref; - } - - @Override - public CompletionStage executeReactiveUpdate(DomainQueryExecutionContext executionContext) { - BulkOperationCleanupAction.schedule( executionContext.getSession(), sqmUpdate ); - final SharedSessionContractImplementor session = executionContext.getSession(); - SqlAstTranslator updateTranslator = jdbcUpdate == null - ? createUpdateTranslator( executionContext ) - : null; - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - domainParameterXref, - jdbcParamsXref, - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmParamMappingTypeResolutions.get(parameter); - } - }, - session - ); - - if ( jdbcUpdate != null && !jdbcUpdate.isCompatibleWith( jdbcParameterBindings, executionContext.getQueryOptions() ) ) { - updateTranslator = createUpdateTranslator( executionContext ); - } - - if ( updateTranslator != null ) { - jdbcUpdate = updateTranslator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - } - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcUpdate, - jdbcParameterBindings, - session.getJdbcCoordinator().getStatementPreparer()::prepareStatement, - (i, ps) -> {}, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ) - ); - } - - // I can probably change ORM to reuse this - private SqlAstTranslator createUpdateTranslator(DomainQueryExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final QueryEngine queryEngine = factory.getQueryEngine(); - - final SqmTranslatorFactory translatorFactory = queryEngine.getSqmTranslatorFactory(); - final SqmTranslator translator = translatorFactory.createMutationTranslator( - sqmUpdate, - executionContext.getQueryOptions(), - domainParameterXref, - executionContext.getQueryParameterBindings(), - executionContext.getSession().getLoadQueryInfluencers(), - factory.getSqlTranslationEngine() - ); - - final SqmTranslation sqmInterpretation = translator.translate(); - tableGroupAccess = sqmInterpretation.getFromClauseAccess(); - this.jdbcParamsXref = SqmUtil - .generateJdbcParamsXref( domainParameterXref, sqmInterpretation::getJdbcParamsBySqmParam ); - this.sqmParamMappingTypeResolutions = sqmInterpretation.getSqmParameterMappingModelTypeResolutions(); - return factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqmInterpretation.getSqlAst() ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java similarity index 80% rename from hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java rename to hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java index 755448cbd..b0bc9f823 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveQuerySqmImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmQueryImpl.java @@ -46,9 +46,12 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.sqm.internal.SqmInterpretationsKey; import org.hibernate.query.sqm.internal.SqmQueryImpl; +import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; +import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; @@ -73,13 +76,13 @@ /** * A reactive {@link SqmQueryImpl} */ -public class ReactiveQuerySqmImpl extends SqmQueryImpl implements ReactiveSqmQueryImplementor { +public class ReactiveSqmQueryImpl extends SqmQueryImpl implements ReactiveSqmQueryImplementor { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final ReactiveAbstractSelectionQuery selectionQueryDelegate; - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( NamedHqlQueryMementoImpl memento, Class expectedResultType, SharedSessionContractImplementor session) { @@ -87,7 +90,7 @@ public ReactiveQuerySqmImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( NamedCriteriaQueryMementoImpl memento, Class resultType, SharedSessionContractImplementor session) { @@ -95,7 +98,7 @@ public ReactiveQuerySqmImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( String hql, HqlInterpretation hqlInterpretation, Class resultType, @@ -104,7 +107,7 @@ public ReactiveQuerySqmImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveQuerySqmImpl( + public ReactiveSqmQueryImpl( SqmStatement criteria, Class resultType, SharedSessionContractImplementor session) { @@ -415,13 +418,13 @@ private ReactiveNonSelectQueryPlan buildUpdateQueryPlan() { final ReactiveSqmMultiTableMutationStrategy multiTableStrategy = (ReactiveSqmMultiTableMutationStrategy) entityDescriptor.getSqmMultiTableMutationStrategy(); return multiTableStrategy == null - ? new ReactiveSimpleUpdateQueryPlan( sqmUpdate, getDomainParameterXref() ) + ? new ReactiveSimpleNonSelectQueryPlan( sqmUpdate, getDomainParameterXref() ) : new ReactiveMultiTableUpdateQueryPlan( sqmUpdate, getDomainParameterXref(), multiTableStrategy ); } private ReactiveNonSelectQueryPlan buildInsertQueryPlan() { //noinspection rawtypes - final SqmInsertStatement sqmInsert = (SqmInsertStatement) getSqmStatement(); + final SqmInsertStatement sqmInsert = (SqmInsertStatement) getSqmStatement(); final String entityNameToInsert = sqmInsert.getTarget().getModel().getHibernateEntityName(); final EntityPersister persister = getSessionFactory() @@ -438,87 +441,101 @@ private ReactiveNonSelectQueryPlan buildInsertQueryPlan() { } } } - if ( !useMultiTableInsert ) { - return new ReactiveSimpleInsertQueryPlan( sqmInsert, getDomainParameterXref() ); - } - else { + if ( useMultiTableInsert ) { return new ReactiveMultiTableInsertQueryPlan( sqmInsert, getDomainParameterXref(), (ReactiveSqmMultiTableInsertStrategy) persister.getSqmMultiTableInsertStrategy() ); } + else if ( sqmInsert instanceof SqmInsertValuesStatement insertValues + && insertValues.getValuesList().size() != 1 + && !getSessionFactory().getJdbcServices().getDialect().supportsValuesListForInsert() ) { + // Split insert-values queries if the dialect doesn't support values lists + final List valuesList = insertValues.getValuesList(); + final ReactiveNonSelectQueryPlan[] planParts = new ReactiveNonSelectQueryPlan[valuesList.size()]; + for ( int i = 0; i < valuesList.size(); i++ ) { + final SqmInsertValuesStatement subInsert = + insertValues.copyWithoutValues( SqmCopyContext.simpleContext() ); + subInsert.values( valuesList.get( i ) ); + planParts[i] = new ReactiveSimpleNonSelectQueryPlan( subInsert, getDomainParameterXref() ); + } + + return new ReactiveAggregatedNonSelectQueryPlan( planParts ); + } + + return new ReactiveSimpleNonSelectQueryPlan( sqmInsert, getDomainParameterXref() ); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // QueryOptions @Override - public ReactiveQuerySqmImpl setHint(String hintName, Object value) { + public ReactiveSqmQueryImpl setHint(String hintName, Object value) { super.setHint( hintName, value ); return this; } @Override - public ReactiveQuerySqmImpl addQueryHint(String hint) { + public ReactiveSqmQueryImpl addQueryHint(String hint) { super.addQueryHint( hint ); return this; } @Override - public ReactiveQuerySqmImpl setLockOptions(LockOptions lockOptions) { + public ReactiveSqmQueryImpl setLockOptions(LockOptions lockOptions) { super.setLockOptions( lockOptions ); return this; } @Override - public ReactiveQuerySqmImpl setLockMode(String alias, LockMode lockMode) { + public ReactiveSqmQueryImpl setLockMode(String alias, LockMode lockMode) { super.setLockMode( alias, lockMode ); return this; } @Override - public ReactiveQuerySqmImpl setTupleTransformer(TupleTransformer transformer) { + public ReactiveSqmQueryImpl setTupleTransformer(TupleTransformer transformer) { throw new UnsupportedOperationException(); } @Override - public ReactiveQuerySqmImpl setResultListTransformer(ResultListTransformer transformer) { + public ReactiveSqmQueryImpl setResultListTransformer(ResultListTransformer transformer) { super.setResultListTransformer( transformer ); return this; } @Override @Deprecated - public ReactiveQuerySqmImpl setResultTransformer(ResultTransformer transformer) { + public ReactiveSqmQueryImpl setResultTransformer(ResultTransformer transformer) { throw new UnsupportedOperationException(); } @Override - public ReactiveQuerySqmImpl setMaxResults(int maxResult) { + public ReactiveSqmQueryImpl setMaxResults(int maxResult) { super.setMaxResults( maxResult ); return this; } @Override - public ReactiveQuerySqmImpl setFirstResult(int startPosition) { + public ReactiveSqmQueryImpl setFirstResult(int startPosition) { super.setFirstResult( startPosition ); return this; } @Override - public ReactiveQuerySqmImpl setHibernateFlushMode(FlushMode flushMode) { + public ReactiveSqmQueryImpl setHibernateFlushMode(FlushMode flushMode) { super.setHibernateFlushMode( flushMode ); return this; } @Override - public ReactiveQuerySqmImpl setFlushMode(FlushModeType flushMode) { + public ReactiveSqmQueryImpl setFlushMode(FlushModeType flushMode) { super.setFlushMode( flushMode ); return this; } @Override - public ReactiveQuerySqmImpl setLockMode(LockModeType lockMode) { + public ReactiveSqmQueryImpl setLockMode(LockModeType lockMode) { super.setLockMode( lockMode ); return this; } @@ -527,313 +544,313 @@ public ReactiveQuerySqmImpl setLockMode(LockModeType lockMode) { // covariance @Override - public ReactiveQuerySqmImpl applyGraph(RootGraph graph, GraphSemantic semantic) { + public ReactiveSqmQueryImpl applyGraph(RootGraph graph, GraphSemantic semantic) { super.applyGraph( graph, semantic ); return this; } @Override - public ReactiveQuerySqmImpl applyLoadGraph(RootGraph graph) { + public ReactiveSqmQueryImpl applyLoadGraph(RootGraph graph) { super.applyLoadGraph( graph ); return this; } @Override - public ReactiveQuerySqmImpl applyFetchGraph(RootGraph graph) { + public ReactiveSqmQueryImpl applyFetchGraph(RootGraph graph) { super.applyFetchGraph( graph ); return this; } @Override - public ReactiveQuerySqmImpl setComment(String comment) { + public ReactiveSqmQueryImpl setComment(String comment) { super.setComment( comment ); return this; } @Override - public ReactiveQuerySqmImpl setCacheMode(CacheMode cacheMode) { + public ReactiveSqmQueryImpl setCacheMode(CacheMode cacheMode) { super.setCacheMode( cacheMode ); return this; } @Override - public ReactiveQuerySqmImpl setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { + public ReactiveSqmQueryImpl setCacheRetrieveMode(CacheRetrieveMode cacheRetrieveMode) { super.setCacheRetrieveMode( cacheRetrieveMode ); return this; } @Override - public ReactiveQuerySqmImpl setCacheStoreMode(CacheStoreMode cacheStoreMode) { + public ReactiveSqmQueryImpl setCacheStoreMode(CacheStoreMode cacheStoreMode) { super.setCacheStoreMode( cacheStoreMode ); return this; } @Override - public ReactiveQuerySqmImpl setCacheable(boolean cacheable) { + public ReactiveSqmQueryImpl setCacheable(boolean cacheable) { super.setCacheable( cacheable ); return this; } @Override - public ReactiveQuerySqmImpl setCacheRegion(String cacheRegion) { + public ReactiveSqmQueryImpl setCacheRegion(String cacheRegion) { super.setCacheRegion( cacheRegion ); return this; } @Override - public ReactiveQuerySqmImpl setHibernateLockMode(LockMode lockMode) { + public ReactiveSqmQueryImpl setHibernateLockMode(LockMode lockMode) { super.setHibernateLockMode( lockMode ); return this; } @Override - public ReactiveQuerySqmImpl setTimeout(int timeout) { + public ReactiveSqmQueryImpl setTimeout(int timeout) { super.setTimeout( timeout ); return this; } @Override - public ReactiveQuerySqmImpl setFetchSize(int fetchSize) { + public ReactiveSqmQueryImpl setFetchSize(int fetchSize) { super.setFetchSize( fetchSize ); return this; } @Override - public ReactiveQuerySqmImpl setReadOnly(boolean readOnly) { + public ReactiveSqmQueryImpl setReadOnly(boolean readOnly) { super.setReadOnly( readOnly ); return this; } @Override - public ReactiveQuerySqmImpl setProperties(Object bean) { + public ReactiveSqmQueryImpl setProperties(Object bean) { super.setProperties( bean ); return this; } @Override - public ReactiveQuerySqmImpl setProperties(Map bean) { + public ReactiveSqmQueryImpl setProperties(Map bean) { super.setProperties( bean ); return this; } @Override - public ReactiveQuerySqmImpl setParameter(String name, Object value) { + public ReactiveSqmQueryImpl setParameter(String name, Object value) { super.setParameter( name, value ); return this; } @Override - public

ReactiveQuerySqmImpl setParameter(String name, P value, Class

javaType) { + public

ReactiveSqmQueryImpl setParameter(String name, P value, Class

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

ReactiveQuerySqmImpl setParameter(String name, P value, Type

type) { + public

ReactiveSqmQueryImpl setParameter(String name, P value, Type

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

ReactiveQuerySqmImpl setParameter(int position, P value, Class

javaType) { + public

ReactiveSqmQueryImpl setParameter(int position, P value, Class

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

ReactiveQuerySqmImpl setParameter(int position, P value, Type

type) { + public

ReactiveSqmQueryImpl setParameter(int position, P value, Type

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

ReactiveQuerySqmImpl setParameter(QueryParameter

parameter, P value) { + public

ReactiveSqmQueryImpl setParameter(QueryParameter

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

ReactiveQuerySqmImpl setParameter(QueryParameter

parameter, P value, Class

javaType) { + public

ReactiveSqmQueryImpl setParameter(QueryParameter

parameter, P value, Class

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

ReactiveQuerySqmImpl setParameter(QueryParameter

parameter, P value, Type

type) { + public

ReactiveSqmQueryImpl setParameter(QueryParameter

parameter, P value, Type

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

ReactiveQuerySqmImpl setParameter(Parameter

parameter, P value) { + public

ReactiveSqmQueryImpl setParameter(Parameter

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

ReactiveQuerySqmImpl setParameterList(String name, Collection values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(String name, Collection values, Class

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

ReactiveQuerySqmImpl setParameterList(String name, Collection values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(String name, Collection values, Type

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

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

javaType) { + public

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

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

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

type) { + public

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

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

ReactiveQuerySqmImpl setParameterList(int position, Collection values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(int position, Collection values, Class

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

ReactiveQuerySqmImpl setParameterList(int position, Collection values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(int position, Collection values, Type

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

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

javaType) { + public

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

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

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

type) { + public

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

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, Collection values) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, Collection values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, Collection values, Class

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, Collection values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, Collection values, Type

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, P[] values) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, P[] values, Class

javaType) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, P[] values, Class

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

ReactiveQuerySqmImpl setParameterList(QueryParameter

parameter, P[] values, Type

type) { + public

ReactiveSqmQueryImpl setParameterList(QueryParameter

parameter, P[] values, Type

type) { super.setParameterList( parameter, values, type ); return this; } @Override - public ReactiveQuerySqmImpl setFollowOnLocking(boolean enable) { + public ReactiveSqmQueryImpl setFollowOnLocking(boolean enable) { super.setFollowOnLocking( enable ); return this; } @@ -844,7 +861,7 @@ public void applyGraph(RootGraphImplementor graph, GraphSemantic semantic) { } @Override - public ReactiveQuerySqmImpl enableFetchProfile(String profileName) { + public ReactiveSqmQueryImpl enableFetchProfile(String profileName) { selectionQueryDelegate.enableFetchProfile( profileName ); return this; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java index d23ae3225..cab920f73 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveHandler.java @@ -5,14 +5,30 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal; +import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.mutation.internal.Handler; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.Handler */ -public interface ReactiveHandler { +public interface ReactiveHandler extends Handler { + Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + @Override + default int execute(DomainQueryExecutionContext executionContext) { + throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + } + + @Override + default int execute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext) { + throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + } /** * Execute the multi-table update or delete indicated by the SQM AST @@ -22,5 +38,17 @@ public interface ReactiveHandler { * * @return The "number of rows affected" count */ - CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext); + default CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext){ + return reactiveExecute( createJdbcParameterBindings( executionContext ), executionContext ); + } + + /** + * Execute the multi-table update or delete indicated by the SQM AST + * passed in when this Handler was created. + * + * @param jdbcParameterBindings The parameter bindings for JDBC parameters + * @param executionContext Contextual information needed for execution + * @return The "number of rows affected" count + */ + CompletionStage reactiveExecute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext executionContext); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java deleted file mode 100644 index b2e36a296..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/ReactiveSqmMutationStrategyHelper.java +++ /dev/null @@ -1,161 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal; - -import java.sql.PreparedStatement; -import java.util.concurrent.CompletionStage; -import java.util.function.BiFunction; - -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.reactive.util.impl.CompletionStages.Completable; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; - -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -/** - * @see org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper - */ -public class ReactiveSqmMutationStrategyHelper { - - private ReactiveSqmMutationStrategyHelper() { - } - - public static CompletionStage cleanUpCollectionTables( - EntityMappingType entityDescriptor, - BiFunction restrictionProducer, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - if ( !entityDescriptor.getEntityPersister().hasCollections() ) { - // none to clean-up - return voidFuture(); - } - - try { - final Completable stage = new Completable<>(); - entityDescriptor - .visitSubTypeAttributeMappings( attributeMapping -> { - if ( attributeMapping instanceof PluralAttributeMapping ) { - cleanUpCollectionTable( - (PluralAttributeMapping) attributeMapping, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( stage::complete ); - } - else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { - cleanUpCollectionTables( - (EmbeddedAttributeMapping) attributeMapping, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( stage::complete ); - } - else { - stage.complete( null, null ); - } - } - ); - return stage.getStage(); - } - catch (Throwable throwable) { - return failedFuture( throwable ); - } - } - - private static CompletionStage cleanUpCollectionTables( - EmbeddedAttributeMapping attributeMapping, - BiFunction restrictionProducer, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - try { - final Completable completable = new Completable<>(); - attributeMapping.visitSubParts( - modelPart -> { - if ( modelPart instanceof PluralAttributeMapping ) { - cleanUpCollectionTable( - (PluralAttributeMapping) modelPart, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( completable::complete ); - } - else if ( modelPart instanceof EmbeddedAttributeMapping ) { - cleanUpCollectionTables( - (EmbeddedAttributeMapping) modelPart, - restrictionProducer, - jdbcParameterBindings, - executionContext - ).handle( completable::complete ); - } - }, - null - ); - return completable.getStage(); - } - catch (Throwable throwable) { - return failedFuture( throwable ); - } - } - - private static CompletionStage cleanUpCollectionTable( - PluralAttributeMapping attributeMapping, - BiFunction restrictionProducer, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - final String separateCollectionTable = attributeMapping.getSeparateCollectionTable(); - if ( separateCollectionTable == null ) { - // one-to-many - update the matching rows in the associated table setting the fk column(s) to null - // not yet implemented - do nothing - return voidFuture(); - } - - final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); - final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); - - // element-collection or many-to-many - delete the collection-table row - - final NamedTableReference tableReference = new NamedTableReference( - separateCollectionTable, - DeleteStatement.DEFAULT_ALIAS, - true - ); - - final DeleteStatement sqlAstDelete = new DeleteStatement( - tableReference, - restrictionProducer.apply( tableReference, attributeMapping ) - ); - - JdbcOperationQueryMutation jdbcDelete = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( sessionFactory, sqlAstDelete ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcDelete, - jdbcParameterBindings, - executionContext.getSession().getJdbcCoordinator().getStatementPreparer()::prepareStatement, - ReactiveSqmMutationStrategyHelper::doNothing, - executionContext - ) - .thenCompose( CompletionStages::voidFuture ); - } - - private static void doNothing(Integer i, PreparedStatement ps) { - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java index 806b75718..c0eb4ee3a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java @@ -5,189 +5,49 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.util.ArrayList; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SqlExpressible; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MatchingIdSelectionHelper; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler; -import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.tree.cte.CteContainer; -import org.hibernate.sql.ast.tree.cte.CteMaterialization; -import org.hibernate.sql.ast.tree.cte.CteStatement; -import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.cte.CteTableGroup; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.basic.BasicResult; -import org.hibernate.sql.results.internal.SqlSelectionImpl; + +import java.util.concurrent.CompletionStage; /** * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler */ public interface ReactiveAbstractCteMutationHandler extends ReactiveAbstractMutationHandler { - CteTable getCteTable(); - - DomainParameterXref getDomainParameterXref(); - - CteMutationStrategy getStrategy(); - - void addDmlCtes( - CteContainer statement, - CteStatement idSelectCte, - MultiTableSqmMutationConverter sqmConverter, - Map, List> parameterResolutions, - SessionFactoryImplementor factory); - /** - * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler#execute(DomainQueryExecutionContext) + * @see org.hibernate.query.sqm.mutation.internal.cte.AbstractCteMutationHandler#execute(JdbcParameterBindings, DomainQueryExecutionContext) */ @Override - default CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final SqmDeleteOrUpdateStatement sqmMutationStatement = getSqmDeleteOrUpdateStatement(); - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final EntityMappingType entityDescriptor = getEntityDescriptor(); - final String explicitDmlTargetAlias; - // We need an alias because we try to acquire a WRITE lock for these rows in the CTE - if ( sqmMutationStatement.getTarget().getExplicitAlias() == null ) { - explicitDmlTargetAlias = "dml_target"; - } - else { - explicitDmlTargetAlias = sqmMutationStatement.getTarget().getExplicitAlias(); - } - - final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( - entityDescriptor, - sqmMutationStatement, - sqmMutationStatement.getTarget(), - explicitDmlTargetAlias, - getDomainParameterXref(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - factory.getSqlTranslationEngine() - ); - final Map, List> parameterResolutions; - if ( getDomainParameterXref().getSqmParameterCount() == 0 ) { - parameterResolutions = Collections.emptyMap(); - } - else { - parameterResolutions = new IdentityHashMap<>(); - } - - final Predicate restriction = sqmConverter.visitWhereClause( sqmMutationStatement.getWhereClause() ); - sqmConverter.pruneTableGroupJoins(); - - final CteStatement idSelectCte = new CteStatement( - getCteTable(), - MatchingIdSelectionHelper.generateMatchingIdSelectStatement( - entityDescriptor, - sqmMutationStatement, - true, - restriction, - sqmConverter, - executionContext - ), - // The id-select cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - - // Create the main query spec that will return the count of - final QuerySpec querySpec = new QuerySpec( true, 1 ); - final List> domainResults = new ArrayList<>( 1 ); - final SelectStatement statement = new SelectStatement( querySpec, domainResults ); - final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( factory, statement ); - - final Expression count = createCountStar( factory, sqmConverter ); - domainResults.add( - new BasicResult<>( - 0, - null, - ( (SqlExpressible) count ).getJdbcMapping() - ) - ); - querySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, count ) ); - querySpec.getFromClause().addRoot( - new CteTableGroup( - new NamedTableReference( - idSelectCte.getCteTable().getTableExpression(), - AbstractCteMutationHandler.CTE_TABLE_IDENTIFIER - ) - ) - ); - - // Add all CTEs - statement.addCteStatement( idSelectCte ); - addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), sqmConverter ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions().get( parameter ); - } - }, - executionContext.getSession() - ); + default CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext executionContext){ final LockOptions lockOptions = executionContext.getQueryOptions().getLockOptions(); - final LockMode lockMode = lockOptions.getAliasSpecificLockMode( explicitDmlTargetAlias ); // Acquire a WRITE lock for the rows that are about to be modified - lockOptions.setAliasSpecificLockMode( explicitDmlTargetAlias, LockMode.WRITE ); - final JdbcOperationQuerySelect select = translator.translate( - jdbcParameterBindings, - executionContext.getQueryOptions() - ); - lockOptions.setAliasSpecificLockMode( explicitDmlTargetAlias, lockMode ); - + lockOptions.setLockMode( LockMode.WRITE ); return ( (ReactiveSharedSessionContractImplementor) executionContext.getSession() ) - .reactiveAutoFlushIfRequired( select.getAffectedTableNames() ) - .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE.list( - select, - jdbcParameterBindings, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), - row -> row[0], - ReactiveListResultsConsumer.UniqueSemantic.NONE - ) - .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) + .reactiveAutoFlushIfRequired( getSelect().getAffectedTableNames() ) + .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE + .list( + getSelect(), + jdbcParameterBindings, + SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), + row -> row[0], + null, + ReactiveListResultsConsumer.UniqueSemantic.NONE, + 1 + ) + .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) ); } - Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter); + JdbcOperationQuerySelect getSelect(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java index a1f555253..67c3641b8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java @@ -5,60 +5,37 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.cte.CteDeleteHandler; import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.sql.ast.tree.cte.CteContainer; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.cte.CteDeleteHandler */ public class ReactiveCteDeleteHandler extends CteDeleteHandler implements ReactiveAbstractCteMutationHandler { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - protected ReactiveCteDeleteHandler( CteTable cteTable, SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory ); - } - - @Override - public void addDmlCtes( - CteContainer statement, - CteStatement idSelectCte, - MultiTableSqmMutationConverter sqmConverter, - Map, List> parameterResolutions, - SessionFactoryImplementor factory) { - super.addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmDeleteStatement, domainParameterXref, strategy, sessionFactory, context, firstJdbcParameterBindingsConsumer ); } @Override - public Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter) { - return super.createCountStar( factory, sqmConverter ); + public JdbcOperationQuerySelect getSelect() { + return super.getSelect(); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java index e16ad0abd..9361df67f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertHandler.java @@ -5,93 +5,35 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.lang.invoke.MethodHandles; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; -import org.hibernate.id.OptimizableGenerator; -import org.hibernate.id.enhanced.Optimizer; -import org.hibernate.internal.util.collections.Stack; -import org.hibernate.metamodel.mapping.BasicValuedMapping; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SqlExpressible; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.results.internal.TableGroupImpl; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.BinaryArithmeticOperator; -import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.SqmInsertStrategyHelper; import org.hibernate.query.sqm.mutation.internal.cte.CteInsertHandler; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter; -import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; -import org.hibernate.query.sqm.tree.insert.SqmInsertValuesStatement; -import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.SqlAstJoinType; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.spi.SqlAstProcessingState; -import org.hibernate.sql.ast.tree.Statement; -import org.hibernate.sql.ast.tree.cte.CteColumn; -import org.hibernate.sql.ast.tree.cte.CteMaterialization; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.cte.CteTableGroup; -import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.QueryLiteral; -import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableGroupJoin; -import org.hibernate.sql.ast.tree.from.ValuesTableGroup; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.insert.Values; -import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; -import org.hibernate.sql.ast.tree.select.QueryPart; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.basic.BasicResult; -import org.hibernate.sql.results.internal.SqlSelectionImpl; + +import java.lang.invoke.MethodHandles; +import java.util.concurrent.CompletionStage; public class ReactiveCteInsertHandler extends CteInsertHandler implements ReactiveHandler { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final SessionFactoryImplementor sessionFactory; - public ReactiveCteInsertHandler( CteTable cteTable, SqmInsertStatement sqmStatement, DomainParameterXref domainParameterXref, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmStatement, domainParameterXref, sessionFactory ); - this.sessionFactory = sessionFactory; + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmStatement, domainParameterXref, context, firstJdbcParameterBindingsConsumer ); } @Override @@ -99,457 +41,23 @@ public int execute(DomainQueryExecutionContext executionContext) { throw LOG.nonReactiveMethodCall( "reactiveExecute" ); } - // Pretty much a copy and paste of the method in the super class - // We should refactor this @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final SqmInsertStatement sqmInsertStatement = getSqmStatement(); - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final EntityPersister entityDescriptor = getEntityDescriptor().getEntityPersister(); - final String explicitDmlTargetAlias; - if ( sqmInsertStatement.getTarget().getExplicitAlias() == null ) { - explicitDmlTargetAlias = "dml_target"; - } - else { - explicitDmlTargetAlias = sqmInsertStatement.getTarget().getExplicitAlias(); - } - - final MultiTableSqmMutationConverter sqmConverter = new MultiTableSqmMutationConverter( - entityDescriptor, - sqmInsertStatement, - sqmInsertStatement.getTarget(), - explicitDmlTargetAlias, - getDomainParameterXref(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - factory.getSqlTranslationEngine() - ); - final TableGroup insertingTableGroup = sqmConverter.getMutatingTableGroup(); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // visit the insertion target using our special converter, collecting - // information about the target paths - - final int size = getSqmStatement().getInsertionTargetPaths().size(); - final List, Assignment>> targetPathColumns = new ArrayList<>( size ); - final List targetPathCteColumns = new ArrayList<>( size ); - final NamedTableReference entityTableReference = new NamedTableReference( - getCteTable().getTableExpression(), - TemporaryTable.DEFAULT_ALIAS, - true - ); - final InsertSelectStatement insertStatement = new InsertSelectStatement( entityTableReference ); - - final BaseSqmToSqlAstConverter.AdditionalInsertValues additionalInsertValues = sqmConverter.visitInsertionTargetPaths( - (assignable, columnReferences) -> { - final SqmPathInterpretation pathInterpretation = (SqmPathInterpretation) assignable; - final List columns = getCteTable().findCteColumns( pathInterpretation.getExpressionType() ); - insertStatement.addTargetColumnReferences( columnReferences ); - targetPathCteColumns.addAll( columns ); - targetPathColumns.add( new AbstractMap.SimpleEntry<>( columns, new Assignment( assignable, (Expression) assignable ) ) ); - }, - sqmInsertStatement, - entityDescriptor, - insertingTableGroup - ); - - final boolean assignsId = targetPathCteColumns.contains( getCteTable().getCteColumns().get( 0 ) ); - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Create the statement that represent the source for the entity cte - - final Stack processingStateStack = sqmConverter.getProcessingStateStack(); - final SqlAstProcessingState oldState = processingStateStack.pop(); - final Statement queryStatement; - if ( sqmInsertStatement instanceof SqmInsertSelectStatement ) { - final QueryPart queryPart = sqmConverter.visitQueryPart( ( (SqmInsertSelectStatement) sqmInsertStatement ).getSelectQueryPart() ); - queryPart.visitQuerySpecs( - querySpec -> { - // This returns true if the insertion target uses a sequence with an optimizer - // in which case we will fill the row_number column instead of the id column - if ( additionalInsertValues.applySelections( querySpec, sessionFactory ) ) { - final CteColumn rowNumberColumn = getCteTable().getCteColumns() - .get( getCteTable().getCteColumns().size() - 1 ); - final ColumnReference columnReference = new ColumnReference( - (String) null, - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - insertStatement.getTargetColumns().set( - insertStatement.getTargetColumns().size() - 1, - columnReference - ); - targetPathCteColumns.set( - targetPathCteColumns.size() - 1, - rowNumberColumn - ); - } - if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - querySpec.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( 0, SqmInsertStrategyHelper.createRowNumberingExpression( querySpec, sessionFactory ) ) ); - } - } - ); - queryStatement = new SelectStatement( queryPart ); - } - else { - final List sqmValuesList = ( (SqmInsertValuesStatement) sqmInsertStatement ).getValuesList(); - final List valuesList = new ArrayList<>( sqmValuesList.size() ); - for ( SqmValues sqmValues : sqmValuesList ) { - final Values values = sqmConverter.visitValues( sqmValues ); - additionalInsertValues.applyValues( values ); - valuesList.add( values ); - } - final QuerySpec querySpec = new QuerySpec( true ); - final NavigablePath navigablePath = new NavigablePath( entityDescriptor.getRootPathName() ); - final List columnNames = new ArrayList<>( targetPathColumns.size() ); - final String valuesAlias = insertingTableGroup.getPrimaryTableReference().getIdentificationVariable(); - for ( Map.Entry, Assignment> entry : targetPathColumns ) { - for ( ColumnReference columnReference : entry.getValue().getAssignable().getColumnReferences() ) { - columnNames.add( columnReference.getColumnExpression() ); - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - columnReference.getQualifier().equals( valuesAlias ) - ? columnReference - : new ColumnReference( - valuesAlias, - columnReference.getColumnExpression(), - false, - null, - columnReference.getJdbcMapping() - ) - ) - ); - } - } - if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - SqmInsertStrategyHelper.createRowNumberingExpression( - querySpec, - sessionFactory - ) - ) - ); - } - final ValuesTableGroup valuesTableGroup = new ValuesTableGroup( - navigablePath, - entityDescriptor.getEntityPersister(), - valuesList, - insertingTableGroup.getPrimaryTableReference().getIdentificationVariable(), - columnNames, - true, - factory - ); - querySpec.getFromClause().addRoot( valuesTableGroup ); - queryStatement = new SelectStatement( querySpec ); - } - processingStateStack.push( oldState ); - sqmConverter.pruneTableGroupJoins(); - - if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - // Add the row number to the assignments - final CteColumn rowNumberColumn = getCteTable().getCteColumns() - .get( getCteTable().getCteColumns().size() - 1 ); - final ColumnReference columnReference = new ColumnReference( - (String) null, - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - insertStatement.getTargetColumns().add( columnReference ); - targetPathCteColumns.add( rowNumberColumn ); - } - - final CteTable entityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); - - // Create the main query spec that will return the count of rows - final QuerySpec querySpec = new QuerySpec( true, 1 ); - final List> domainResults = new ArrayList<>( 1 ); - final SelectStatement statement = new SelectStatement( querySpec, domainResults ); - - final CteStatement entityCte; - if ( additionalInsertValues.requiresRowNumberIntermediate() ) { - final CteTable fullEntityCteTable = getCteTable(); - final String baseTableName = "base_" + entityCteTable.getTableExpression(); - final CteStatement baseEntityCte = new CteStatement( - entityCteTable.withName( baseTableName ), - queryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( baseEntityCte ); - - final CteColumn rowNumberColumn = fullEntityCteTable.getCteColumns().get( - fullEntityCteTable.getCteColumns().size() - 1 - ); - final ColumnReference rowNumberColumnReference = new ColumnReference( - "e", - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ); - final CteColumn idColumn = fullEntityCteTable.getCteColumns().get( 0 ); - final BasicValuedMapping idType = (BasicValuedMapping) idColumn.getJdbcMapping(); - final Optimizer optimizer = ( (OptimizableGenerator) entityDescriptor.getGenerator() ).getOptimizer(); - final BasicValuedMapping integerType = (BasicValuedMapping) rowNumberColumn.getJdbcMapping(); - final Expression rowNumberMinusOneModuloIncrement = new BinaryArithmeticExpression( - new BinaryArithmeticExpression( - rowNumberColumnReference, - BinaryArithmeticOperator.SUBTRACT, - new QueryLiteral<>( - 1, - (BasicValuedMapping) rowNumberColumn.getJdbcMapping() - ), - integerType - ), - BinaryArithmeticOperator.MODULO, - new QueryLiteral<>( - optimizer.getIncrementSize(), - integerType - ), - integerType - ); - - // Create the CTE that fetches a new sequence value for the row numbers that need it - { - final QuerySpec rowsWithSequenceQuery = new QuerySpec( true ); - rowsWithSequenceQuery.getFromClause().addRoot( - new CteTableGroup( new NamedTableReference( baseTableName, "e" ) ) - ); - rowsWithSequenceQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - rowNumberColumnReference - ) - ); - final String fragment = ( (BulkInsertionCapableIdentifierGenerator) entityDescriptor.getGenerator() ) - .determineBulkInsertionIdentifierGenerationSelectFragment( - sessionFactory.getSqlStringGenerationContext() - ); - rowsWithSequenceQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 1, - new SelfRenderingSqlFragmentExpression( fragment ) - ) - ); - rowsWithSequenceQuery.applyPredicate( - new ComparisonPredicate( - rowNumberMinusOneModuloIncrement, - ComparisonOperator.EQUAL, - new QueryLiteral<>( - 0, - integerType - ) - ) - ); - final CteTable rowsWithSequenceCteTable = new CteTable( - ROW_NUMBERS_WITH_SEQUENCE_VALUE, - List.of( rowNumberColumn, idColumn ) - ); - final SelectStatement rowsWithSequenceStatement = new SelectStatement( rowsWithSequenceQuery ); - final CteStatement rowsWithSequenceCte = new CteStatement( - rowsWithSequenceCteTable, - rowsWithSequenceStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( rowsWithSequenceCte ); - } - - // Create the CTE that represents the entity cte - { - final QuerySpec entityQuery = new QuerySpec( true ); - final NavigablePath navigablePath = new NavigablePath( baseTableName ); - final TableGroup baseTableGroup = new TableGroupImpl( - navigablePath, - null, - new NamedTableReference( baseTableName, "e" ), - null - ); - final TableGroup rowsWithSequenceTableGroup = new CteTableGroup( - new NamedTableReference( - ROW_NUMBERS_WITH_SEQUENCE_VALUE, - "t" - ) - ); - baseTableGroup.addTableGroupJoin( - new TableGroupJoin( - rowsWithSequenceTableGroup.getNavigablePath(), - SqlAstJoinType.LEFT, - rowsWithSequenceTableGroup, - new ComparisonPredicate( - new BinaryArithmeticExpression( - rowNumberColumnReference, - BinaryArithmeticOperator.SUBTRACT, - rowNumberMinusOneModuloIncrement, - integerType - ), - ComparisonOperator.EQUAL, - new ColumnReference( - "t", - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ) - ) - ) - ); - entityQuery.getFromClause().addRoot( baseTableGroup ); - entityQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - 0, - new BinaryArithmeticExpression( - new ColumnReference( - "t", - idColumn.getColumnExpression(), - false, - null, - idColumn.getJdbcMapping() - ), - BinaryArithmeticOperator.ADD, - new BinaryArithmeticExpression( - rowNumberColumnReference, - BinaryArithmeticOperator.SUBTRACT, - new ColumnReference( - "t", - rowNumberColumn.getColumnExpression(), - false, - null, - rowNumberColumn.getJdbcMapping() - ), - integerType - ), - idType - ) - ) - ); - final CteTable finalEntityCteTable; - if ( targetPathCteColumns.contains( getCteTable().getCteColumns().get( 0 ) ) ) { - finalEntityCteTable = entityCteTable; - } - else { - targetPathCteColumns.add( 0, getCteTable().getCteColumns().get( 0 ) ); - finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); - } - final List cteColumns = finalEntityCteTable.getCteColumns(); - for ( int i = 1; i < cteColumns.size(); i++ ) { - final CteColumn cteColumn = cteColumns.get( i ); - entityQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - i, - new ColumnReference( - "e", - cteColumn.getColumnExpression(), - false, - null, - cteColumn.getJdbcMapping() - ) - ) - ); - } - - final SelectStatement entityStatement = new SelectStatement( entityQuery ); - entityCte = new CteStatement( - finalEntityCteTable, - entityStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( entityCte ); - } - } - else if ( !assignsId && entityDescriptor.getGenerator().generatedOnExecution() ) { - final String baseTableName = "base_" + entityCteTable.getTableExpression(); - final CteStatement baseEntityCte = new CteStatement( - entityCteTable.withName( baseTableName ), - queryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( baseEntityCte ); - targetPathCteColumns.add( 0, getCteTable().getCteColumns().get( 0 ) ); - final CteTable finalEntityCteTable = createCteTable( getCteTable(), targetPathCteColumns ); - final QuerySpec finalQuerySpec = new QuerySpec( true ); - final SelectStatement finalQueryStatement = new SelectStatement( finalQuerySpec ); - entityCte = new CteStatement( - finalEntityCteTable, - finalQueryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - } - else { - entityCte = new CteStatement( - entityCteTable, - queryStatement, - // The query cte will be reused multiple times - CteMaterialization.MATERIALIZED - ); - statement.addCteStatement( entityCte ); - } - - // Add all CTEs - final String baseInsertCte = addDmlCtes( - statement, - entityCte, - targetPathColumns, - assignsId, - sqmConverter, - factory - ); - - final Expression count = createCountStar( factory, sqmConverter ); - domainResults - .add( new BasicResult<>( 0, null, ( (SqlExpressible) count ).getJdbcMapping() ) ); - querySpec.getSelectClause().addSqlSelection( new SqlSelectionImpl( 0, count ) ); - querySpec.getFromClause().addRoot( - new CteTableGroup( - new NamedTableReference( - // We want to return the insertion count of the base table - baseInsertCte, - CTE_TABLE_IDENTIFIER - ) - ) - ); - - // Execute the statement - final JdbcServices jdbcServices = factory.getJdbcServices(); - final SqlAstTranslator translator = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( factory, statement ); - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), sqmConverter ), - new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) sqmConverter.getSqmParameterMappingModelExpressibleResolutions() - .get( parameter ); - } - }, - executionContext.getSession() - ); - final JdbcOperationQuerySelect select = translator.translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - return ( (ReactiveSharedSessionContractImplementor) executionContext.getSession() ) - .reactiveAutoFlushIfRequired( select.getAffectedTableNames() ) - .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE.list( - select, + public CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext context) { + return ( (ReactiveSharedSessionContractImplementor) context.getSession() ) + .reactiveAutoFlushIfRequired( getSelect().getAffectedTableNames() ) + .thenCompose( v -> StandardReactiveSelectExecutor.INSTANCE + .list( + getSelect(), jdbcParameterBindings, - SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ), + SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ), row -> row[0], - ReactiveListResultsConsumer.UniqueSemantic.NONE + null, + ReactiveListResultsConsumer.UniqueSemantic.NONE, + 1 ) - .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) ); + .thenApply( list -> ( (Number) list.get( 0 ) ).intValue() ) + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java index d7259ebb1..e0a0e10fd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteInsertStrategy.java @@ -5,16 +5,18 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.util.concurrent.CompletionStage; - +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.InsertHandler; import org.hibernate.query.sqm.mutation.internal.cte.CteInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveCteInsertStrategy extends CteInsertStrategy implements ReactiveSqmMultiTableInsertStrategy { @@ -31,11 +33,16 @@ public ReactiveCteInsertStrategy( } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new ReactiveCteInsertHandler( getEntityCteTable(), sqmInsertStatement, domainParameterXref, getSessionFactory() ) - .reactiveExecute( context ); + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final InsertHandler multiTableHandler = new ReactiveCteInsertHandler( + getEntityCteTable(), + sqmInsertStatement, + domainParameterXref, + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java index 6da230aad..be67bd8ad 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java @@ -5,17 +5,21 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.util.concurrent.CompletionStage; - +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveCteMutationStrategy extends CteMutationStrategy implements ReactiveSqmMultiTableMutationStrategy { @@ -28,22 +32,51 @@ public ReactiveCteMutationStrategy(EntityPersister rootDescriptor, RuntimeModelC } @Override - public CompletionStage reactiveExecuteUpdate( - SqmUpdateStatement sqmUpdateStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - checkMatch( sqmUpdateStatement ); - return new ReactiveCteUpdateHandler( getIdCteTable(), sqmUpdateStatement, domainParameterXref, this, getSessionFactory() ) - .reactiveExecute( context ); + public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = sqmStatement instanceof SqmDeleteStatement sqmDelete + ? buildHandler( sqmDelete, domainParameterXref, context, firstJdbcParameterBindings) + : buildHandler( (SqmUpdateStatement) sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } - @Override - public CompletionStage reactiveExecuteDelete( - SqmDeleteStatement sqmDeleteStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - checkMatch( sqmDeleteStatement ); - return new ReactiveCteDeleteHandler( getIdCteTable(), sqmDeleteStatement, domainParameterXref, this, getSessionFactory() ) - .reactiveExecute( context ); + public ReactiveHandler buildHandler(SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { + checkMatch( sqmDelete ); + if ( getRootDescriptor().getSoftDeleteMapping() != null ) { + return new ReactiveCteSoftDeleteHandler( + getIdCteTable(), + sqmDelete, + domainParameterXref, + this, + getSessionFactory(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveCteDeleteHandler( + getIdCteTable(), + sqmDelete, + domainParameterXref, + this, + getSessionFactory(), + context, + firstJdbcParameterBindingsConsumer + ); + } } + + public MultiTableHandler buildHandler(SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { + checkMatch( sqmUpdate ); + return new ReactiveCteUpdateHandler( + getIdCteTable(), + sqmUpdate, + domainParameterXref, + this, + getSessionFactory(), + context, + firstJdbcParameterBindingsConsumer + ); + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java new file mode 100644 index 000000000..8e0740d14 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java @@ -0,0 +1,43 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.mutation.internal.cte; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; +import org.hibernate.query.sqm.mutation.internal.cte.CteSoftDeleteHandler; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.sql.ast.tree.cte.CteTable; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +public class ReactiveCteSoftDeleteHandler extends CteSoftDeleteHandler implements ReactiveAbstractCteMutationHandler { + protected ReactiveCteSoftDeleteHandler( + CteTable cteTable, + SqmDeleteStatement sqmDeleteStatement, + DomainParameterXref domainParameterXref, + CteMutationStrategy strategy, + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( + cteTable, + sqmDeleteStatement, + domainParameterXref, + strategy, + sessionFactory, + context, + firstJdbcParameterBindingsConsumer + ); + } + + @Override + public JdbcOperationQuerySelect getSelect() { + return super.getSelect(); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java index 0730ff33e..c855f35ad 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java @@ -5,59 +5,35 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.cte; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; - import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; import org.hibernate.query.sqm.mutation.internal.cte.CteUpdateHandler; -import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.sql.ast.tree.cte.CteContainer; -import org.hibernate.sql.ast.tree.cte.CteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see CteUpdateHandler */ public class ReactiveCteUpdateHandler extends CteUpdateHandler implements ReactiveAbstractCteMutationHandler { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public ReactiveCteUpdateHandler( CteTable cteTable, SqmUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, CteMutationStrategy strategy, - SessionFactoryImplementor sessionFactory) { - super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory ); - } - - @Override - public void addDmlCtes( - CteContainer statement, - CteStatement idSelectCte, - MultiTableSqmMutationConverter sqmConverter, - Map, List> parameterResolutions, - SessionFactoryImplementor factory) { - super.addDmlCtes( statement, idSelectCte, sqmConverter, parameterResolutions, factory ); - } - - @Override - public int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); + SessionFactoryImplementor sessionFactory, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( cteTable, sqmStatement, domainParameterXref, strategy, sessionFactory, context, firstJdbcParameterBindingsConsumer ); } @Override - public Expression createCountStar(SessionFactoryImplementor factory, MultiTableSqmMutationConverter sqmConverter) { - return super.createCountStar( factory, sqmConverter ); + public JdbcOperationQuerySelect getSelect() { + return super.getSelect(); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java deleted file mode 100644 index 399e8b30a..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveInsertExecutionDelegate.java +++ /dev/null @@ -1,72 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.cte; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.InsertExecutionDelegate; -import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTableBasedInsertHandler; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.insert.ConflictClause; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.exec.spi.ExecutionContext; - -/** - * @see InsertExecutionDelegate - */ -public class ReactiveInsertExecutionDelegate extends InsertExecutionDelegate implements ReactiveTableBasedInsertHandler.ReactiveExecutionDelegate { - - public ReactiveInsertExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable entityTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup insertingTableGroup, - Map tableReferenceByAlias, - List assignments, - boolean assignedId, - InsertSelectStatement insertStatement, - ConflictClause conflictClause, - JdbcParameter sessionUidParameter, - DomainQueryExecutionContext executionContext) { - super( - sqmConverter, - entityTable, - temporaryTableStrategy, - forceDropAfterUse, - sessionUidAccess, - domainParameterXref, - insertingTableGroup, - tableReferenceByAlias, - assignments, - assignedId, - insertStatement, - conflictClause, - sessionUidParameter, - executionContext - ); - } - - @Override - public CompletionStage reactiveExecute(ExecutionContext executionContext) { - // FIXME: Why is this null? - return null; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java index d936c590f..164403171 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveExecuteWithTemporaryTableHelper.java @@ -7,6 +7,8 @@ import java.lang.invoke.MethodHandles; import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.UUID; import java.util.concurrent.CompletionStage; import java.util.function.Function; @@ -16,42 +18,34 @@ import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableColumn; +import org.hibernate.dialect.temptable.TemporaryTableSessionUidColumn; import org.hibernate.dialect.temptable.TemporaryTableStrategy; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.query.sqm.ComparisonOperator; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; +import org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithTemporaryTableHelper; import org.hibernate.query.sqm.mutation.spi.AfterUseAction; import org.hibernate.query.sqm.mutation.spi.BeforeUseAction; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTemporaryTableHelper.TemporaryTableCreationWork; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.QueryLiteral; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.StandardTableGroup; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.internal.SqlSelectionImpl; import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveTemporaryTableHelper.cleanTemporaryTableRows; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; @@ -68,75 +62,6 @@ public final class ReactiveExecuteWithTemporaryTableHelper { private ReactiveExecuteWithTemporaryTableHelper() { } - public static CompletionStage saveMatchingIdsIntoIdTable( - MultiTableSqmMutationConverter sqmConverter, - Predicate suppliedPredicate, - TemporaryTable idTable, - Function sessionUidAccess, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - - final TableGroup mutatingTableGroup = sqmConverter.getMutatingTableGroup(); - - assert mutatingTableGroup.getModelPart() instanceof EntityMappingType; - final EntityMappingType mutatingEntityDescriptor = (EntityMappingType) mutatingTableGroup.getModelPart(); - - final NamedTableReference idTableReference = new NamedTableReference( - idTable.getTableExpression(), - InsertSelectStatement.DEFAULT_ALIAS - ); - final InsertSelectStatement idTableInsert = new InsertSelectStatement( idTableReference ); - - for ( int i = 0; i < idTable.getColumns().size(); i++ ) { - final TemporaryTableColumn column = idTable.getColumns().get( i ); - idTableInsert.addTargetColumnReferences( - new ColumnReference( - idTableReference, - column.getColumnName(), - // id columns cannot be formulas and cannot have custom read and write expressions - false, - null, - column.getJdbcMapping() - ) - ); - } - - final QuerySpec matchingIdSelection = new QuerySpec( true, 1 ); - idTableInsert.setSourceSelectStatement( matchingIdSelection ); - - matchingIdSelection.getFromClause().addRoot( mutatingTableGroup ); - - mutatingEntityDescriptor.getIdentifierMapping().forEachSelectable( - (selectionIndex, selection) -> { - matchingIdSelection.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - selectionIndex + 1, - sqmConverter.getSqlExpressionResolver().resolveSqlExpression( - mutatingTableGroup.resolveTableReference( mutatingTableGroup.getNavigablePath(), selection.getContainingTableExpression() ), - selection - ) - ) - ); - } - ); - - if ( idTable.getSessionUidColumn() != null ) { - final int jdbcPosition = matchingIdSelection.getSelectClause().getSqlSelections().size(); - matchingIdSelection.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - jdbcPosition, - new QueryLiteral<>( - UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ), - (BasicValuedMapping) idTable.getSessionUidColumn().getJdbcMapping() - ) - ) - ); - } - - matchingIdSelection.applyPredicate( suppliedPredicate ); - return saveIntoTemporaryTable( idTableInsert, jdbcParameterBindings, executionContext ); - } - public static CompletionStage saveIntoTemporaryTable( InsertSelectStatement temporaryTableInsert, JdbcParameterBindings jdbcParameterBindings, @@ -150,9 +75,10 @@ public static CompletionStage saveIntoTemporaryTable( // Acquire a WRITE lock for the rows that are about to be modified lockOptions.setLockMode( LockMode.WRITE ); // Visit the table joins and reset the lock mode if we encounter OUTER joins that are not supported - if ( temporaryTableInsert.getSourceSelectStatement() != null + final QueryPart sourceSelectStatement = temporaryTableInsert.getSourceSelectStatement(); + if ( sourceSelectStatement != null && !jdbcEnvironment.getDialect().supportsOuterJoinForUpdate() ) { - temporaryTableInsert.getSourceSelectStatement().visitQuerySpecs( + sourceSelectStatement.visitQuerySpecs( querySpec -> querySpec.getFromClause().visitTableJoins( tableJoin -> { if ( tableJoin.isInitialized() @@ -167,124 +93,39 @@ public static CompletionStage saveIntoTemporaryTable( .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); lockOptions.setLockMode( lockMode ); + return saveIntoTemporaryTable(jdbcInsert, jdbcParameterBindings, executionContext); + } + + public static CompletionStage saveIntoTemporaryTable( + JdbcOperationQueryMutation jdbcInsert, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { return StandardReactiveJdbcMutationExecutor.INSTANCE .executeReactive( jdbcInsert, jdbcParameterBindings, - executionContext.getSession().getJdbcCoordinator().getStatementPreparer()::prepareStatement, - ReactiveExecuteWithTemporaryTableHelper::doNothing, + sql -> executionContext.getSession().getJdbcCoordinator() + .getStatementPreparer().prepareStatement( sql ), + (integer, preparedStatement) -> {}, executionContext - ); } public static QuerySpec createIdTableSelectQuerySpec( TemporaryTable idTable, - Function sessionUidAccess, + JdbcParameter sessionUidParameter, EntityMappingType entityDescriptor, ExecutionContext executionContext) { - return createIdTableSelectQuerySpec( idTable, null, sessionUidAccess, entityDescriptor, executionContext ); + return createIdTableSelectQuerySpec( idTable, null, sessionUidParameter, entityDescriptor, executionContext ); } public static QuerySpec createIdTableSelectQuerySpec( TemporaryTable idTable, ModelPart fkModelPart, - Function sessionUidAccess, + JdbcParameter sessionUidParameter, EntityMappingType entityDescriptor, ExecutionContext executionContext) { - final QuerySpec querySpec = new QuerySpec( false ); - - final NamedTableReference idTableReference = new NamedTableReference( - idTable.getTableExpression(), - TemporaryTable.DEFAULT_ALIAS, - true - ); - final TableGroup idTableGroup = new StandardTableGroup( - true, - new NavigablePath( idTableReference.getTableExpression() ), - entityDescriptor, - null, - idTableReference, - null, - executionContext.getSession().getFactory() - ); - - querySpec.getFromClause().addRoot( idTableGroup ); - - applyIdTableSelections( querySpec, idTableReference, idTable, fkModelPart, entityDescriptor ); - applyIdTableRestrictions( querySpec, idTableReference, idTable, sessionUidAccess, executionContext ); - - return querySpec; - } - - // TODO: I think we can reuse the method in ExecuteWithTemporaryTableHelper - private static void applyIdTableSelections( - QuerySpec querySpec, - TableReference tableReference, - TemporaryTable idTable, - ModelPart fkModelPart, - EntityMappingType entityDescriptor) { - if ( fkModelPart == null ) { - final int size = entityDescriptor.getIdentifierMapping().getJdbcTypeCount(); - for ( int i = 0; i < size; i++ ) { - final TemporaryTableColumn temporaryTableColumn = idTable.getColumns().get( i ); - if ( temporaryTableColumn != idTable.getSessionUidColumn() ) { - querySpec.getSelectClause().addSqlSelection( - new SqlSelectionImpl( - i, - new ColumnReference( - tableReference, - temporaryTableColumn.getColumnName(), - false, - null, - temporaryTableColumn.getJdbcMapping() - ) - ) - ); - } - } - } - else { - fkModelPart.forEachSelectable( - (i, selectableMapping) -> querySpec.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( - i, - new ColumnReference( - tableReference, - selectableMapping.getSelectionExpression(), - false, - null, - selectableMapping.getJdbcMapping() - ) - ) - ) - ); - } - } - - private static void applyIdTableRestrictions( - QuerySpec querySpec, - TableReference idTableReference, - TemporaryTable idTable, - Function sessionUidAccess, - ExecutionContext executionContext) { - if ( idTable.getSessionUidColumn() != null ) { - querySpec.applyPredicate( - new ComparisonPredicate( - new ColumnReference( - idTableReference, - idTable.getSessionUidColumn().getColumnName(), - false, null, - idTable.getSessionUidColumn().getJdbcMapping() - ), - ComparisonOperator.EQUAL, - new QueryLiteral<>( - UUID.fromString( sessionUidAccess.apply( executionContext.getSession() ) ), - (BasicValuedMapping) idTable.getSessionUidColumn().getJdbcMapping() - ) - ) - ); - } + return ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec( idTable, fkModelPart, sessionUidParameter, entityDescriptor, executionContext ); } @Deprecated(forRemoval = true, since = "3.1") @@ -336,6 +177,46 @@ public static CompletionStage performAfterTemporaryTableUseActions( }; } + public static CompletionStage loadInsertedRowNumbers( + String sqlSelect, + TemporaryTable temporaryTable, + Function sessionUidAccess, + int rows, + ExecutionContext executionContext) { + final TemporaryTableSessionUidColumn sessionUidColumn = temporaryTable.getSessionUidColumn(); + final SharedSessionContractImplementor session = executionContext.getSession(); + final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); + PreparedStatement preparedStatement = null; + preparedStatement = jdbcCoordinator.getStatementPreparer().prepareStatement( sqlSelect ); + Object[] parameters = new Object[1]; + if ( sessionUidColumn != null ) { + parameters[0] = UUID.fromString( sessionUidAccess.apply( session ) ); + } + final Integer[] rowNumbers = new Integer[rows]; + return reactiveConnection(session).selectJdbc( sqlSelect, parameters ) + .thenApply( resultSet -> getRowNumbers( rows, resultSet, rowNumbers ) ); + } + + private static Integer[] getRowNumbers(int rows, ResultSet resultSet, Integer[] rowNumbers) { + int rowIndex = 0; + try { + while ( resultSet.next() ) { + rowNumbers[rowIndex++] = resultSet.getInt( 1 ); + } + return rowNumbers; + } + catch ( IndexOutOfBoundsException e ) { + throw new IllegalArgumentException( "Expected " + rows + " to be inserted but found more", e ); + } + catch ( SQLException ex ) { + throw new IllegalStateException( ex ); + } + } + + private static ReactiveConnection reactiveConnection(SharedSessionContractImplementor session) { + return ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); + } + private static CompletionStage dropAction( TemporaryTable temporaryTable, ExecutionContext executionContext, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java index 708a7b072..9ca1dfce8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableInsertStrategy.java @@ -6,16 +6,19 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.InsertHandler; import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableInsertStrategy @@ -48,11 +51,9 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage.thenCompose( v -> new ReactiveTableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final InsertHandler multiTableHandler = new ReactiveTableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), @@ -60,12 +61,13 @@ public CompletionStage reactiveExecuteInsert( false, // generally a global temp table should already track a Connection-specific uid, // but just in case a particular env needs it... - ReactiveGlobalTemporaryTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } - @Override public boolean isPrepared() { return prepared; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java index baa4ed9a5..aaad73b10 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java @@ -6,22 +6,28 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy; import org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** * @see org.hibernate.query.sqm.mutation.internal.temptable.GlobalTemporaryTableMutationStrategy */ -public class ReactiveGlobalTemporaryTableMutationStrategy extends GlobalTemporaryTableStrategy +public class ReactiveGlobalTemporaryTableMutationStrategy extends GlobalTemporaryTableMutationStrategy implements ReactiveGlobalTemporaryTableStrategy, ReactiveSqmMultiTableMutationStrategy { private final CompletableFuture tableCreatedStage = new CompletableFuture<>(); @@ -50,37 +56,68 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteUpdate( - SqmUpdateStatement sqmUpdateStatement, + public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement sqmStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = sqmStatement instanceof SqmDeleteStatement sqmDelete + ? buildHandler( sqmDelete, domainParameterXref, context, firstJdbcParameterBindings ) + : buildHandler( (SqmUpdateStatement) sqmStatement, domainParameterXref, context, firstJdbcParameterBindings ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); + } + + public MultiTableHandler buildHandler( + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedUpdateHandler( - sqmUpdateStatement, - domainParameterXref, - getTemporaryTable(), - getTemporaryTableStrategy(), - false, - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + return new ReactiveTableBasedUpdateHandler( + sqmUpdate, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); } - @Override - public CompletionStage reactiveExecuteDelete( - SqmDeleteStatement sqmDeleteStatement, + public MultiTableHandler buildHandler( + SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedDeleteHandler( - sqmDeleteStatement, - domainParameterXref, - getTemporaryTable(), - getTemporaryTableStrategy(), - false, - ReactiveGlobalTemporaryTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new ReactiveTableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveTableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + // generally a global temp table should already track a Connection-specific uid, + // but just in case a particular env needs it... + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java index 322127730..21a9b3ced 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableInsertStrategy.java @@ -5,15 +5,15 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.util.concurrent.CompletionStage; - -import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableInsertStrategy; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveLocalTemporaryTableInsertStrategy extends LocalTemporaryTableInsertStrategy implements ReactiveSqmMultiTableInsertStrategy { @@ -23,28 +23,21 @@ public ReactiveLocalTemporaryTableInsertStrategy(LocalTemporaryTableInsertStrate } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new ReactiveTableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = new ReactiveTableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), getTemporaryTableStrategy(), isDropIdTables(), - ReactiveLocalTemporaryTableInsertStrategy::throwUnexpectedCallToSessionUIDError, - getSessionFactory() - ).reactiveExecute( context ); - } - - private static String throwUnexpectedCallToSessionUIDError(SharedSessionContractImplementor session) { - throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } - private AfterUseAction afterUserAction() { - return isDropIdTables() - ? AfterUseAction.DROP - : getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java index d22d50e5b..1073f118f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java @@ -5,16 +5,16 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.util.concurrent.CompletionStage; - -import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.temptable.LocalTemporaryTableMutationStrategy; -import org.hibernate.query.sqm.mutation.spi.AfterUseAction; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveLocalTemporaryTableMutationStrategy extends LocalTemporaryTableMutationStrategy implements ReactiveSqmMultiTableMutationStrategy { @@ -23,46 +23,59 @@ public ReactiveLocalTemporaryTableMutationStrategy(LocalTemporaryTableMutationSt super( mutationStrategy.getTemporaryTable(), mutationStrategy.getSessionFactory() ); } - private static String throwUnexpectedAccessToSessionUID(SharedSessionContractImplementor session) { - // Should probably go in the LOG - throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); - } - - @Override - public CompletionStage reactiveExecuteUpdate( + public MultiTableHandler buildHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { return new ReactiveTableBasedUpdateHandler( sqmUpdate, domainParameterXref, getTemporaryTable(), getTemporaryTableStrategy(), isDropIdTables(), - ReactiveLocalTemporaryTableMutationStrategy::throwUnexpectedAccessToSessionUID, - getSessionFactory() - ).reactiveExecute( context ); + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); } - @Override - public CompletionStage reactiveExecuteDelete( + public MultiTableHandler buildHandler( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return new ReactiveTableBasedDeleteHandler( - sqmDelete, - domainParameterXref, - getTemporaryTable(), - getTemporaryTableStrategy(), - isDropIdTables(), - ReactiveLocalTemporaryTableMutationStrategy::throwUnexpectedAccessToSessionUID, - getSessionFactory() - ).reactiveExecute( context ); - } - - private AfterUseAction afterUseAction() { - return isDropIdTables() - ? AfterUseAction.DROP - : getSessionFactory().getJdbcServices().getDialect().getTemporaryTableAfterUseAction(); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new ReactiveTableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveTableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + isDropIdTables(), + session -> { + throw new UnsupportedOperationException( "Unexpected call to access Session uid" ); + }, + context, + firstJdbcParameterBindingsConsumer + ); + } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java index 951cabce1..a5b0c36c3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableInsertStrategy.java @@ -10,12 +10,16 @@ import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableInsertStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableInsertStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactivePersistentTableInsertStrategy extends PersistentTableInsertStrategy implements ReactivePersistentTableStrategy, ReactiveSqmMultiTableInsertStrategy { @@ -45,19 +49,19 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce } @Override - public CompletionStage reactiveExecuteInsert( - SqmInsertStatement sqmInsertStatement, - DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage.thenCompose( v -> new ReactiveTableBasedInsertHandler( + public MultiTableHandlerBuildResult buildHandler(SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context) { + final MutableObject firstJdbcParameterBindings = new MutableObject<>(); + final MultiTableHandler multiTableHandler = new ReactiveTableBasedInsertHandler( sqmInsertStatement, domainParameterXref, getTemporaryTable(), getTemporaryTableStrategy(), false, - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindings + ); + return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java index 8f23ce727..7b7a641ae 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java @@ -5,18 +5,22 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.mutation.internal.temptable.PersistentTableMutationStrategy; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveSqmMultiTableMutationStrategy; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; public class ReactivePersistentTableMutationStrategy extends PersistentTableMutationStrategy @@ -46,40 +50,55 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce release( sessionFactory, connectionAccess, tableDroppedStage ); } - @Override - public CompletionStage reactiveExecuteUpdate( - SqmUpdateStatement sqmUpdateStatement, + public MultiTableHandler buildHandler( + SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedUpdateHandler( - sqmUpdateStatement, - domainParameterXref, - getTemporaryTable(), - getTemporaryTableStrategy(), - false, - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + return new ReactiveTableBasedUpdateHandler( + sqmUpdate, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); } - @Override - public CompletionStage reactiveExecuteDelete( - SqmDeleteStatement sqmDeleteStatement, + public MultiTableHandler buildHandler( + SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context) { - return tableCreatedStage - .thenCompose( v -> new ReactiveTableBasedDeleteHandler( - sqmDeleteStatement, - domainParameterXref, - getTemporaryTable(), - getTemporaryTableStrategy(), - false, - ReactivePersistentTableStrategy::sessionIdentifier, - getSessionFactory() - ).reactiveExecute( context ) ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + final EntityPersister rootDescriptor = context.getSession().getFactory().getMappingMetamodel() + .getEntityDescriptor( sqmDelete.getRoot().getEntityName() ); + if ( rootDescriptor.getSoftDeleteMapping() != null ) { + return new ReactiveTableBasedSoftDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } + else { + return new ReactiveTableBasedDeleteHandler( + sqmDelete, + domainParameterXref, + getTemporaryTable(), + getTemporaryTableStrategy(), + false, + session -> session.getSessionIdentifier().toString(), + context, + firstJdbcParameterBindingsConsumer + ); + } } - @Override public CompletionStage getDropTableActionStage() { return tableDroppedStage; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java deleted file mode 100644 index c119e718a..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveRestrictedDeleteExecutionDelegate.java +++ /dev/null @@ -1,605 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.temptable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.MutableInteger; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.SelectableConsumer; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.TableKeyExpressionCollector; -import org.hibernate.query.sqm.mutation.internal.temptable.AbstractDeleteExecutionDelegate; -import org.hibernate.query.sqm.mutation.internal.temptable.ColumnReferenceCheckingSqlAstWalker; -import org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithoutIdTableHelper; -import org.hibernate.query.sqm.mutation.internal.temptable.RestrictedDeleteExecutionDelegate; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.reactive.util.impl.CompletionStages.Completable; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.spi.SqlExpressionResolver; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.expression.SqlTuple; -import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.from.UnionTableReference; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.predicate.PredicateCollector; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; - -import static java.lang.invoke.MethodHandles.lookup; -import static org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; -import static org.hibernate.reactive.logging.impl.LoggerFactory.make; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -/** - * The reactive version of {@link RestrictedDeleteExecutionDelegate} - */ -// Basically a copy of RestrictedDeleteExecutionDelegate, we will probably need to refactor this code to avoid duplication -public class ReactiveRestrictedDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate - implements ReactiveTableBasedDeleteHandler.ReactiveExecutionDelegate { - - private static final Log LOG = make( Log.class, lookup() ); - - public ReactiveRestrictedDeleteExecutionDelegate( - EntityMappingType entityDescriptor, TemporaryTable idTable, TemporaryTableStrategy temporaryTableStrategy, boolean forceDropAfterUse, SqmDeleteStatement sqmDelete, - DomainParameterXref domainParameterXref, QueryOptions queryOptions, LoadQueryInfluencers loadQueryInfluencers, QueryParameterBindings queryParameterBindings, - Function sessionUidAccess, SessionFactoryImplementor sessionFactory - ) { - super( - entityDescriptor, idTable, temporaryTableStrategy, forceDropAfterUse, sqmDelete, domainParameterXref, queryOptions, loadQueryInfluencers, queryParameterBindings, sessionUidAccess, - sessionFactory - ); - } - - - @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { - final EntityPersister entityDescriptor = getSessionFactory().getRuntimeMetamodels() - .getMappingMetamodel() - .getEntityDescriptor( getSqmDelete().getTarget().getEntityName() ); - final String hierarchyRootTableName = entityDescriptor.getTableName(); - - final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup(); - - final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( - deletingTableGroup.getNavigablePath(), - hierarchyRootTableName - ); - assert hierarchyRootTableReference != null; - - // Use the converter to interpret the where-clause. We do this for 2 reasons: - // 1) the resolved Predicate is ultimately the base for applying restriction to the deletes - // 2) we also inspect each ColumnReference that is part of the where-clause to see which - // table it comes from. If all the referenced columns (if any at all) are from the root table - // we can perform all the deletes without using an id-table - final Predicate specifiedRestriction = getConverter().visitWhereClause( getSqmDelete().getWhereClause() ); - - final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); - entityDescriptor.applyBaseRestrictions( - predicateCollector, - deletingTableGroup, - true, - executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), - false, - null, - getConverter() - ); - - getConverter().pruneTableGroupJoins(); - final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( - hierarchyRootTableReference.getIdentificationVariable() - ); - if ( predicateCollector.getPredicate() != null ) { - predicateCollector.getPredicate().accept( walker ); - } - - // We need an id table if we want to delete from an intermediate table to avoid FK violations - // The intermediate table has a FK to the root table, so we can't delete from the root table first - // Deleting from the intermediate table first also isn't possible, - // because that is the source for deletion in other tables, hence we need an id table - final boolean needsIdTable = !walker.isAllColumnReferencesFromIdentificationVariable() - || entityDescriptor != entityDescriptor.getRootEntityDescriptor(); - - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( - executionContext ); - - if ( needsIdTable ) { - return executeWithIdTable( - predicateCollector.getPredicate(), - getConverter().getJdbcParamsBySqmParam(), - getConverter().getSqmParameterMappingModelExpressibleResolutions(), - executionContextAdapter - ); - } - else { - return executeWithoutIdTable( - predicateCollector.getPredicate(), - deletingTableGroup, - getConverter().getJdbcParamsBySqmParam(), - getConverter().getSqmParameterMappingModelExpressibleResolutions(), - getConverter().getSqlExpressionResolver(), - executionContextAdapter - ); - } - } - - private CompletionStage executeWithoutIdTable( - Predicate suppliedPredicate, - TableGroup tableGroup, - Map, List>> restrictionSqmParameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, - SqlExpressionResolver sqlExpressionResolver, - ExecutionContext executionContext) { - assert getEntityDescriptor() == getEntityDescriptor().getRootEntityDescriptor(); - - final EntityPersister rootEntityPersister = getEntityDescriptor().getEntityPersister(); - final String rootTableName = rootEntityPersister.getTableName(); - final NamedTableReference rootTableReference = (NamedTableReference) tableGroup.resolveTableReference( - tableGroup.getNavigablePath(), - rootTableName - ); - - final QuerySpec matchingIdSubQuerySpec = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( - tableGroup.getNavigablePath(), - rootTableReference, - suppliedPredicate, - rootEntityPersister, - sqlExpressionResolver, - getSessionFactory() - ); - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( - getDomainParameterXref(), - () -> restrictionSqmParameterResolutions - ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - executionContext.getSession() - ); - - - CompletionStage cleanUpCollectionTablesStage = ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - getEntityDescriptor(), - (tableReference, attributeMapping) -> { - // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup - if ( suppliedPredicate == null ) { - return null; - } - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idSelectFkSubQuery; - // todo (6.0): based on the location of the attribute mapping, we could prune the table group of the subquery - if ( fkDescriptor.getTargetPart().isEntityIdentifierMapping() ) { - idSelectFkSubQuery = matchingIdSubQuerySpec; - } - else { - idSelectFkSubQuery = ExecuteWithoutIdTableHelper.createIdMatchingSubQuerySpec( - tableGroup.getNavigablePath(), - rootTableReference, - suppliedPredicate, - rootEntityPersister, - sqlExpressionResolver, - getSessionFactory() - ); - } - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - getSessionFactory() - ), - idSelectFkSubQuery, - false - ); - - }, - jdbcParameterBindings, - executionContext - ); - - final CompletionStage[] deleteFromNonRootStages = new CompletionStage[] { voidFuture() }; - if ( rootTableReference instanceof UnionTableReference ) { - final MutableInteger rows = new MutableInteger(); - return cleanUpCollectionTablesStage - .thenCompose( v -> visitUnionTableReferences( - suppliedPredicate, - tableGroup, - sqlExpressionResolver, - executionContext, - matchingIdSubQuerySpec, - jdbcParameterBindings, - deleteFromNonRootStages, - rows - ) ) - .thenApply( o -> rows.get() ); - } - else { - getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> { - if ( !tableExpression.equals( rootTableName ) ) { - final NamedTableReference tableReference = (NamedTableReference) tableGroup.getTableReference( - tableGroup.getNavigablePath(), - tableExpression, - true - ); - final QuerySpec idMatchingSubQuerySpec; - // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup - idMatchingSubQuerySpec = suppliedPredicate == null ? null : matchingIdSubQuerySpec; - CompletableFuture future = new CompletableFuture<>(); - deleteFromNonRootStages[0] = deleteFromNonRootStages[0] - .thenCompose( v -> future ); - try { - deleteFromNonRootTableWithoutIdTable( - tableReference, - tableKeyColumnVisitationSupplier, - sqlExpressionResolver, - tableGroup, - idMatchingSubQuerySpec, - jdbcParameterBindings, - executionContext - ) - .thenCompose( CompletionStages::voidFuture ) - .whenComplete( (unused, throwable) -> { - if ( throwable == null ) { - future.complete( unused ); - } - else { - future.completeExceptionally( throwable ); - } - } ); - } - catch (Throwable t) { - future.completeExceptionally( t ); - } - } - } - ); - - return deleteFromNonRootStages[0] - .thenCompose( v -> deleteFromRootTableWithoutIdTable( - rootTableReference, - suppliedPredicate, - jdbcParameterBindings, - executionContext - ) ); - } - } - - private CompletionStage visitUnionTableReferences( - Predicate suppliedPredicate, - TableGroup tableGroup, - SqlExpressionResolver sqlExpressionResolver, - ExecutionContext executionContext, - QuerySpec matchingIdSubQuerySpec, - JdbcParameterBindings jdbcParameterBindings, - CompletionStage[] deleteFromNonRootStages, - MutableInteger rows) { - getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> { - final NamedTableReference tableReference = new NamedTableReference( - tableExpression, - tableGroup.getPrimaryTableReference().getIdentificationVariable() - ); - final QuerySpec idMatchingSubQuerySpec; - // No need for a predicate if there is no supplied predicate i.e. this is a full cleanup - idMatchingSubQuerySpec = suppliedPredicate == null ? null : matchingIdSubQuerySpec; - CompletableFuture future = new CompletableFuture<>(); - deleteFromNonRootStages[0] = deleteFromNonRootStages[0] - .thenCompose( v -> future ); - deleteFromNonRootTableWithoutIdTable( - tableReference, - tableKeyColumnVisitationSupplier, - sqlExpressionResolver, - tableGroup, - idMatchingSubQuerySpec, - jdbcParameterBindings, - executionContext - ) - .thenAccept( rows::plus ) - .whenComplete( (unused, throwable) -> { - if ( throwable == null ) { - future.complete( unused ); - } - else { - future.completeExceptionally( throwable ); - } - } ); - } - ); - return deleteFromNonRootStages[0]; - } - - private CompletionStage deleteFromRootTableWithoutIdTable( - NamedTableReference rootTableReference, - Predicate predicate, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - return executeSqlDelete( - new DeleteStatement( rootTableReference, predicate ), - jdbcParameterBindings, - executionContext - ); - } - - private CompletionStage deleteFromNonRootTableWithoutIdTable( - NamedTableReference targetTableReference, - Supplier> tableKeyColumnVisitationSupplier, - SqlExpressionResolver sqlExpressionResolver, - TableGroup rootTableGroup, - QuerySpec matchingIdSubQuerySpec, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - assert targetTableReference != null; - LOG.tracef( "deleteFromNonRootTable - %s", targetTableReference.getTableExpression() ); - - final NamedTableReference deleteTableReference = new NamedTableReference( - targetTableReference.getTableExpression(), - DeleteStatement.DEFAULT_ALIAS, - true - ); - final Predicate tableDeletePredicate; - if ( matchingIdSubQuerySpec == null ) { - tableDeletePredicate = null; - } - else { - /* - * delete from sub_table - * where sub_id in ( - * select root_id from root_table - * where {predicate} - * ) - */ - - /* - * Create the `sub_id` reference as the LHS of the in-subquery predicate - */ - final List deletingTableColumnRefs = new ArrayList<>(); - tableKeyColumnVisitationSupplier.get().accept( - (columnIndex, selection) -> { - assert deleteTableReference.getTableReference( selection.getContainingTableExpression() ) != null; - - final Expression expression = sqlExpressionResolver.resolveSqlExpression( - deleteTableReference, - selection - ); - - deletingTableColumnRefs.add( (ColumnReference) expression ); - } - ); - - final Expression deletingTableColumnRefsExpression = deletingTableColumnRefs.size() == 1 - ? deletingTableColumnRefs.get( 0 ) - : new SqlTuple( deletingTableColumnRefs, getEntityDescriptor().getIdentifierMapping() ); - - tableDeletePredicate = new InSubQueryPredicate( - deletingTableColumnRefsExpression, - matchingIdSubQuerySpec, - false - ); - } - - final DeleteStatement sqlAstDelete = new DeleteStatement( deleteTableReference, tableDeletePredicate ); - return executeSqlDelete( - sqlAstDelete, - jdbcParameterBindings, - executionContext - ).thenApply( rows -> { - LOG.debugf( "deleteFromNonRootTable - `%s` : %s rows", targetTableReference, rows ); - return rows; - } ); - } - - private static CompletionStage executeSqlDelete( - DeleteStatement sqlAst, - JdbcParameterBindings jdbcParameterBindings, - ExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - - final JdbcServices jdbcServices = factory.getJdbcServices(); - - final JdbcOperationQueryMutation jdbcDelete = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, sqlAst ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcDelete, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContext - ); - } - - private CompletionStage executeWithIdTable( - Predicate predicate, - Map, List>> restrictionSqmParameterResolutions, - Map, MappingModelExpressible> paramTypeResolutions, - ExecutionContext executionContext) { - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( - getDomainParameterXref(), - () -> restrictionSqmParameterResolutions - ), - new SqmParameterMappingModelResolutionAccess() { - @Override @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) paramTypeResolutions.get(parameter); - } - }, - executionContext.getSession() - ); - - return ReactiveExecuteWithTemporaryTableHelper - .performBeforeTemporaryTableUseActions( getIdTable(), getTemporaryTableStrategy(), executionContext ) - .thenCompose( v -> executeUsingIdTable( predicate, executionContext, jdbcParameterBindings ) - .handle( CompletionStages::handle ) - .thenCompose( resultHandler -> ReactiveExecuteWithTemporaryTableHelper - .performAfterTemporaryTableUseActions( - getIdTable(), - getSessionUidAccess(), - getAfterUseAction(), - executionContext - ) - .thenCompose( resultHandler::getResultAsCompletionStage ) - ) - ); - } - - private CompletionStage executeUsingIdTable( - Predicate predicate, - ExecutionContext executionContext, - JdbcParameterBindings jdbcParameterBindings) { - return ReactiveExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable( - getConverter(), - predicate, - getIdTable(), - getSessionUidAccess(), - jdbcParameterBindings, - executionContext ) - .thenCompose( rows -> { - final QuerySpec idTableIdentifierSubQuery = createIdTableSelectQuerySpec( - getIdTable(), - getSessionUidAccess(), - getEntityDescriptor(), - executionContext - ); - - return ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - getEntityDescriptor(), - (tableReference, attributeMapping) -> { - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idTableFkSubQuery = fkDescriptor.getTargetPart().isEntityIdentifierMapping() - ? idTableIdentifierSubQuery - : createIdTableSelectQuerySpec( getIdTable(), fkDescriptor.getTargetPart(), getSessionUidAccess(), getEntityDescriptor(), executionContext ); - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - getSessionFactory() - ), - idTableFkSubQuery, - false - ); - - }, - JdbcParameterBindings.NO_BINDINGS, - executionContext - ).thenCompose( unused -> visitConstraintOrderedTables( idTableIdentifierSubQuery, executionContext ) - .thenApply( v -> rows ) ); - } ); - } - - private CompletionStage visitConstraintOrderedTables( - QuerySpec idTableIdentifierSubQuery, - ExecutionContext executionContext) { - final Completable completable = new Completable<>(); - getEntityDescriptor() - .visitConstraintOrderedTables( (tableExpression, tableKeyColumnVisitationSupplier) -> deleteFromTableUsingIdTable( - tableExpression, - tableKeyColumnVisitationSupplier, - idTableIdentifierSubQuery, - executionContext - ) - .handle( completable::complete ) - ); - return completable.getStage().thenCompose( CompletionStages::voidFuture ); - } - - private CompletionStage deleteFromTableUsingIdTable( - String tableExpression, - Supplier> tableKeyColumnVisitationSupplier, - QuerySpec idTableSubQuery, - ExecutionContext executionContext) { - LOG.tracef( "deleteFromTableUsingIdTable - %s", tableExpression ); - - final TableKeyExpressionCollector keyColumnCollector = new TableKeyExpressionCollector( getEntityDescriptor() ); - final NamedTableReference targetTable = new NamedTableReference( tableExpression, DeleteStatement.DEFAULT_ALIAS, true ); - - tableKeyColumnVisitationSupplier.get().accept( - (columnIndex, selection) -> { - assert selection.getContainingTableExpression().equals( tableExpression ); - assert !selection.isFormula(); - assert selection.getCustomReadExpression() == null; - assert selection.getCustomWriteExpression() == null; - - keyColumnCollector - .apply( new ColumnReference( targetTable, selection ) ); - } - ); - - final InSubQueryPredicate predicate = new InSubQueryPredicate( - keyColumnCollector.buildKeyExpression(), - idTableSubQuery, - false - ); - - return executeSqlDelete( - new DeleteStatement( targetTable, predicate ), - JdbcParameterBindings.NO_BINDINGS, - executionContext - ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveSoftDeleteExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveSoftDeleteExecutionDelegate.java deleted file mode 100644 index 607812f93..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveSoftDeleteExecutionDelegate.java +++ /dev/null @@ -1,320 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.temptable; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.LoadQueryInfluencers; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.MappingModelExpressible; -import org.hibernate.metamodel.mapping.TableDetails; -import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; -import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.internal.SqmUtil; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.AbstractDeleteExecutionDelegate; -import org.hibernate.query.sqm.mutation.internal.temptable.ColumnReferenceCheckingSqlAstWalker; -import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess; -import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; -import org.hibernate.query.sqm.tree.expression.SqmParameter; -import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveSqmMutationStrategyHelper; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.tree.delete.DeleteStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.SqlTuple; -import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.predicate.PredicateCollector; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.sql.results.internal.SqlSelectionImpl; - -import static java.util.Collections.singletonList; -import static org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter.omittingLockingAndPaging; -import static org.hibernate.query.sqm.mutation.internal.temptable.ExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; - -public class ReactiveSoftDeleteExecutionDelegate extends AbstractDeleteExecutionDelegate - implements ReactiveTableBasedDeleteHandler.ReactiveExecutionDelegate { - - public ReactiveSoftDeleteExecutionDelegate( - EntityMappingType entityDescriptor, TemporaryTable idTable, TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, - QueryOptions queryOptions, LoadQueryInfluencers loadQueryInfluencers, QueryParameterBindings queryParameterBindings, - Function sessionUidAccess, SessionFactoryImplementor sessionFactory - ) { - super( - entityDescriptor, idTable, temporaryTableStrategy, forceDropAfterUse, sqmDelete, domainParameterXref, queryOptions, loadQueryInfluencers, queryParameterBindings, sessionUidAccess, - sessionFactory - ); - } - - @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext domainQueryExecutionContext) { - final String targetEntityName = getSqmDelete().getTarget().getEntityName(); - final EntityPersister targetEntityDescriptor = getSessionFactory().getMappingMetamodel().getEntityDescriptor( targetEntityName ); - - final EntityMappingType rootEntityDescriptor = targetEntityDescriptor.getRootEntityDescriptor(); - - // determine if we need to use a sub-query for matching ids - - // 1. if the target is not the root we will - // 2. if the supplied predicate (if any) refers to columns from a table - // other than the identifier table we will - final SqmJdbcExecutionContextAdapter executionContext = omittingLockingAndPaging( domainQueryExecutionContext ); - - final TableGroup deletingTableGroup = getConverter().getMutatingTableGroup(); - final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails(); - final NamedTableReference rootTableReference = (NamedTableReference) deletingTableGroup.resolveTableReference( - deletingTableGroup.getNavigablePath(), - softDeleteTable.getTableName() - ); - assert rootTableReference != null; - - // NOTE : `converter.visitWhereClause` already applies the soft-delete restriction - final Predicate specifiedRestriction = getConverter().visitWhereClause( getSqmDelete().getWhereClause() ); - - final PredicateCollector predicateCollector = new PredicateCollector( specifiedRestriction ); - targetEntityDescriptor.applyBaseRestrictions( - predicateCollector, - deletingTableGroup, - true, - executionContext.getSession().getLoadQueryInfluencers().getEnabledFilters(), - false, - null, - getConverter() - ); - - getConverter().pruneTableGroupJoins(); - final ColumnReferenceCheckingSqlAstWalker walker = new ColumnReferenceCheckingSqlAstWalker( - rootTableReference.getIdentificationVariable() - ); - if ( predicateCollector.getPredicate() != null ) { - predicateCollector.getPredicate().accept( walker ); - } - - final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( - executionContext.getQueryParameterBindings(), - getDomainParameterXref(), - SqmUtil.generateJdbcParamsXref( getDomainParameterXref(), getConverter() ), - new SqmParameterMappingModelResolutionAccess() { - @Override - @SuppressWarnings("unchecked") - public MappingModelExpressible getResolvedMappingModelType(SqmParameter parameter) { - return (MappingModelExpressible) getConverter().getSqmParameterMappingModelExpressibleResolutions().get( parameter ); - } - }, - executionContext.getSession() - ); - - final boolean needsSubQuery = !walker.isAllColumnReferencesFromIdentificationVariable() - || targetEntityDescriptor != rootEntityDescriptor; - - if ( needsSubQuery ) { - return getSessionFactory().getJdbcServices().getDialect().supportsSubqueryOnMutatingTable() - ? performDeleteWithSubQuery( rootEntityDescriptor, deletingTableGroup, rootTableReference, predicateCollector, jdbcParameterBindings, getConverter(), executionContext ) - : performDeleteWithIdTable( rootEntityDescriptor, rootTableReference, predicateCollector, jdbcParameterBindings, executionContext ); - } - return performDirectDelete( rootEntityDescriptor, rootTableReference, predicateCollector, jdbcParameterBindings, executionContext ); - } - - private CompletionStage performDeleteWithIdTable( - EntityMappingType rootEntityDescriptor, - NamedTableReference targetTableReference, - PredicateCollector predicateCollector, - JdbcParameterBindings jdbcParameterBindings, - SqmJdbcExecutionContextAdapter executionContext - ) { - ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( getIdTable(), getTemporaryTableStrategy(), executionContext ); - try { - return deleteUsingIdTable( rootEntityDescriptor, targetTableReference, predicateCollector, jdbcParameterBindings, executionContext ); - } - finally { - ReactiveExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions( getIdTable(), getSessionUidAccess(), getAfterUseAction(), executionContext ); - } - } - - private CompletionStage deleteUsingIdTable( - EntityMappingType rootEntityDescriptor, - NamedTableReference targetTableReference, - PredicateCollector predicateCollector, - JdbcParameterBindings jdbcParameterBindings, - SqmJdbcExecutionContextAdapter executionContext - ) { - return ReactiveExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable( getConverter(), - predicateCollector.getPredicate(), - getIdTable(), - getSessionUidAccess(), - jdbcParameterBindings, - executionContext - ) - .thenCompose( rows -> { - final QuerySpec idTableIdentifierSubQuery = createIdTableSelectQuerySpec( - getIdTable(), - getSessionUidAccess(), - getEntityDescriptor(), - executionContext - ); - - return ReactiveSqmMutationStrategyHelper.cleanUpCollectionTables( - getEntityDescriptor(), - (tableReference, attributeMapping) -> { - final ForeignKeyDescriptor fkDescriptor = attributeMapping.getKeyDescriptor(); - final QuerySpec idTableFkSubQuery = fkDescriptor.getTargetPart().isEntityIdentifierMapping() - ? idTableIdentifierSubQuery - : createIdTableSelectQuerySpec( getIdTable(), fkDescriptor.getTargetPart(), getSessionUidAccess(), getEntityDescriptor(), executionContext ); - return new InSubQueryPredicate( - MappingModelCreationHelper.buildColumnReferenceExpression( - new MutatingTableReferenceGroupWrapper( - new NavigablePath( attributeMapping.getRootPathName() ), - attributeMapping, - (NamedTableReference) tableReference - ), - fkDescriptor, - null, - getSessionFactory() - ), - idTableFkSubQuery, - false - ); - - }, - JdbcParameterBindings.NO_BINDINGS, - executionContext - ).thenCompose( unused -> { - final Assignment softDeleteAssignment = rootEntityDescriptor - .getSoftDeleteMapping() - .createSoftDeleteAssignment( targetTableReference ); - final Expression idExpression = createIdExpression( rootEntityDescriptor, targetTableReference ); - final UpdateStatement updateStatement = new UpdateStatement( - targetTableReference, - singletonList( softDeleteAssignment ), - new InSubQueryPredicate( idExpression, idTableIdentifierSubQuery, false ) - ); - - return executeUpdate( updateStatement, jdbcParameterBindings, executionContext ); - } ) - .thenApply( v -> rows ); - } ); - } - - private static Expression createIdExpression(EntityMappingType rootEntityDescriptor, NamedTableReference targetTableReference) { - final TableDetails softDeleteTable = rootEntityDescriptor.getSoftDeleteTableDetails(); - final TableDetails.KeyDetails keyDetails = softDeleteTable.getKeyDetails(); - final List idExpressions = new ArrayList<>( keyDetails.getColumnCount() ); - keyDetails.forEachKeyColumn( (position, column) -> idExpressions.add( new ColumnReference( targetTableReference, column ) ) ); - return idExpressions.size() == 1 - ? idExpressions.get( 0 ) - : new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() ); - } - - private CompletionStage performDeleteWithSubQuery( - EntityMappingType rootEntityDescriptor, - TableGroup deletingTableGroup, - NamedTableReference rootTableReference, - PredicateCollector predicateCollector, - JdbcParameterBindings jdbcParameterBindings, - MultiTableSqmMutationConverter converter, - SqmJdbcExecutionContextAdapter executionContext - ) { - final QuerySpec matchingIdSubQuery = new QuerySpec( false, 1 ); - matchingIdSubQuery.getFromClause().addRoot( deletingTableGroup ); - - final TableDetails identifierTableDetails = rootEntityDescriptor.getIdentifierTableDetails(); - final TableDetails.KeyDetails keyDetails = identifierTableDetails.getKeyDetails(); - - final NamedTableReference targetTable = new NamedTableReference( - identifierTableDetails.getTableName(), - DeleteStatement.DEFAULT_ALIAS, - false - ); - - final List idExpressions = new ArrayList<>( keyDetails.getColumnCount() ); - keyDetails.forEachKeyColumn( (position, column) -> { - final Expression columnReference = converter.getSqlExpressionResolver().resolveSqlExpression( - rootTableReference, - column - ); - matchingIdSubQuery.getSelectClause().addSqlSelection( - new SqlSelectionImpl( position, columnReference ) - ); - idExpressions.add( new ColumnReference( targetTable, column ) ); - } ); - - matchingIdSubQuery.applyPredicate( predicateCollector.getPredicate() ); - final Expression idExpression = idExpressions.size() == 1 - ? idExpressions.get( 0 ) - : new SqlTuple( idExpressions, rootEntityDescriptor.getIdentifierMapping() ); - - final Assignment softDeleteAssignment = rootEntityDescriptor - .getSoftDeleteMapping() - .createSoftDeleteAssignment( targetTable ); - - final UpdateStatement updateStatement = new UpdateStatement( - targetTable, - singletonList( softDeleteAssignment ), - new InSubQueryPredicate( idExpression, matchingIdSubQuery, false ) - ); - - return executeUpdate( updateStatement, jdbcParameterBindings, executionContext ); - } - - private CompletionStage performDirectDelete( - EntityMappingType rootEntityDescriptor, - NamedTableReference rootTableReference, - PredicateCollector predicateCollector, - JdbcParameterBindings jdbcParameterBindings, - SqmJdbcExecutionContextAdapter executionContext - ) { - - final Assignment softDeleteAssignment = rootEntityDescriptor.getSoftDeleteMapping().createSoftDeleteAssignment( rootTableReference ); - final UpdateStatement updateStatement = new UpdateStatement( rootTableReference, singletonList( softDeleteAssignment ), predicateCollector.getPredicate() ); - return executeUpdate( updateStatement, jdbcParameterBindings, executionContext ); - } - - private CompletionStage executeUpdate(UpdateStatement updateStatement, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { - final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); - final JdbcServices jdbcServices = factory.getJdbcServices(); - - final JdbcOperationQueryMutation jdbcUpdate = jdbcServices.getJdbcEnvironment() - .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, updateStatement ) - .translate( jdbcParameterBindings, executionContext.getQueryOptions() ); - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcUpdate, - jdbcParameterBindings, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareStatement( sql ), - (integer, preparedStatement) -> {}, - executionContext - ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java index d916e4910..c27fbf502 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java @@ -6,34 +6,38 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.lang.invoke.MethodHandles; +import java.util.UUID; import java.util.concurrent.CompletionStage; import java.util.function.Function; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.persister.entity.UnionSubclassEntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedDeleteHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; public class ReactiveTableBasedDeleteHandler extends TableBasedDeleteHandler implements ReactiveAbstractMutationHandler { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public interface ReactiveExecutionDelegate extends TableBasedDeleteHandler.ExecutionDelegate { - - @Override - default int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext); - } - public ReactiveTableBasedDeleteHandler( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, @@ -41,7 +45,8 @@ public ReactiveTableBasedDeleteHandler( TemporaryTableStrategy temporaryTableStrategy, boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { super( sqmDeleteStatement, domainParameterXref, @@ -49,46 +54,99 @@ public ReactiveTableBasedDeleteHandler( temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, - sessionFactory + context, + firstJdbcParameterBindingsConsumer ); } @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { + public CompletionStage reactiveExecute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { if ( LOG.isTraceEnabled() ) { - LOG.tracef( "Starting multi-table delete execution - %s", getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() ); + LOG.tracef( + "Starting multi-table delete execution - %s", + getSqmStatement().getTarget().getModel().getName() + ); + } + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + final CacheableSqmInterpretation idTableInsert = getIdTableInsert(); + final TemporaryTable idTable = getIdTable(); + final StandardReactiveJdbcMutationExecutor jdbcMutationExecutor = StandardReactiveJdbcMutationExecutor.INSTANCE; + if ( idTableInsert != null ) { + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + idTable, + getTemporaryTableStrategy(), + executionContext + ).thenCompose( unused -> + ReactiveExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( idTableInsert.jdbcOperation(), jdbcParameterBindings, executionContext ) + .thenCompose( rows -> executeDelete( jdbcParameterBindings, rows, executionContext, jdbcMutationExecutor ) ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( idTable, getSessionUidAccess(), getAfterUseAction(), executionContext ) + .thenCompose( v -> handler.getResultAsCompletionStage() ) ) + ); + } + else { + return loop(getCollectionTableDeletes() ,delete -> + reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + ).thenCompose( v -> { + final int[] rows = { 0 }; + return deleteRows( jdbcParameterBindings, jdbcMutationExecutor, executionContext, rows ) + .thenApply( vv -> rows[0] ); + } ); } - return resolveDelegate( executionContext ).reactiveExecute( executionContext ); } - protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - if ( getEntityDescriptor().getSoftDeleteMapping() != null ) { - return new ReactiveSoftDeleteExecutionDelegate( - getEntityDescriptor(), - getIdTable(), - getTemporaryTableStrategy(), - isForceDropAfterUse(), - getSqmDeleteOrUpdateStatement(), - getDomainParameterXref(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - getSessionUidAccess(), - getSessionFactory() + private CompletionStage deleteRows(JdbcParameterBindings jdbcParameterBindings, StandardReactiveJdbcMutationExecutor jdbcMutationExecutor, SqmJdbcExecutionContextAdapter executionContext, int[] rows) { + if ( getEntityDescriptor() instanceof UnionSubclassEntityPersister ) { + return CompletionStages + .loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + .thenApply( tot -> rows[0] += tot ) + ); + } + else { + return CompletionStages + .loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + .thenApply( tot -> rows[0] = tot ) + ); + } + } + + private CompletionStage executeDelete( + JdbcParameterBindings jdbcParameterBindings, + Integer rows, + SqmJdbcExecutionContextAdapter executionContext, + StandardReactiveJdbcMutationExecutor jdbcMutationExecutor) { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( executionContext.getSession() ) ) + ) ); } - return new ReactiveRestrictedDeleteExecutionDelegate( - getEntityDescriptor(), - getIdTable(), - getTemporaryTableStrategy(), - isForceDropAfterUse(), - getSqmDeleteOrUpdateStatement(), - getDomainParameterXref(), - executionContext.getQueryOptions(), - executionContext.getSession().getLoadQueryInfluencers(), - executionContext.getQueryParameterBindings(), - getSessionUidAccess(), - getSessionFactory() + return loop( getCollectionTableDeletes(), delete -> + reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + ).thenApply( v -> rows ); + } + + private static CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + JdbcOperationQueryMutation delete, + StandardReactiveJdbcMutationExecutor jdbcMutationExecutor, + SqmJdbcExecutionContextAdapter executionContext) { + return jdbcMutationExecutor.executeReactive( + delete, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java index eb10dc8a4..194b8b598 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedInsertHandler.java @@ -5,46 +5,57 @@ */ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; - import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.Generator; +import org.hibernate.generator.values.GeneratedValuesMutationDelegate; +import org.hibernate.id.insert.Binder; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.metamodel.mapping.BasicEntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedInsertHandler; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; +import org.hibernate.reactive.id.insert.ReactiveInsertGeneratedIdentifierDelegate; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; -import org.hibernate.reactive.query.sqm.mutation.internal.cte.ReactiveInsertExecutionDelegate; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; +import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.expression.JdbcParameter; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.insert.ConflictClause; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBinder; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.type.descriptor.ValueBinder; -public class ReactiveTableBasedInsertHandler extends TableBasedInsertHandler implements ReactiveHandler { +import java.lang.invoke.MethodHandles; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; +import java.util.stream.IntStream; - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); +import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; - public interface ReactiveExecutionDelegate extends ExecutionDelegate { - @Override - default int execute(ExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } +public class ReactiveTableBasedInsertHandler extends TableBasedInsertHandler implements ReactiveHandler { - CompletionStage reactiveExecute(ExecutionContext executionContext); - } + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); public ReactiveTableBasedInsertHandler( SqmInsertStatement sqmInsert, @@ -53,62 +64,339 @@ public ReactiveTableBasedInsertHandler( TemporaryTableStrategy temporaryTableStrategy, boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmInsert, domainParameterXref, entityTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmInsert, domainParameterXref, entityTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, context, firstJdbcParameterBindingsConsumer ); } @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { + public CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext context) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Starting multi-table insert execution - %s", - getSqmInsertStatement().getTarget().getModel().getName() + getSqmStatement().getTarget().getModel().getName() ); } - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter - .omittingLockingAndPaging( executionContext ); - return resolveDelegate( executionContext ) - .reactiveExecute( executionContextAdapter ); + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + // NOTE: we could get rid of using a temporary table if the expressions in Values are "stable". + // But that is a non-trivial optimization that requires more effort + // as we need to split out individual inserts if we have a non-bulk capable optimizer + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + getEntityTable(), + getTemporaryTableStrategy(), + executionContext + ).thenCompose( createdTable -> + ReactiveExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + getTemporaryTableInsert().jdbcOperation(), + jdbcParameterBindings, + executionContext + ).thenCompose( rows -> { + if ( rows != 0 ) { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( executionContext.getSession() ) ) + ) + ); + } + return insertRootTable( rows, createdTable, sessionUidBindings, executionContext ) + .thenCompose( insertedRows -> CompletionStages + .loop( + getNonRootTableInserts(), nonRootTableInsert -> + insertTable( nonRootTableInsert, sessionUidBindings, executionContext ) + ).thenApply( v -> insertedRows ) + ); + } + return CompletionStages.completedFuture( rows ); + } ) ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( + getEntityTable(), + getSessionUidAccess(), + getAfterUseAction(), + executionContext + ) + .thenCompose( v -> handler.getResultAsCompletionStage() ) + ); } - @Override - protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - return (ReactiveExecutionDelegate) super.resolveDelegate( executionContext ); + private CompletionStage insertTable( + JdbcOperationQueryMutation nonRootTableInsert, + JdbcParameterBindings sessionUidBindings, + ExecutionContext executionContext) { + return StandardReactiveJdbcMutationExecutor.INSTANCE + .executeReactive( + nonRootTableInsert, + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ).thenCompose( unused -> CompletionStages.voidFuture() ); } - @Override - protected ExecutionDelegate buildExecutionDelegate( - SqmInsertStatement sqmInsert, - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable entityTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup insertingTableGroup, - Map tableReferenceByAlias, - List assignments, - boolean assignsId, - InsertSelectStatement insertStatement, - ConflictClause conflictClause, - JdbcParameter sessionUidParameter, - DomainQueryExecutionContext executionContext) { - return new ReactiveInsertExecutionDelegate( - sqmConverter, - entityTable, - temporaryTableStrategy, - forceDropAfterUse, - sessionUidAccess, - domainParameterXref, - insertingTableGroup, - tableReferenceByAlias, - assignments, - assignsId, - insertStatement, - conflictClause, - sessionUidParameter, - executionContext - ); + private CompletionStage insertRootTable( + int rows, + boolean rowNumberStartsAtOne, + JdbcParameterBindings sessionUidBindings, + SqmJdbcExecutionContextAdapter executionContext) { + final EntityPersister entityPersister = getEntityDescriptor().getEntityPersister(); + final Generator generator = entityPersister.getGenerator(); + final EntityIdentifierMapping identifierMapping = entityPersister.getIdentifierMapping(); + + final SharedSessionContractImplementor session = executionContext.getSession(); + final RootTableInserter rootTableInserter = getRootTableInserter(); + + if ( rootTableInserter.temporaryTableIdentitySelect() != null ) { + return StandardReactiveSelectExecutor.INSTANCE.list( + rootTableInserter.temporaryTableIdentitySelect(), + sessionUidBindings, + executionContext, + null, + null, + ReactiveListResultsConsumer.UniqueSemantic.NONE, + rows + ).thenApply( list -> { + Map entityTableToRootIdentity = new LinkedHashMap<>( list.size() ); + for ( Object o : list ) { + entityTableToRootIdentity.put( o, null ); + } + return entityTableToRootIdentity; + } ).thenCompose( entityTableToRootIdentity -> insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session + ) ); + } + else { + final Map entityTableToRootIdentity = null; + + if ( rootTableInserter.temporaryTableIdUpdate() != null ) { + final BeforeExecutionGenerator beforeExecutionGenerator = (BeforeExecutionGenerator) generator; + final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 3 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + updateBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( session ) ) + ) + ); + } + final List parameterBinders = rootTableInserter.temporaryTableIdUpdate().getParameterBinders(); + final JdbcParameter rootIdentity = (JdbcParameter) parameterBinders.get( 0 ); + final JdbcParameter rowNumber = (JdbcParameter) parameterBinders.get( 1 ); + final BasicEntityIdentifierMapping basicIdentifierMapping = (BasicEntityIdentifierMapping) identifierMapping; + + if ( !rowNumberStartsAtOne ) { + return ReactiveExecuteWithTemporaryTableHelper.loadInsertedRowNumbers( + rootTableInserter.temporaryTableRowNumberSelectSql(), + getEntityTable(), + getSessionUidAccess(), + rows, + executionContext + ).thenCompose( rowNumbers -> + forEachRow( + rowNumbers, + executionContext, + updateBindings, + rowNumber, + rootIdentity, + basicIdentifierMapping, + beforeExecutionGenerator, + session, + rootTableInserter + ).thenCompose( v -> insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session ) + ) + ); + } + else { + final Integer[] rowNumbers = IntStream.range( 1, rows + 1 ).boxed() + .toArray(Integer[]::new); + return forEachRow( + rowNumbers, + executionContext, + updateBindings, + rowNumber, + rootIdentity, + basicIdentifierMapping, + beforeExecutionGenerator, + session, + rootTableInserter + ).thenCompose( v -> insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session) + ); + } + } + return insertRootTable( + sessionUidBindings, + executionContext, + rootTableInserter, + entityPersister, + identifierMapping, + entityTableToRootIdentity, + session + ); + } } + + private static CompletionStage forEachRow( + Integer[] rowNumbers, + SqmJdbcExecutionContextAdapter executionContext, + JdbcParameterBindings updateBindings, + JdbcParameter rowNumber, + JdbcParameter rootIdentity, + BasicEntityIdentifierMapping basicIdentifierMapping, + BeforeExecutionGenerator beforeExecutionGenerator, + SharedSessionContractImplementor session, + RootTableInserter rootTableInserter) { + return loop( rowNumbers, rowNumberValue -> { + updateBindings.addBinding( + rowNumber, + new JdbcParameterBindingImpl( + rowNumber.getExpressionType().getSingleJdbcMapping(), + rowNumberValue + ) + ); + updateBindings.addBinding( + rootIdentity, + new JdbcParameterBindingImpl( + basicIdentifierMapping.getJdbcMapping(), + beforeExecutionGenerator.generate( session, null, null, INSERT ) + ) + ); + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + rootTableInserter.temporaryTableIdUpdate(), + updateBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ).thenApply( updateCount -> { + assert updateCount == 1; + return updateCount; + } ); + } ); + } + + private CompletionStage insertRootTable( + JdbcParameterBindings sessionUidBindings, + SqmJdbcExecutionContextAdapter executionContext, + RootTableInserter rootTableInserter, + EntityPersister entityPersister, + EntityIdentifierMapping identifierMapping, + Map entityTableToRootIdentity, + SharedSessionContractImplementor session ) { + if ( rootTableInserter.rootTableInsertWithReturningSql() != null ) { + final GeneratedValuesMutationDelegate insertDelegate = entityPersister.getEntityPersister().getInsertDelegate(); + final BasicEntityIdentifierMapping basicIdentifierMapping = (BasicEntityIdentifierMapping) identifierMapping; + // todo 7.0 : InsertGeneratedIdentifierDelegate will be removed once we're going to handle + // generated values within the jdbc insert operaetion itself + final ReactiveInsertGeneratedIdentifierDelegate identifierDelegate = (ReactiveInsertGeneratedIdentifierDelegate) insertDelegate; + final ValueBinder jdbcValueBinder = basicIdentifierMapping.getJdbcMapping().getJdbcValueBinder(); + return loop(entityTableToRootIdentity.entrySet() , entry -> + identifierDelegate.reactivePerformInsertReturning( + rootTableInserter.rootTableInsertWithReturningSql(), + session, + new Binder() { + @Override + public void bindValues(PreparedStatement ps) throws SQLException { + jdbcValueBinder.bind( ps, entry.getKey(), 1, session ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidParameter.getParameterBinder().bindParameterValue( + ps, + 2, + sessionUidBindings, + executionContext + ); + } + } + + @Override + public Object getEntity() { + return null; + } + } + ).thenAccept( generatedValues -> { + entry.setValue( generatedValues.getGeneratedValue( identifierMapping ) ); + } ) + ).thenCompose( unused -> { + final JdbcParameterBindings updateBindings = new JdbcParameterBindingsImpl( 2 ); + + final List parameterBinders = rootTableInserter.temporaryTableIdentityUpdate() + .getParameterBinders(); + final JdbcParameter rootIdentity = (JdbcParameter) parameterBinders.get( 0 ); + final JdbcParameter entityIdentity = (JdbcParameter) parameterBinders.get( 1 ); + return loop(entityTableToRootIdentity.entrySet(), entry -> { + JdbcMapping jdbcMapping = basicIdentifierMapping.getJdbcMapping(); + updateBindings.addBinding( + entityIdentity, + new JdbcParameterBindingImpl( jdbcMapping, entry.getKey() ) + ); + updateBindings.addBinding( + rootIdentity, + new JdbcParameterBindingImpl( jdbcMapping, entry.getValue() ) + ); + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + rootTableInserter.temporaryTableIdentityUpdate(), + updateBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + }).thenApply( v -> entityTableToRootIdentity.size() ); + }); + } + else { + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + rootTableInserter.rootTableInsert(), + sessionUidBindings, + sql -> session + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, + executionContext + ); + } + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java new file mode 100644 index 000000000..e0edb82e2 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedSoftDeleteHandler.java @@ -0,0 +1,129 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.query.sqm.mutation.internal.temptable; + +import org.hibernate.dialect.temptable.TemporaryTable; +import org.hibernate.dialect.temptable.TemporaryTableStrategy; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; +import org.hibernate.query.spi.DomainQueryExecutionContext; +import org.hibernate.query.sqm.internal.CacheableSqmInterpretation; +import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; +import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedSoftDeleteHandler; +import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import java.lang.invoke.MethodHandles; +import java.util.UUID; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +public class ReactiveTableBasedSoftDeleteHandler extends TableBasedSoftDeleteHandler implements ReactiveHandler { + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + public ReactiveTableBasedSoftDeleteHandler( + SqmDeleteStatement sqmDelete, + DomainParameterXref domainParameterXref, + TemporaryTable idTable, + TemporaryTableStrategy temporaryTableStrategy, + boolean forceDropAfterUse, + Function sessionUidAccess, + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( + sqmDelete, + domainParameterXref, + idTable, + temporaryTableStrategy, + forceDropAfterUse, + sessionUidAccess, + context, + firstJdbcParameterBindingsConsumer + ); + } + + @Override + public CompletionStage reactiveExecute( + JdbcParameterBindings jdbcParameterBindings, + DomainQueryExecutionContext context) { + if ( LOG.isTraceEnabled() ) { + LOG.tracef( + "Starting multi-table delete execution - %s", + getSqmStatement().getTarget().getModel().getName() + ); + } + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + StandardReactiveJdbcMutationExecutor jdbcMutationExecutor = StandardReactiveJdbcMutationExecutor.INSTANCE; + + final CacheableSqmInterpretation idTableInsert = getIdTableInsert(); + if ( idTableInsert != null ) { + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + getIdTable(), + getTemporaryTableStrategy(), + executionContext + ).thenCompose( unused -> + ReactiveExecuteWithTemporaryTableHelper.saveIntoTemporaryTable( + idTableInsert.jdbcOperation(), + jdbcParameterBindings, + executionContext + ).thenCompose( rows -> { + final JdbcParameterBindings sessionUidBindings = new JdbcParameterBindingsImpl( 1 ); + final JdbcParameter sessionUidParameter = getSessionUidParameter(); + if ( sessionUidParameter != null ) { + sessionUidBindings.addBinding( + sessionUidParameter, + new JdbcParameterBindingImpl( + sessionUidParameter.getExpressionType().getSingleJdbcMapping(), + UUID.fromString( getSessionUidAccess().apply( executionContext.getSession() ) ) + ) + ); + } + return jdbcMutationExecutor.executeReactive( + getSoftDelete(), + sessionUidBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ).thenApply( u -> rows ); + } ) + .handle( CompletionStages::handle) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( + getIdTable(), + getSessionUidAccess(), + getAfterUseAction(), + executionContext) + .thenCompose( v -> handler.getResultAsCompletionStage() ) ) + ); + } + else { + return jdbcMutationExecutor.executeReactive( + getSoftDelete(), + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> {}, + executionContext + ); + } + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java index 05a6d1511..d3ba6f6f3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedUpdateHandler.java @@ -6,43 +6,35 @@ package org.hibernate.reactive.query.sqm.mutation.internal.temptable; import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.function.Function; +import org.hibernate.AssertionFailure; import org.hibernate.dialect.temptable.TemporaryTable; import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.MutableObject; import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; import org.hibernate.query.sqm.mutation.internal.temptable.TableBasedUpdateHandler; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; public class ReactiveTableBasedUpdateHandler extends TableBasedUpdateHandler implements ReactiveAbstractMutationHandler { private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - public interface ReactiveExecutionDelegate extends TableBasedUpdateHandler.ExecutionDelegate { - @Override - default int execute(ExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - CompletionStage reactiveExecute(ExecutionContext executionContext); - } - public ReactiveTableBasedUpdateHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, @@ -50,58 +42,81 @@ public ReactiveTableBasedUpdateHandler( TemporaryTableStrategy temporaryTableStrategy, boolean forceDropAfterUse, Function sessionUidAccess, - SessionFactoryImplementor sessionFactory) { - super( sqmUpdate, domainParameterXref, idTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, sessionFactory ); + DomainQueryExecutionContext context, + MutableObject firstJdbcParameterBindingsConsumer) { + super( sqmUpdate, domainParameterXref, idTable, temporaryTableStrategy, forceDropAfterUse, sessionUidAccess, context, firstJdbcParameterBindingsConsumer ); } @Override - public int execute(DomainQueryExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - @Override - public CompletionStage reactiveExecute(DomainQueryExecutionContext executionContext) { + public CompletionStage reactiveExecute(JdbcParameterBindings jdbcParameterBindings, DomainQueryExecutionContext context) { if ( LOG.isTraceEnabled() ) { LOG.tracef( "Starting multi-table update execution - %s", - getSqmDeleteOrUpdateStatement().getRoot().getModel().getName() + getSqmStatement().getTarget().getModel().getName() ); } - final SqmJdbcExecutionContextAdapter executionContextAdapter = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( executionContext ); - return resolveDelegate( executionContext ).reactiveExecute( executionContextAdapter ); + final SqmJdbcExecutionContextAdapter executionContext = SqmJdbcExecutionContextAdapter.omittingLockingAndPaging( context ); + return ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions( + getIdTable(), + getTemporaryTableStrategy(), + executionContext + ).thenCompose( unused -> ReactiveExecuteWithTemporaryTableHelper + .saveIntoTemporaryTable( + getMatchingIdsIntoIdTableInsert().jdbcOperation(), + jdbcParameterBindings, + executionContext + ).thenCompose( rows -> loop(getTableUpdaters(), tableUpdater -> + updateTable( tableUpdater, rows, jdbcParameterBindings, executionContext ) ) + .thenApply(v -> rows)) + .handle( CompletionStages::handle) + .thenCompose( handler -> ReactiveExecuteWithTemporaryTableHelper + .performAfterTemporaryTableUseActions( getIdTable(), getSessionUidAccess(), getAfterUseAction(), executionContext ) + .thenCompose( v -> handler.getResultAsCompletionStage() )) + ); } - @Override - protected ReactiveExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionContext) { - return (ReactiveExecutionDelegate) super.resolveDelegate( executionContext ); + private CompletionStage updateTable( + TableUpdater tableUpdater, + int expectedUpdateCount, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext) { + if ( tableUpdater == null ) { + // no assignments for this table - skip it + return voidFuture(); + } + return executeMutation( tableUpdater.jdbcUpdate(), jdbcParameterBindings, executionContext ) + .thenCompose( updateCount -> { + // We are done when the update count matches + if ( updateCount == expectedUpdateCount ) { + return voidFuture(); + } + + // If the table is optional, execute an insert + if ( tableUpdater.jdbcInsert() != null ) { + return executeMutation( tableUpdater.jdbcInsert(), jdbcParameterBindings, executionContext ) + .thenAccept( insertCount -> { + if(insertCount + updateCount != expectedUpdateCount){ + throw new AssertionFailure( "insertCount + updateCount != expectedUpdateCount"); + } + } ); + } + return voidFuture(); + } ); } - @Override - protected ReactiveUpdateExecutionDelegate buildExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable idTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup updatingTableGroup, - Map tableReferenceByAlias, - List assignments, - Predicate suppliedPredicate, - DomainQueryExecutionContext executionContext) { - return new ReactiveUpdateExecutionDelegate( - sqmConverter, - idTable, - temporaryTableStrategy, - forceDropAfterUse, - sessionUidAccess, - domainParameterXref, - updatingTableGroup, - tableReferenceByAlias, - assignments, - suppliedPredicate, + private CompletionStage executeMutation(JdbcOperationQueryMutation jdbcUpdate, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext) { + return StandardReactiveJdbcMutationExecutor.INSTANCE.executeReactive( + jdbcUpdate, + jdbcParameterBindings, + sql -> executionContext.getSession() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( sql ), + (integer, preparedStatement) -> { + }, executionContext ); } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java index 2b3eda5f5..9dde81ab7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTemporaryTableHelper.java @@ -44,7 +44,6 @@ public interface ReactiveWork { public static class TemporaryTableCreationWork implements ReactiveWork { private final TemporaryTable temporaryTable; private final TemporaryTableExporter exporter; - private final SessionFactoryImplementor sessionFactory; public TemporaryTableCreationWork( TemporaryTable temporaryTable, @@ -62,7 +61,6 @@ public TemporaryTableCreationWork( SessionFactoryImplementor sessionFactory) { this.temporaryTable = temporaryTable; this.exporter = exporter; - this.sessionFactory = sessionFactory; } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java deleted file mode 100644 index 454864b80..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveUpdateExecutionDelegate.java +++ /dev/null @@ -1,295 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.query.sqm.mutation.internal.temptable; - -import java.lang.invoke.MethodHandles; -import java.sql.PreparedStatement; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -import org.hibernate.dialect.temptable.TemporaryTable; -import org.hibernate.dialect.temptable.TemporaryTableStrategy; -import org.hibernate.engine.jdbc.spi.JdbcServices; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.SelectableConsumer; -import org.hibernate.query.spi.DomainQueryExecutionContext; -import org.hibernate.query.sqm.internal.DomainParameterXref; -import org.hibernate.query.sqm.mutation.internal.MultiTableSqmMutationConverter; -import org.hibernate.query.sqm.mutation.internal.temptable.UpdateExecutionDelegate; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; -import org.hibernate.reactive.util.impl.CompletionStages; -import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.ast.tree.expression.ColumnReference; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.SqlTuple; -import org.hibernate.sql.ast.tree.from.NamedTableReference; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.ast.tree.from.TableReference; -import org.hibernate.sql.ast.tree.insert.InsertSelectStatement; -import org.hibernate.sql.ast.tree.predicate.ExistsPredicate; -import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; -import org.hibernate.sql.ast.tree.predicate.Predicate; -import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.ast.tree.update.Assignment; -import org.hibernate.sql.ast.tree.update.UpdateStatement; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.results.internal.SqlSelectionImpl; - -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.createIdTableSelectQuerySpec; -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.performAfterTemporaryTableUseActions; -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.performBeforeTemporaryTableUseActions; -import static org.hibernate.reactive.query.sqm.mutation.internal.temptable.ReactiveExecuteWithTemporaryTableHelper.saveMatchingIdsIntoIdTable; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -public class ReactiveUpdateExecutionDelegate extends UpdateExecutionDelegate implements ReactiveTableBasedUpdateHandler.ReactiveExecutionDelegate { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - public ReactiveUpdateExecutionDelegate( - MultiTableSqmMutationConverter sqmConverter, - TemporaryTable idTable, - TemporaryTableStrategy temporaryTableStrategy, - boolean forceDropAfterUse, - Function sessionUidAccess, - DomainParameterXref domainParameterXref, - TableGroup updatingTableGroup, - Map tableReferenceByAlias, - List assignments, - Predicate suppliedPredicate, - DomainQueryExecutionContext executionContext) { - super( - sqmConverter, - idTable, - temporaryTableStrategy, - forceDropAfterUse, - sessionUidAccess, - domainParameterXref, - updatingTableGroup, - tableReferenceByAlias, - assignments, - suppliedPredicate, - executionContext - ); - } - - private static void doNothing(Integer integer, PreparedStatement preparedStatement) { - } - - @Override - public int execute(ExecutionContext executionContext) { - throw LOG.nonReactiveMethodCall( "reactiveExecute" ); - } - - @Override - public CompletionStage reactiveExecute(ExecutionContext executionContext) { - return performBeforeTemporaryTableUseActions( - getIdTable(), - getTemporaryTableStrategy(), - executionContext - ) - .thenCompose( v -> saveMatchingIdsIntoIdTable( - getSqmConverter(), - getSuppliedPredicate(), - getIdTable(), - getSessionUidAccess(), - getJdbcParameterBindings(), - executionContext - ) ) - .thenCompose( rows -> { - final QuerySpec idTableSubQuery = createIdTableSelectQuerySpec( - getIdTable(), - getSessionUidAccess(), - getEntityDescriptor(), - executionContext - ); - - final CompletionStage[] resultStage = new CompletionStage[] { voidFuture() }; - getEntityDescriptor().visitConstraintOrderedTables( - (tableExpression, tableKeyColumnVisitationSupplier) -> resultStage[0] = resultStage[0].thenCompose( - v -> reactiveUpdateTable( - tableExpression, - tableKeyColumnVisitationSupplier, - rows, - idTableSubQuery, - executionContext - ) ) - ); - return resultStage[0].thenApply( v -> rows ); - }) - .handle( CompletionStages::handle ) - .thenCompose( handler -> performAfterTemporaryTableUseActions( - getIdTable(), - getSessionUidAccess(), - getAfterUseAction(), - executionContext - ) - .thenCompose( handler::getResultAsCompletionStage ) - ); - } - - private CompletionStage reactiveUpdateTable( - String tableExpression, - Supplier> tableKeyColumnVisitationSupplier, - int expectedUpdateCount, - QuerySpec idTableSubQuery, - ExecutionContext executionContext) { - - // update `updatingTableReference` - // set ... - // where `keyExpression` in ( `idTableSubQuery` ) - - final TableReference updatingTableReference = getUpdatingTableGroup().getTableReference( - getUpdatingTableGroup().getNavigablePath(), - tableExpression, - true - ); - - final List assignments = getAssignmentsByTable().get( updatingTableReference ); - if ( assignments == null || assignments.isEmpty() ) { - // no assignments for this table - skip it - return voidFuture(); - } - - final NamedTableReference dmlTableReference = resolveUnionTableReference( updatingTableReference, tableExpression ); - final JdbcServices jdbcServices = getSessionFactory().getJdbcServices(); - final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcServices.getJdbcEnvironment().getSqlAstTranslatorFactory(); - - final Expression keyExpression = resolveMutatingTableKeyExpression( tableExpression, tableKeyColumnVisitationSupplier ); - - return executeUpdate( idTableSubQuery, executionContext, assignments, dmlTableReference, sqlAstTranslatorFactory, keyExpression ) - .thenCompose( updateCount -> { - // We are done when the update count matches - if ( updateCount == expectedUpdateCount ) { - return voidFuture(); - } - // If the table is optional, execute an insert - if ( isTableOptional( tableExpression ) ) { - return executeInsert( - tableExpression, - dmlTableReference, - keyExpression, - tableKeyColumnVisitationSupplier, - idTableSubQuery, - assignments, - sqlAstTranslatorFactory, - executionContext - ) - .thenAccept( insertCount -> { - assert insertCount + updateCount == expectedUpdateCount; - } ); - } - return voidFuture(); - } ); - } - - - private CompletionStage executeUpdate(QuerySpec idTableSubQuery, ExecutionContext executionContext, List assignments, NamedTableReference dmlTableReference, SqlAstTranslatorFactory sqlAstTranslatorFactory, Expression keyExpression) { - final UpdateStatement sqlAst = new UpdateStatement( - dmlTableReference, - assignments, - new InSubQueryPredicate( keyExpression, idTableSubQuery, false ) - ); - - final JdbcOperationQueryMutation jdbcUpdate = sqlAstTranslatorFactory - .buildMutationTranslator( getSessionFactory(), sqlAst ) - .translate( getJdbcParameterBindings(), executionContext.getQueryOptions() ); - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcUpdate, - getJdbcParameterBindings(), - executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - ::prepareStatement, - ReactiveUpdateExecutionDelegate::doNothing, - executionContext - ); - } - - private CompletionStage executeInsert( - String targetTableExpression, - NamedTableReference targetTableReference, - Expression targetTableKeyExpression, - Supplier> tableKeyColumnVisitationSupplier, - QuerySpec idTableSubQuery, - List assignments, - SqlAstTranslatorFactory sqlAstTranslatorFactory, - ExecutionContext executionContext) { - - // Execute a query in the form - - // - // insert into (...) - // select ... - // from temptable_ - // where not exists ( - // select 1 - // from dml_ - // where dml_. = temptable_. - // ) - - // Create a new QuerySpec for the "insert source" select query. This - // is mostly a copy of the incoming `idTableSubQuery` along with the - // NOT-EXISTS predicate - final QuerySpec insertSourceSelectQuerySpec = makeInsertSourceSelectQuerySpec( idTableSubQuery ); - - // create the `select 1 ...` sub-query and apply the not-exists predicate - final QuerySpec existsSubQuerySpec = createExistsSubQuerySpec( targetTableExpression, tableKeyColumnVisitationSupplier, idTableSubQuery ); - insertSourceSelectQuerySpec.applyPredicate( - new ExistsPredicate( - existsSubQuerySpec, - true, - getSessionFactory().getTypeConfiguration().getBasicTypeForJavaType( Boolean.class ) - ) - ); - - // Collect the target column references from the key expressions - final List targetColumnReferences = new ArrayList<>(); - if ( targetTableKeyExpression instanceof SqlTuple ) { - //noinspection unchecked - targetColumnReferences.addAll( (Collection) ( (SqlTuple) targetTableKeyExpression ).getExpressions() ); - } - else { - targetColumnReferences.add( (ColumnReference) targetTableKeyExpression ); - } - - // And transform assignments to target column references and selections - for ( Assignment assignment : assignments ) { - targetColumnReferences.addAll( assignment.getAssignable().getColumnReferences() ); - insertSourceSelectQuerySpec.getSelectClause() - .addSqlSelection( new SqlSelectionImpl( assignment.getAssignedValue() ) ); - } - - final InsertSelectStatement insertSqlAst = new InsertSelectStatement( targetTableReference ); - insertSqlAst.addTargetColumnReferences( targetColumnReferences.toArray( new ColumnReference[0] ) ); - insertSqlAst.setSourceSelectStatement( insertSourceSelectQuerySpec ); - - final JdbcOperationQueryMutation jdbcInsert = sqlAstTranslatorFactory - .buildMutationTranslator( getSessionFactory(), insertSqlAst ) - .translate( getJdbcParameterBindings(), executionContext.getQueryOptions() ); - - return StandardReactiveJdbcMutationExecutor.INSTANCE - .executeReactive( - jdbcInsert, - getJdbcParameterBindings(), - executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - ::prepareStatement, - ReactiveUpdateExecutionDelegate::doNothing, - executionContext - ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java index a4a622537..f3a3b9a42 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveAbstractMutationHandler.java @@ -8,7 +8,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; /** @@ -16,8 +15,6 @@ */ public interface ReactiveAbstractMutationHandler extends ReactiveHandler { - SqmDeleteOrUpdateStatement getSqmDeleteOrUpdateStatement(); - EntityMappingType getEntityDescriptor(); SessionFactoryImplementor getSessionFactory(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java index dc52d893e..93399180d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableInsertStrategy.java @@ -10,10 +10,12 @@ import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableInsertStrategy; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; public interface ReactiveSqmMultiTableInsertStrategy extends SqmMultiTableInsertStrategy { @@ -27,8 +29,18 @@ default int executeInsert( throw LOG.nonReactiveMethodCall( "reactiveExecuteInsert" ); } - CompletionStage reactiveExecuteInsert( + /** + * Execute the multi-table insert indicated by the passed SqmInsertStatement + * + * @return The number of rows affected + * @deprecated Uses {@link #buildHandler(SqmInsertStatement, DomainParameterXref, DomainQueryExecutionContext)} instead + */ + @Deprecated(forRemoval = true, since = "3.1") + default CompletionStage reactiveExecuteInsert( SqmInsertStatement sqmInsertStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context){ + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmInsertStatement, domainParameterXref, context ); + return ((ReactiveHandler)buildResult.multiTableHandler()).reactiveExecute( buildResult.firstJdbcParameterBindings(), context ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java index 41b1c6224..b4a4bd077 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/spi/ReactiveSqmMultiTableMutationStrategy.java @@ -10,11 +10,14 @@ import org.hibernate.query.spi.DomainQueryExecutionContext; import org.hibernate.query.sqm.internal.DomainParameterXref; +import org.hibernate.query.sqm.mutation.spi.MultiTableHandlerBuildResult; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; +import org.hibernate.query.sqm.tree.SqmDeleteOrUpdateStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; +import org.hibernate.reactive.query.sqm.mutation.internal.ReactiveHandler; public interface ReactiveSqmMultiTableMutationStrategy extends SqmMultiTableMutationStrategy { @@ -28,10 +31,21 @@ default int executeUpdate( throw LOG.nonReactiveMethodCall( "reactiveExecuteUpdate" ); } - CompletionStage reactiveExecuteUpdate( + /** + * Execute the multi-table update indicated by the passed SqmUpdateStatement + * + * @return The number of rows affected + * @deprecated Use {@link #buildHandler(SqmDeleteOrUpdateStatement, DomainParameterXref, DomainQueryExecutionContext)} instead + */ + @Deprecated(forRemoval = true, since = "7.1") + default CompletionStage reactiveExecuteUpdate( SqmUpdateStatement sqmUpdateStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context){ + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmUpdateStatement, domainParameterXref, context ); + return ((ReactiveHandler)buildResult.multiTableHandler()).reactiveExecute( buildResult.firstJdbcParameterBindings(), context ); + + } @Override default int executeDelete( @@ -41,8 +55,18 @@ default int executeDelete( throw LOG.nonReactiveMethodCall( "reactiveExecuteDelete" ); } - CompletionStage reactiveExecuteDelete( + /** + * Execute the multi-table update indicated by the passed SqmUpdateStatement + * + * @return The number of rows affected + * @deprecated Use {@link #buildHandler(SqmDeleteOrUpdateStatement, DomainParameterXref, DomainQueryExecutionContext)} instead + */ + @Deprecated(forRemoval = true, since = "3.1") + default CompletionStage reactiveExecuteDelete( SqmDeleteStatement sqmDeleteStatement, DomainParameterXref domainParameterXref, - DomainQueryExecutionContext context); + DomainQueryExecutionContext context){ + final MultiTableHandlerBuildResult buildResult = buildHandler( sqmDeleteStatement, domainParameterXref, context ); + return ((ReactiveHandler)buildResult.multiTableHandler()).reactiveExecute( buildResult.firstJdbcParameterBindings(), context ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 795b565ff..7b0bd112a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -109,7 +109,7 @@ import org.hibernate.reactive.query.ReactiveSelectionQuery; import org.hibernate.reactive.query.sql.internal.ReactiveNativeQueryImpl; import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor; -import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; +import org.hibernate.reactive.query.sqm.internal.ReactiveSqmQueryImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.util.impl.CompletionStages; @@ -390,7 +390,7 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) } protected ReactiveQueryImplementor createReactiveCriteriaQuery(SqmStatement criteria, Class resultType) { - final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( criteria, resultType, this ); + final ReactiveSqmQueryImpl query = new ReactiveSqmQueryImpl<>( criteria, resultType, this ); applyQuerySettingsAndHints( query ); return query; } @@ -400,11 +400,11 @@ public ReactiveQuery createReactiveQuery(TypedQueryReference typedQuer checksBeforeQueryCreation(); if ( typedQueryReference instanceof SelectionSpecificationImpl specification ) { final CriteriaQuery query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, specification.getResultType(), this ); } else if ( typedQueryReference instanceof MutationSpecificationImpl specification ) { final CommonAbstractCriteria query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); } else { @SuppressWarnings("unchecked") @@ -433,8 +433,8 @@ public ReactiveQuery createReactiveQuery(String queryString, Class exp try { final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); - final ReactiveQuerySqmImpl query = - new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this ); + final ReactiveSqmQueryImpl query = + new ReactiveSqmQueryImpl<>( queryString, interpretation, expectedResultType, this ); applyQuerySettingsAndHints( query ); query.setComment( queryString ); return query; @@ -611,7 +611,7 @@ protected ReactiveNativeQueryImpl createReactiveNativeQueryImplementor(Cl return (ReactiveNativeQueryImpl) query; } - protected ReactiveQuerySqmImpl createReactiveSqmQueryImplementor(Class resultType, NamedSqmQueryMemento memento) { + protected ReactiveSqmQueryImpl createReactiveSqmQueryImplementor(Class resultType, NamedSqmQueryMemento memento) { final SqmQueryImplementor query = memento.toQuery( this, resultType ); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic query" ); @@ -620,7 +620,7 @@ protected ReactiveQuerySqmImpl createReactiveSqmQueryImplementor(Class if ( memento.getLockOptions() != null ) { query.setLockOptions( memento.getLockOptions() ); } - return (ReactiveQuerySqmImpl) query; + return (ReactiveSqmQueryImpl) query; } private RuntimeException convertNamedQueryException(RuntimeException e) { @@ -649,7 +649,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(String hqlString final QueryImplementor query = createQuery( hqlString ); final SqmStatement sqmStatement = ( (SqmQueryImplementor) query ).getSqmStatement(); checkMutationQuery( hqlString, sqmStatement ); - return new ReactiveQuerySqmImpl<>( sqmStatement, null, this ); + return new ReactiveSqmQueryImpl<>( sqmStatement, null, this ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index 63babefd0..51cf19747 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -68,7 +68,7 @@ import org.hibernate.reactive.query.ReactiveSelectionQuery; import org.hibernate.reactive.query.sql.internal.ReactiveNativeQueryImpl; import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor; -import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; +import org.hibernate.reactive.query.sqm.internal.ReactiveSqmQueryImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; import org.hibernate.reactive.session.ReactiveSqmQueryImplementor; import org.hibernate.reactive.session.ReactiveStatelessSession; @@ -951,11 +951,11 @@ public ReactiveQuery createReactiveQuery(TypedQueryReference typedQuer checksBeforeQueryCreation(); if ( typedQueryReference instanceof SelectionSpecificationImpl specification ) { final CriteriaQuery query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, specification.getResultType(), this ); } if ( typedQueryReference instanceof MutationSpecificationImpl specification ) { final CommonAbstractCriteria query = specification.buildCriteria( getCriteriaBuilder() ); - return new ReactiveQuerySqmImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); + return new ReactiveSqmQueryImpl<>( (SqmStatement) query, (Class) specification.getResultType(), this ); } @SuppressWarnings("unchecked") // this cast is fine because of all our impls of TypedQueryReference return Class @@ -1000,7 +1000,7 @@ public ReactiveQuery createReactiveQuery(CriteriaQuery criteriaQuery) } private ReactiveQuery createReactiveCriteriaQuery(SqmStatement criteria, Class resultType) { - final ReactiveQuerySqmImpl query = new ReactiveQuerySqmImpl<>( criteria, resultType, this ); + final ReactiveSqmQueryImpl query = new ReactiveSqmQueryImpl<>( criteria, resultType, this ); applyQuerySettingsAndHints( query ); return query; } @@ -1026,8 +1026,8 @@ public ReactiveSqmQueryImplementor createReactiveQuery(String queryString try { final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType ); - final ReactiveQuerySqmImpl query = - new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this ); + final ReactiveSqmQueryImpl query = + new ReactiveSqmQueryImpl<>( queryString, interpretation, expectedResultType, this ); applyQuerySettingsAndHints( query ); query.setComment( queryString ); @@ -1170,7 +1170,7 @@ public ReactiveMutationQuery createReactiveMutationQuery(String hqlString final QueryImplementor query = createQuery( hqlString ); final SqmStatement sqmStatement = ( (SqmQueryImplementor) query ).getSqmStatement(); checkMutationQuery( hqlString, sqmStatement ); - return new ReactiveQuerySqmImpl<>( sqmStatement, null, this ); + return new ReactiveSqmQueryImpl<>( sqmStatement, null, this ); } @Override From 0d1644d07cdfb7d3755cff799e5e97c3b1bebccf Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 6 Aug 2025 16:30:25 +0200 Subject: [PATCH 066/185] [#2404] Align code to ORM 7.1.0.CR2 --- .../impl/ReactiveAbstractEntityPersister.java | 138 +++++++++++------- .../ReactiveAbstractPersisterDelegate.java | 62 ++++++++ ...ReactiveJoinedSubclassEntityPersister.java | 25 +++- .../ReactiveSingleTableEntityPersister.java | 28 +++- .../ReactiveUnionSubclassEntityPersister.java | 28 +++- 5 files changed, 214 insertions(+), 67 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java index 129b9e236..1031f8b17 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractEntityPersister.java @@ -30,7 +30,6 @@ import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.collection.spi.PersistentCollection; -import org.hibernate.engine.internal.ManagedTypeHelper; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -69,11 +68,13 @@ import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.type.BasicType; +import org.hibernate.type.Type; import jakarta.persistence.metamodel.Attribute; import static java.lang.invoke.MethodHandles.lookup; import static java.util.Collections.emptyMap; +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.generator.EventType.INSERT; import static org.hibernate.generator.EventType.UPDATE; import static org.hibernate.internal.util.collections.CollectionHelper.setOfSize; @@ -105,6 +106,8 @@ */ public interface ReactiveAbstractEntityPersister extends ReactiveEntityPersister { + Log LOG = make( Log.class, lookup() ); + /** * A self-reference of type {@code AbstractEntityPersister}. * @@ -374,32 +377,51 @@ default CompletionStage reactiveInitializeLazyPropertiesFromDatastore( EntityEntry entry, String fieldName, SharedSessionContractImplementor session) { + return isNonLazyPropertyName( fieldName ) + ? initLazyProperty( entity, id, entry, fieldName, session ) + : initLazyProperties( entity, id, entry, fieldName, session ); + } - if ( !hasLazyProperties() ) { - throw new AssertionFailure( "no lazy properties" ); - } + boolean isNonLazyPropertyName(String fieldName); - final PersistentAttributeInterceptor interceptor = ManagedTypeHelper.asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor(); - if ( interceptor == null ) { - throw new AssertionFailure( "Expecting bytecode interceptor to be non-null" ); - } + private CompletionStage initLazyProperty( + Object entity, + Object id, + EntityEntry entry, + String fieldName, + SharedSessionContractImplementor session) { + // An eager property can be lazy because of an applied EntityGraph + final int propertyIndex = getPropertyIndex( fieldName ); + final List partsToSelect = List.of( getAttributeMapping( propertyIndex ) ); + return reactiveGetOrCreateLazyLoadPlan( fieldName, partsToSelect ) + .load( id, session ) + .thenApply( results -> { + final Object result = results[0]; + initializeLazyProperty( entity, entry, result, propertyIndex, getPropertyTypes()[propertyIndex] ); + return result; + } ); + } + + ReactiveSingleIdArrayLoadPlan reactiveGetOrCreateLazyLoadPlan(String fieldName, List partsToSelect); - make( Log.class, lookup() ).tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName ); + private CompletionStage initLazyProperties( + Object entity, + Object id, + EntityEntry entry, + String fieldName, + SharedSessionContractImplementor session) { - final String fetchGroup = getEntityPersister().getBytecodeEnhancementMetadata() - .getLazyAttributesMetadata() - .getFetchGroupName( fieldName ); - final List fetchGroupAttributeDescriptors = getEntityPersister().getBytecodeEnhancementMetadata() - .getLazyAttributesMetadata() - .getFetchGroupAttributeDescriptors( fetchGroup ); + assert hasLazyProperties(); + LOG.tracef( "Initializing lazy properties from datastore (triggered for '%s')", fieldName ); - @SuppressWarnings("deprecation") - Set initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames(); + final var interceptor = asPersistentAttributeInterceptable( entity ).$$_hibernate_getInterceptor(); + assert interceptor != null : "Expecting bytecode interceptor to be non-null"; + final Set initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames(); + LOG.tracef( "Initializing lazy properties from datastore (triggered for `%s`)", fieldName ); - // FIXME: How do I pass this to the query? - Object[] arguments = PreparedStatementAdaptor.bind( - statement -> getIdentifierType().nullSafeSet( statement, id, 1, session ) - ); + final var lazyAttributesMetadata = getBytecodeEnhancementMetadata().getLazyAttributesMetadata(); + final String fetchGroup = lazyAttributesMetadata.getFetchGroupName( fieldName ); + final var fetchGroupAttributeDescriptors = lazyAttributesMetadata.getFetchGroupAttributeDescriptors( fetchGroup ); return reactiveGetSQLLazySelectLoadPlan( fetchGroup ) .load( id, session ) @@ -420,51 +442,55 @@ default CompletionStage initLazyProperty( PersistentAttributeInterceptor interceptor, List fetchGroupAttributeDescriptors, Set initializedLazyAttributeNames, - Object[] values) { // Load all the lazy properties that are in the same fetch group - CompletionStage resultStage = nullFuture(); - int i = 0; - for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) { - if ( initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() ) ) { + Object[] results) { // Load all the lazy properties that are in the same fetch group + CompletionStage finalResultStage = nullFuture(); + final int[] i = { 0 }; + for ( var fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) { + final String attributeName = fetchGroupAttributeDescriptor.getName(); + final boolean previousInitialized = initializedLazyAttributeNames.contains( attributeName ); + final int index = i[0]++; + if ( previousInitialized ) { // Already initialized - if ( fetchGroupAttributeDescriptor.getName().equals( fieldName ) ) { - resultStage = completedFuture( entry.getLoadedValue( fetchGroupAttributeDescriptor.getName() ) ); + if ( attributeName.equals( fieldName ) ) { + finalResultStage = finalResultStage + .thenApply( finalResult -> entry.getLoadedValue( fetchGroupAttributeDescriptor.getName() ) ); } + // it's already been initialized (e.g. by a write) so we don't want to overwrite + // TODO: we should consider un-marking an attribute as dirty based on the selected value + // - we know the current value: + // getPropertyValue( entity, fetchGroupAttributeDescriptor.getAttributeIndex() ); + // - we know the selected value (see selectedValue below) + // - we can use the attribute Type to tell us if they are the same + // - assuming entity is a SelfDirtinessTracker we can also know if the attribute is currently + // considered dirty, and if really not dirty we would do the un-marking + // - of course that would mean a new method on SelfDirtinessTracker to allow un-marking continue; } - final Object selectedValue = values[i++]; - if ( selectedValue instanceof CompletionStage ) { + final Object result = results[index]; + if ( result instanceof CompletionStage ) { // This happens with a lazy one-to-one (bytecode enhancement enabled) - CompletionStage selectedValueStage = (CompletionStage) selectedValue; - resultStage = resultStage - .thenCompose( result -> selectedValueStage - .thenApply( selected -> { - final boolean set = initializeLazyProperty( - fieldName, - entity, - entry, - fetchGroupAttributeDescriptor.getLazyIndex(), - selected - ); - if ( set ) { - interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() ); - return selected; - } - return result; - } ) - ); + final CompletionStage resultStage = (CompletionStage) result; + finalResultStage = finalResultStage.thenCompose( finalResult -> resultStage + .thenApply( value -> { + if ( initializeLazyProperty( fieldName, entity, entry, fetchGroupAttributeDescriptor, result ) ) { + interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() ); + return value; + } + return finalResult; + } ) + ); } else { - final boolean set = initializeLazyProperty( fieldName, entity, entry, fetchGroupAttributeDescriptor.getLazyIndex(), selectedValue ); - if ( set ) { - resultStage = completedFuture( selectedValue ); + if ( initializeLazyProperty( fieldName, entity, entry, fetchGroupAttributeDescriptor, result ) ) { interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() ); + finalResultStage = finalResultStage.thenApply( finalResult -> result ); } } } - return resultStage.thenApply( result -> { - make( Log.class, lookup() ).trace( "Done initializing lazy properties" ); + return finalResultStage.thenApply( result -> { + LOG.trace( "Done initializing lazy properties" ); return result; } ); } @@ -526,9 +552,11 @@ private CompletionStage loadFromDatabaseOrCache( .load( identifier, entity, lockOptions, session ); } - SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions); + boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, LazyAttributeDescriptor fetchGroupAttributeDescriptor, Object propValue); - boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue); + void initializeLazyProperty(Object entity, EntityEntry entry, Object propValue, int index, Type type); + + SingleIdEntityLoader determineLoaderToUse(SharedSessionContractImplementor session, LockOptions lockOptions); Object initializeLazyProperty(String fieldName, Object entity, SharedSessionContractImplementor session); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java index c5d068db7..62ddf29f5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import org.hibernate.FetchMode; @@ -23,6 +24,7 @@ import org.hibernate.generator.Generator; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.id.IdentityGenerator; +import org.hibernate.loader.ast.internal.LoaderSelectBuilder; import org.hibernate.loader.ast.spi.BatchLoaderFactory; import org.hibernate.loader.ast.spi.EntityBatchLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; @@ -34,6 +36,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl; import org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor; @@ -48,6 +51,7 @@ import org.hibernate.query.named.NamedQueryMemento; import org.hibernate.reactive.loader.ast.internal.ReactiveMultiIdEntityLoaderArrayParam; import org.hibernate.reactive.loader.ast.internal.ReactiveMultiIdEntityLoaderStandard; +import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdEntityLoaderProvidedQueryImpl; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdEntityLoaderStandardImpl; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleUniqueKeyEntityLoaderStandard; @@ -64,6 +68,8 @@ import org.hibernate.reactive.sql.results.internal.ReactiveEntityResultImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; @@ -90,6 +96,7 @@ public class ReactiveAbstractPersisterDelegate { private final EntityPersister entityDescriptor; private Map> uniqueKeyLoadersNew; + private ConcurrentHashMap nonLazyPropertyLoadPlansByName; public ReactiveAbstractPersisterDelegate( final EntityPersister entityPersister, @@ -347,6 +354,61 @@ else if ( entityIdentifierMapping instanceof EmbeddedIdentifierMappingImpl embed } } + /* + * Same as AbstractEntityPersister#getOrCreateLazyLoadPlan + */ + public ReactiveSingleIdArrayLoadPlan getOrCreateLazyLoadPlan(String fieldName, List partsToSelect) { + var propertyLoadPlansByName = nonLazyPropertyLoadPlansByName; + if ( propertyLoadPlansByName == null ) { + propertyLoadPlansByName = new ConcurrentHashMap<>(); + final ReactiveSingleIdArrayLoadPlan newLazyLoanPlan = createLazyLoadPlan( partsToSelect ); + propertyLoadPlansByName.put( fieldName, newLazyLoanPlan ); + nonLazyPropertyLoadPlansByName = propertyLoadPlansByName; + return newLazyLoanPlan; + } + else { + final ReactiveSingleIdArrayLoadPlan lazyLoanPlan = nonLazyPropertyLoadPlansByName.get( fieldName ); + if ( lazyLoanPlan == null ) { + final ReactiveSingleIdArrayLoadPlan newLazyLoanPlan = createLazyLoadPlan( partsToSelect ); + nonLazyPropertyLoadPlansByName.put( fieldName, newLazyLoanPlan ); + return newLazyLoanPlan; + } + else { + return lazyLoanPlan; + } + } + } + + private ReactiveSingleIdArrayLoadPlan createLazyLoadPlan(List partsToSelect) { + if ( partsToSelect.isEmpty() ) { + // only one-to-one is lazily fetched + return null; + } + else { + final LockOptions lockOptions = new LockOptions(); + final JdbcParametersList.Builder jdbcParametersBuilder = JdbcParametersList.newBuilder(); + final SelectStatement select = LoaderSelectBuilder.createSelect( + entityDescriptor, + partsToSelect, + entityDescriptor.getIdentifierMapping(), + null, + 1, + new LoadQueryInfluencers( entityDescriptor.getFactory() ), + lockOptions, + jdbcParametersBuilder::add, + entityDescriptor.getFactory() + ); + return new ReactiveSingleIdArrayLoadPlan( + entityDescriptor, + entityDescriptor.getIdentifierMapping(), + select, + jdbcParametersBuilder.build(), + lockOptions, + entityDescriptor.getFactory() + ); + } + } + private static class ReactiveNonAggregatedIdentifierMappingImpl extends NonAggregatedIdentifierMappingImpl { public ReactiveNonAggregatedIdentifierMappingImpl( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java index 2443d4d9b..e9be58295 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java @@ -12,6 +12,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.spi.CascadeStyle; @@ -27,6 +28,7 @@ import org.hibernate.mapping.Property; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; @@ -52,6 +54,7 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.type.EntityType; +import org.hibernate.type.Type; import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; @@ -60,8 +63,7 @@ * An {@link ReactiveEntityPersister} backed by {@link JoinedSubclassEntityPersister} * and {@link ReactiveAbstractEntityPersister}. */ -public class ReactiveJoinedSubclassEntityPersister extends JoinedSubclassEntityPersister - implements ReactiveAbstractEntityPersister { +public class ReactiveJoinedSubclassEntityPersister extends JoinedSubclassEntityPersister implements ReactiveAbstractEntityPersister { private static final Log LOG = make( Log.class, lookup() ); @@ -76,6 +78,16 @@ public ReactiveJoinedSubclassEntityPersister( reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, new ReactiveRuntimeModelCreationContext( creationContext ) ); } + @Override + public boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, LazyAttributeDescriptor fetchGroupAttributeDescriptor, Object propValue) { + return super.initializeLazyProperty( fieldName, entity, entry, fetchGroupAttributeDescriptor, propValue ); + } + + @Override + public void initializeLazyProperty(Object entity, EntityEntry entry, Object propValue, int index, Type type) { + super.initializeLazyProperty( entity, entry, propValue, index, type ); + } + @Override protected SingleIdEntityLoader buildSingleIdEntityLoader() { return reactiveDelegate.buildSingleIdEntityLoader(); @@ -389,4 +401,13 @@ public ReactiveSingleIdArrayLoadPlan reactiveGetSQLLazySelectLoadPlan(String fet return this.getLazyLoadPlanByFetchGroup( getSubclassPropertyNameClosure() ).get(fetchGroup ); } + @Override + public ReactiveSingleIdArrayLoadPlan reactiveGetOrCreateLazyLoadPlan(String fieldName, List partsToSelect) { + return reactiveDelegate.getOrCreateLazyLoadPlan( fieldName, partsToSelect ); + } + + @Override + public boolean isNonLazyPropertyName(String fieldName) { + return super.isNonLazyPropertyName( fieldName ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java index 2f5878737..787ef5b5c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java @@ -13,6 +13,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.spi.CascadeStyle; @@ -30,6 +31,7 @@ import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; @@ -56,6 +58,7 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.type.EntityType; +import org.hibernate.type.Type; import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; @@ -80,6 +83,16 @@ public ReactiveSingleTableEntityPersister( reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, new ReactiveRuntimeModelCreationContext( creationContext ) ); } + @Override + public boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, LazyAttributeDescriptor fetchGroupAttributeDescriptor, Object propValue) { + return super.initializeLazyProperty( fieldName, entity, entry, fetchGroupAttributeDescriptor, propValue ); + } + + @Override + public void initializeLazyProperty(Object entity, EntityEntry entry, Object propValue, int index, Type type) { + super.initializeLazyProperty( entity, entry, propValue, index, type ); + } + @Override public GeneratedValuesMutationDelegate createInsertDelegate() { return ReactiveAbstractEntityPersister.super.createReactiveInsertDelegate(); @@ -209,11 +222,6 @@ protected AttributeMapping buildPluralAttributeMapping( ); } - @Override - public boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue) { - return super.initializeLazyProperty(fieldName, entity, entry, lazyIndex, selectedValue); - } - @Override public Object initializeLazyPropertiesFromDatastore(final Object entity, final Object id, final EntityEntry entry, final String fieldName, final SharedSessionContractImplementor session) { return reactiveInitializeLazyPropertiesFromDatastore( entity, id, entry, fieldName, session ); @@ -444,4 +452,14 @@ public ReactiveSingleIdArrayLoadPlan reactiveGetSQLLazySelectLoadPlan(String fet public NaturalIdMapping generateNaturalIdMapping(MappingModelCreationProcess creationProcess, PersistentClass bootEntityDescriptor) { return ReactiveAbstractEntityPersister.super.generateNaturalIdMapping(creationProcess, bootEntityDescriptor); } + + @Override + public ReactiveSingleIdArrayLoadPlan reactiveGetOrCreateLazyLoadPlan(String fieldName, List partsToSelect) { + return reactiveDelegate.getOrCreateLazyLoadPlan( fieldName, partsToSelect ); + } + + @Override + public boolean isNonLazyPropertyName(String fieldName) { + return super.isNonLazyPropertyName( fieldName ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java index bba3cf86d..71b91d4a5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java @@ -14,6 +14,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.MappingException; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.spi.CascadeStyle; @@ -30,6 +31,7 @@ import org.hibernate.mapping.Property; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; @@ -56,6 +58,7 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.type.EntityType; +import org.hibernate.type.Type; /** * An {@link ReactiveEntityPersister} backed by {@link UnionSubclassEntityPersister} @@ -76,6 +79,16 @@ public ReactiveUnionSubclassEntityPersister( reactiveDelegate = new ReactiveAbstractPersisterDelegate( this, persistentClass, new ReactiveRuntimeModelCreationContext( creationContext ) ); } + @Override + public boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, LazyAttributeDescriptor fetchGroupAttributeDescriptor, Object propValue) { + return super.initializeLazyProperty( fieldName, entity, entry, fetchGroupAttributeDescriptor, propValue ); + } + + @Override + public void initializeLazyProperty(Object entity, EntityEntry entry, Object propValue, int index, Type type) { + super.initializeLazyProperty( entity, entry, propValue, index, type ); + } + @Override protected SingleIdEntityLoader buildSingleIdEntityLoader() { return reactiveDelegate.buildSingleIdEntityLoader(); @@ -184,11 +197,6 @@ public Generator getGenerator() throws HibernateException { return reactiveDelegate.reactive( super.getGenerator() ); } - @Override - public boolean initializeLazyProperty(String fieldName, Object entity, EntityEntry entry, int lazyIndex, Object selectedValue) { - return super.initializeLazyProperty( fieldName, entity, entry, lazyIndex, selectedValue ); - } - /** * @see #insertReactive(Object[], Object, SharedSessionContractImplementor) */ @@ -415,4 +423,14 @@ protected ReactiveSingleUniqueKeyEntityLoader getReactiveUniqueKeyLoader public ReactiveSingleIdArrayLoadPlan reactiveGetSQLLazySelectLoadPlan(String fetchGroup) { return this.getLazyLoadPlanByFetchGroup( getSubclassPropertyNameClosure() ).get(fetchGroup ); } + + @Override + public ReactiveSingleIdArrayLoadPlan reactiveGetOrCreateLazyLoadPlan(String fieldName, List partsToSelect) { + return reactiveDelegate.getOrCreateLazyLoadPlan( fieldName, partsToSelect ); + } + + @Override + public boolean isNonLazyPropertyName(String fieldName) { + return super.isNonLazyPropertyName( fieldName ); + } } From 9632d0d7593dab2eef0bf76303d22ed7f6c4a3c2 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 6 Aug 2025 16:32:26 +0200 Subject: [PATCH 067/185] [#2404] Remove left-over code --- .../sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java index 81f1addf5..4f499f5cf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; import org.hibernate.ScrollMode; import org.hibernate.engine.spi.SubselectFetch; @@ -130,9 +129,6 @@ private static CompletionStage executeQueryInterpreter( ReactiveResultsConsumer resultsConsumer) { final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); - // I'm using a supplier so that the whenComplete at the end will catch any errors, like a finally-block - final Supplier fetchHandlerSupplier = () -> SubselectFetch - .createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.statement(), JdbcParametersList.empty(), jdbcParameterBindings ); return CompletionStages .supplyStage( () -> { final var subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( From 1c9fd8d4194260592a11698870195cb419b8daa4 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Wed, 6 Aug 2025 15:10:02 +0000 Subject: [PATCH 068/185] Update project version to : `3.1.0.CR2` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 93f7caab8..3f7da0e35 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.0-SNAPSHOT \ No newline at end of file +projectVersion=3.1.0.CR2 \ No newline at end of file From 67fe139fc6a542a885636cd2d0bb5e856b2f1086 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Wed, 6 Aug 2025 15:11:00 +0000 Subject: [PATCH 069/185] Update project version to : `3.1.0-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 3f7da0e35..93f7caab8 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.0.CR2 \ No newline at end of file +projectVersion=3.1.0-SNAPSHOT \ No newline at end of file From 51ffda64dd28516173aed490669b845db5f1eab2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 05:10:45 +0000 Subject: [PATCH 070/185] Bump cockroachdb/cockroach from v25.2.2 to v25.2.4 in /tooling/docker Bumps cockroachdb/cockroach from v25.2.2 to v25.2.4. --- updated-dependencies: - dependency-name: cockroachdb/cockroach dependency-version: v25.2.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- tooling/docker/cockroachdb.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile index 5f92dd720..3783d7f28 100644 --- a/tooling/docker/cockroachdb.Dockerfile +++ b/tooling/docker/cockroachdb.Dockerfile @@ -1,3 +1,3 @@ # CockroachDB # See https://hub.docker.com/r/cockroachdb/cockroach -FROM docker.io/cockroachdb/cockroach:v25.2.2 +FROM docker.io/cockroachdb/cockroach:v25.2.4 From ef24ed419d11ea7d488c6f4faa4c1a53808bf226 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 7 Aug 2025 14:35:00 +0200 Subject: [PATCH 071/185] Update dependencies for testing * Hibernate Gradle plugin from 7.0.8.Final to 7.0.9.Final * Junit from 5.13.3 to 5.13.4 * MySQL docker image from 9.3.0 to 9.4.0 --- gradle/libs.versions.toml | 4 ++-- tooling/docker/mysql.Dockerfile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9840d6432..e39179b65 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] assertjVersion = "3.27.3" hibernateOrmVersion = "7.1.0.CR2" -hibernateOrmGradlePluginVersion = "7.0.8.Final" +hibernateOrmGradlePluginVersion = "7.0.9.Final" jacksonDatabindVersion = "2.19.2" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" -junitVersion = "5.13.3" +junitVersion = "5.13.4" junitPlatformVersion = "1.13.3" log4jVersion = "2.25.1" testcontainersVersion = "1.21.3" diff --git a/tooling/docker/mysql.Dockerfile b/tooling/docker/mysql.Dockerfile index 69b12ca62..897fbd5ee 100644 --- a/tooling/docker/mysql.Dockerfile +++ b/tooling/docker/mysql.Dockerfile @@ -1,3 +1,3 @@ # MySQL # See https://hub.docker.com/_/mysql -FROM container-registry.oracle.com/mysql/community-server:9.3.0 +FROM container-registry.oracle.com/mysql/community-server:9.4.0 From 05ff2096c53ab97cb0e7428a3c8d7caf854789db Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 8 Aug 2025 09:44:48 +0200 Subject: [PATCH 072/185] [#2408] Update Hibernate ORM to 7.1.0.Final And realign the Hibernate Gradle plugin to the same version --- gradle.properties | 4 ++-- gradle/libs.versions.toml | 4 ++-- settings.gradle | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle.properties b/gradle.properties index 80dfe6f33..49109ad93 100644 --- a/gradle.properties +++ b/gradle.properties @@ -39,10 +39,10 @@ org.gradle.java.installations.auto-download=false ### Settings the following properties will override the version defined in gradle/libs.versions.toml # The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) -#hibernateOrmVersion = 7.1.0.CR1 +#hibernateOrmVersion = 7.1.0.Final # Override default Hibernate ORM Gradle plugin version -#hibernateOrmGradlePluginVersion = 7.1.0.CR1 +#hibernateOrmGradlePluginVersion = 7.1.0.Final # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) # this is required when using intervals or weird versions or the build will fail diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e39179b65..91aee6af1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.3" -hibernateOrmVersion = "7.1.0.CR2" -hibernateOrmGradlePluginVersion = "7.0.9.Final" +hibernateOrmVersion = "7.1.0.Final" +hibernateOrmGradlePluginVersion = "7.1.0.Final" jacksonDatabindVersion = "2.19.2" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" diff --git a/settings.gradle b/settings.gradle index 9e3962b53..f1c542d37 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,13 +24,13 @@ def GRADLE_MAX_SUPPORTED_BYTECODE_VERSION = 23 dependencyResolutionManagement { versionCatalogs { create("libs") { - // ./gradlew build -PhibernateOrmVersion=7.1.0.CR1 + // ./gradlew build -PhibernateOrmVersion=7.1.0.Final def hibernateOrmVersion = settings.ext.find("hibernateOrmVersion") ?: "" if ( hibernateOrmVersion != "" ) { version("hibernateOrmVersion", hibernateOrmVersion) } - // ./gradlew build -PhibernateOrmGradlePluginVersion=7.1.0.CR1 + // ./gradlew build -PhibernateOrmGradlePluginVersion=7.1.0.Final def hibernateOrmGradlePluginVersion = settings.ext.find("hibernateOrmGradlePluginVersion") ?: "" if ( hibernateOrmGradlePluginVersion ) { version("hibernateOrmGradlePluginVersion", hibernateOrmGradlePluginVersion) From 9222f470ff61fe0e4bc4420ff1737c142bc11190 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Fri, 8 Aug 2025 09:12:57 +0000 Subject: [PATCH 073/185] Update project version to : `3.1.0.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 93f7caab8..623585af8 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.0-SNAPSHOT \ No newline at end of file +projectVersion=3.1.0.Final \ No newline at end of file From f7e36004c0fafc032485be51707a6b609b8ed977 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Fri, 8 Aug 2025 09:13:56 +0000 Subject: [PATCH 074/185] Update project version to : `3.1.1-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 623585af8..261beff6f 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.0.Final \ No newline at end of file +projectVersion=3.1.1-SNAPSHOT \ No newline at end of file From 56c4a25d23ad49bab59bce49166fc23d9b6688f5 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 8 Aug 2025 14:46:18 +0200 Subject: [PATCH 075/185] [#2408] Fix Hibernate Gradle Plugin setup in the examples --- examples/native-sql-example/build.gradle | 2 +- examples/session-example/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/native-sql-example/build.gradle b/examples/native-sql-example/build.gradle index 50c5a4117..052166b33 100644 --- a/examples/native-sql-example/build.gradle +++ b/examples/native-sql-example/build.gradle @@ -44,7 +44,7 @@ dependencies { } // Optional: enable the bytecode enhancements -hibernate { enhancement } +hibernate { enhancement {} } // Create tasks to run the different API available. // diff --git a/examples/session-example/build.gradle b/examples/session-example/build.gradle index 41f9135a5..f734d6513 100644 --- a/examples/session-example/build.gradle +++ b/examples/session-example/build.gradle @@ -45,7 +45,7 @@ dependencies { } // Optional: enable the bytecode enhancements -hibernate { enhancement } +hibernate { enhancement {} } // Create tasks to run the different API available. // From a1598ae0c69b546ff5856d3f85e23199295a3af3 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 8 Aug 2025 16:14:03 +0200 Subject: [PATCH 076/185] [#2408] Add missing Override annotations --- .../sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java | 2 ++ .../temptable/ReactiveGlobalTemporaryTableMutationStrategy.java | 2 ++ .../temptable/ReactiveLocalTemporaryTableMutationStrategy.java | 2 ++ .../temptable/ReactivePersistentTableMutationStrategy.java | 2 ++ 4 files changed, 8 insertions(+) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java index be67bd8ad..445cfe2f6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteMutationStrategy.java @@ -40,6 +40,7 @@ public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement s return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } + @Override public ReactiveHandler buildHandler(SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { checkMatch( sqmDelete ); if ( getRootDescriptor().getSoftDeleteMapping() != null ) { @@ -66,6 +67,7 @@ public ReactiveHandler buildHandler(SqmDeleteStatement sqmDelete, DomainParam } } + @Override public MultiTableHandler buildHandler(SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, DomainQueryExecutionContext context, MutableObject firstJdbcParameterBindingsConsumer) { checkMatch( sqmUpdate ); return new ReactiveCteUpdateHandler( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java index aaad73b10..0995dc70e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveGlobalTemporaryTableMutationStrategy.java @@ -64,6 +64,7 @@ public MultiTableHandlerBuildResult buildHandler(SqmDeleteOrUpdateStatement s return new MultiTableHandlerBuildResult( multiTableHandler, firstJdbcParameterBindings.get() ); } + @Override public MultiTableHandler buildHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, @@ -83,6 +84,7 @@ public MultiTableHandler buildHandler( ); } + @Override public MultiTableHandler buildHandler( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java index 1073f118f..3e26378d9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveLocalTemporaryTableMutationStrategy.java @@ -23,6 +23,7 @@ public ReactiveLocalTemporaryTableMutationStrategy(LocalTemporaryTableMutationSt super( mutationStrategy.getTemporaryTable(), mutationStrategy.getSessionFactory() ); } + @Override public MultiTableHandler buildHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, @@ -42,6 +43,7 @@ public MultiTableHandler buildHandler( ); } + @Override public MultiTableHandler buildHandler( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java index 7b7a641ae..76109c78e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactivePersistentTableMutationStrategy.java @@ -50,6 +50,7 @@ public void release(SessionFactoryImplementor sessionFactory, JdbcConnectionAcce release( sessionFactory, connectionAccess, tableDroppedStage ); } + @Override public MultiTableHandler buildHandler( SqmUpdateStatement sqmUpdate, DomainParameterXref domainParameterXref, @@ -67,6 +68,7 @@ public MultiTableHandler buildHandler( ); } + @Override public MultiTableHandler buildHandler( SqmDeleteStatement sqmDelete, DomainParameterXref domainParameterXref, From 774e96c1bd9ecb15288275723029ac946ddcf08b Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 11 Aug 2025 11:05:32 +0200 Subject: [PATCH 077/185] [#2420] Keep only major.minor version in README For Hibernate ORM and Vert.x. The current automated release system and dependabot don't update the readme. It would get out of date at every minor release. I've included a link to the exact version in the catalog, though. --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ad38e4b2c..5e4cbf2d4 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,18 @@ Hibernate Reactive has been tested with: - CockroachDB v25 - MS SQL Server 2025 - Oracle 23 -- [Hibernate ORM][] 7.1.0.Final -- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.16 -- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.16 -- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.16 -- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5.16 -- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5.16 +- [Hibernate ORM][] 7.1 +- [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5 +- [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5 +- [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5 +- [Vert.x Reactive MS SQL Server Client](https://vertx.io/docs/vertx-mssql-client/java/) 4.5 +- [Vert.x Reactive Oracle Client](https://vertx.io/docs/vertx-oracle-client/java/) 4.5 - [Quarkus][Quarkus] via the Hibernate Reactive extension +The exact version of the libraries and images are in the +[catalog](https://github.com/hibernate/hibernate-reactive/blob/3.1/gradle/libs.versions.toml) +and in the [tooling/docker](https://github.com/hibernate/hibernate-reactive/tree/3.1/tooling/docker) folder. + [PostgreSQL]: https://www.postgresql.org [MySQL]: https://www.mysql.com [MariaDB]: https://mariadb.com From 1d2b305b2346a401fc02d046acd7a841d2ea35db Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 8 Aug 2025 12:59:38 +0200 Subject: [PATCH 078/185] [#2398] Add test to show Mutation query with Joined Inheritance for databases supporting Global Temporary Tabels issue has been solved --- .../JoinedSubclassInheritanceTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java index ab64e1925..9c3846ce0 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java @@ -5,6 +5,8 @@ */ package org.hibernate.reactive; +import org.hibernate.reactive.annotations.DisabledFor; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -28,6 +30,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL; @Timeout(value = 10, timeUnit = MINUTES) @@ -185,6 +188,31 @@ public void testQueryUpdateWithParameters(VertxTestContext context) { ); } + @Test + @DisabledFor( value = POSTGRESQL, reason = "vertx-sql-client issue: https://github.com/eclipse-vertx/vertx-sql-client/issues/1540" ) + public void testHqlInsertWithTransaction(VertxTestContext context) { + Integer id = 1; + String title = "Spell Book: A Comprehensive Guide to Magic Spells and Incantations"; + test( context, getMutinySessionFactory() + .withTransaction( session -> session.createMutationQuery( "insert into SpellBook (id, title, forbidden) values (:id, :title, :forbidden)" ) + .setParameter( "id", id ) + .setParameter( "title", title ) + .setParameter( "forbidden", true ) + .executeUpdate() + ).call( () -> getMutinySessionFactory() + .withTransaction( session -> session.createSelectionQuery( "from SpellBook g where g.id = :id ", SpellBook.class ) + .setParameter( "id", id ) + .getSingleResult() + .invoke( spellBook -> { + assertThat( spellBook.getTitle() ).isEqualTo( title ); + assertThat( spellBook.forbidden ).isTrue(); + } + ) + ) + ) + ); + } + @Entity(name="SpellBook") @Table(name = "SpellBookJS") @DiscriminatorValue("S") From b081630477a35887cafd2c500b0e3cefbddcb79c Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 11 Aug 2025 19:24:50 +0200 Subject: [PATCH 079/185] [#2398] Update reason for disabling the test and formattation Better link to the Hibernate Reactive issue, instead of the Vert.x one. --- .../JoinedSubclassInheritanceTest.java | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java index 9c3846ce0..432535b51 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java @@ -189,27 +189,26 @@ public void testQueryUpdateWithParameters(VertxTestContext context) { } @Test - @DisabledFor( value = POSTGRESQL, reason = "vertx-sql-client issue: https://github.com/eclipse-vertx/vertx-sql-client/issues/1540" ) + @DisabledFor(value = POSTGRESQL, reason = "https://github.com/hibernate/hibernate-reactive/issues/2412") public void testHqlInsertWithTransaction(VertxTestContext context) { - Integer id = 1; - String title = "Spell Book: A Comprehensive Guide to Magic Spells and Incantations"; - test( context, getMutinySessionFactory() - .withTransaction( session -> session.createMutationQuery( "insert into SpellBook (id, title, forbidden) values (:id, :title, :forbidden)" ) + final Integer id = 1; + final String title = "Spell Book: A Comprehensive Guide to Magic Spells and Incantations"; + test( context, getMutinySessionFactory().withTransaction( session -> session + .createMutationQuery( "insert into SpellBook (id, title, forbidden) values (:id, :title, :forbidden)" ) + .setParameter( "id", id ) + .setParameter( "title", title ) + .setParameter( "forbidden", true ) + .executeUpdate() ) + .call( () -> getMutinySessionFactory().withTransaction( session -> session + .createSelectionQuery( "from SpellBook g where g.id = :id ", SpellBook.class ) .setParameter( "id", id ) - .setParameter( "title", title ) - .setParameter( "forbidden", true ) - .executeUpdate() - ).call( () -> getMutinySessionFactory() - .withTransaction( session -> session.createSelectionQuery( "from SpellBook g where g.id = :id ", SpellBook.class ) - .setParameter( "id", id ) - .getSingleResult() - .invoke( spellBook -> { - assertThat( spellBook.getTitle() ).isEqualTo( title ); - assertThat( spellBook.forbidden ).isTrue(); - } - ) + .getSingleResult() + .invoke( spellBook -> { + assertThat( spellBook.getTitle() ).isEqualTo( title ); + assertThat( spellBook.forbidden ).isTrue(); + } ) - ) + ) ) ); } From 066caa9e7f08d8868db1faa3e3ea066f5b3d437a Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 16 Jul 2025 11:08:17 +0200 Subject: [PATCH 080/185] [#2332] SqlException, Reactive doesn't use JDBC when locking previously loaded entity --- .../DefaultReactiveLoadEventListener.java | 23 +-- .../ast/internal/ReactiveLoaderHelper.java | 143 ++++++++++++++++++ .../internal/ReactiveCacheLoadHelper.java | 46 ++++++ 3 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/internal/ReactiveCacheLoadHelper.java diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java index 9804a02f6..4f3d53e58 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLoadEventListener.java @@ -27,7 +27,6 @@ import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEventListener; -import org.hibernate.loader.internal.CacheLoadHelper; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMappingsList; import org.hibernate.metamodel.mapping.CompositeIdentifierMapping; @@ -48,7 +47,7 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSecondLevelCache; -import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSessionCache; +import static org.hibernate.reactive.loader.internal.ReactiveCacheLoadHelper.loadFromSessionCache; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.session.impl.SessionUtil.checkEntityFound; @@ -653,15 +652,17 @@ private CompletionStage doLoad( return nullFuture(); } else { - final CacheLoadHelper.PersistenceContextEntry persistenceContextEntry = - loadFromSessionCache( keyToLoad, event.getLockOptions(), options, event.getSession() ); - final Object entity = persistenceContextEntry.entity(); - if ( entity != null ) { - return persistenceContextEntry.isManaged() ? initializeIfNecessary( entity ) : nullFuture(); - } - else { - return loadFromCacheOrDatasource( event, persister, keyToLoad ); - } + return loadFromSessionCache( keyToLoad, event.getLockOptions(), options, event.getSession() ).thenCompose( + persistenceContextEntry -> { + final Object entity = persistenceContextEntry.entity(); + if ( entity != null ) { + return persistenceContextEntry.isManaged() ? initializeIfNecessary( entity ) : nullFuture(); + } + else { + return loadFromCacheOrDatasource( event, persister, keyToLoad ); + } + } + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java index 44c90bfdf..e15c42956 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java @@ -9,10 +9,23 @@ import java.util.List; import java.util.concurrent.CompletionStage; +import org.hibernate.Hibernate; +import org.hibernate.LockMode; import org.hibernate.LockOptions; +import org.hibernate.ObjectDeletedException; +import org.hibernate.cache.spi.access.EntityDataAccess; +import org.hibernate.cache.spi.access.SoftLock; +import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; +import org.hibernate.event.monitor.spi.DiagnosticEvent; +import org.hibernate.event.monitor.spi.EventMonitor; +import org.hibernate.event.spi.EventSource; +import org.hibernate.internal.OptimisticLockHelper; +import org.hibernate.loader.LoaderLogging; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.pretty.MessageHelper; +import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.tree.expression.JdbcParameter; @@ -25,6 +38,8 @@ import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import static java.util.Objects.requireNonNull; +import static org.hibernate.reactive.util.impl.CompletionStages.supplyStage; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * @see org.hibernate.loader.ast.internal.LoaderHelper @@ -92,4 +107,132 @@ public static CompletionStage> loadByArrayParameter( ReactiveListResultsConsumer.UniqueSemantic.FILTER ); } + + /** + * A Reactive implementation of {@link org.hibernate.loader.ast.internal.LoaderHelper#upgradeLock(Object, EntityEntry, LockOptions, SharedSessionContractImplementor)} + */ + public static CompletionStage upgradeLock( + Object object, + EntityEntry entry, + LockOptions lockOptions, + SharedSessionContractImplementor session) { + final LockMode requestedLockMode = lockOptions.getLockMode(); + if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) { + // Request is for a more restrictive lock than the lock already held + final ReactiveEntityPersister persister = (ReactiveEntityPersister) entry.getPersister(); + + if ( entry.getStatus().isDeletedOrGone()) { + throw new ObjectDeletedException( + "attempted to lock a deleted instance", + entry.getId(), + persister.getEntityName() + ); + } + + if ( LoaderLogging.LOADER_LOGGER.isTraceEnabled() ) { + LoaderLogging.LOADER_LOGGER.tracef( + "Locking `%s( %s )` in `%s` lock-mode", + persister.getEntityName(), + entry.getId(), + requestedLockMode + ); + } + + final boolean cachingEnabled = persister.canWriteToCache(); + SoftLock lock = null; + Object ck = null; + try { + if ( cachingEnabled ) { + final EntityDataAccess cache = persister.getCacheAccessStrategy(); + ck = cache.generateCacheKey( entry.getId(), persister, session.getFactory(), session.getTenantIdentifier() ); + lock = cache.lockItem( session, ck, entry.getVersion() ); + } + + if ( persister.isVersioned() && entry.getVersion() == null ) { + // This should be an empty entry created for an uninitialized bytecode proxy + if ( !Hibernate.isPropertyInitialized( object, persister.getVersionMapping().getPartName() ) ) { + Hibernate.initialize( object ); + entry = session.getPersistenceContextInternal().getEntry( object ); + assert entry.getVersion() != null; + } + else { + throw new IllegalStateException( String.format( + "Trying to lock versioned entity %s but found null version", + MessageHelper.infoString( persister.getEntityName(), entry.getId() ) + ) ); + } + } + + if ( persister.isVersioned() && requestedLockMode == LockMode.PESSIMISTIC_FORCE_INCREMENT ) { + // todo : should we check the current isolation mode explicitly? + OptimisticLockHelper.forceVersionIncrement( object, entry, session ); + } + else if ( entry.isExistsInDatabase() ) { + final EventMonitor eventMonitor = session.getEventMonitor(); + final DiagnosticEvent entityLockEvent = eventMonitor.beginEntityLockEvent(); + return reactiveLock( object, entry, lockOptions, session, persister, eventMonitor, entityLockEvent, cachingEnabled, ck, lock ); + } + else { + // should only be possible for a stateful session + if ( session instanceof EventSource eventSource ) { + eventSource.forceFlush( entry ); + } + } + entry.setLockMode(requestedLockMode); + } + finally { + // the database now holds a lock + the object is flushed from the cache, + // so release the soft lock + if ( cachingEnabled ) { + persister.getCacheAccessStrategy().unlockItem( session, ck, lock ); + } + } + } + return voidFuture(); + } + + private static CompletionStage reactiveLock( + Object object, + EntityEntry entry, + LockOptions lockOptions, + SharedSessionContractImplementor session, + ReactiveEntityPersister persister, + EventMonitor eventMonitor, + DiagnosticEvent entityLockEvent, + boolean cachingEnabled, + Object ck, + SoftLock lock) { + return supplyStage( () -> supplyStage( () -> persister.reactiveLock( entry.getId(), entry.getVersion(), object, lockOptions, session ) ) + .whenComplete( (v, e) -> completeLockEvent( entry, lockOptions, session, persister, eventMonitor, entityLockEvent, cachingEnabled, ck, lock, e == null ) ) ) + .whenComplete( (v, e) -> { + if ( cachingEnabled ) { + persister.getCacheAccessStrategy().unlockItem( session, ck, lock ); + } + } ); + } + + private static void completeLockEvent( + EntityEntry entry, + LockOptions lockOptions, + SharedSessionContractImplementor session, + ReactiveEntityPersister persister, + EventMonitor eventMonitor, + DiagnosticEvent entityLockEvent, + boolean cachingEnabled, + Object ck, + SoftLock lock, + boolean succes) { + eventMonitor.completeEntityLockEvent( + entityLockEvent, + entry.getId(), + persister.getEntityName(), + lockOptions.getLockMode(), + succes, + session + ); + if ( cachingEnabled ) { + persister.getCacheAccessStrategy().unlockItem( session, ck, lock ); + } + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/internal/ReactiveCacheLoadHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/internal/ReactiveCacheLoadHelper.java new file mode 100644 index 000000000..1e6f3eddd --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/internal/ReactiveCacheLoadHelper.java @@ -0,0 +1,46 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.loader.internal; + +import org.hibernate.LockOptions; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.event.spi.LoadEventListener; +import org.hibernate.loader.internal.CacheLoadHelper; +import org.hibernate.loader.internal.CacheLoadHelper.PersistenceContextEntry.EntityStatus; + +import java.util.concurrent.CompletionStage; + +import static org.hibernate.loader.internal.CacheLoadHelper.PersistenceContextEntry.EntityStatus.MANAGED; +import static org.hibernate.reactive.loader.ast.internal.ReactiveLoaderHelper.upgradeLock; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; + +/** + * A reactive implementation of {@link CacheLoadHelper} + */ +public class ReactiveCacheLoadHelper { + + public static CompletionStage loadFromSessionCache( + EntityKey keyToLoad, + LockOptions lockOptions, + LoadEventListener.LoadType options, + SharedSessionContractImplementor session) { + final Object old = session.getEntityUsingInterceptor( keyToLoad ); + EntityStatus entityStatus = MANAGED; + if ( old != null ) { + // this object was already loaded + final EntityEntry oldEntry = session.getPersistenceContext().getEntry( old ); + entityStatus = CacheLoadHelper.entityStatus( keyToLoad, options, session, oldEntry, old ); + if ( entityStatus == MANAGED ) { + return upgradeLock( old, oldEntry, lockOptions, session ) + .thenApply(v -> new CacheLoadHelper.PersistenceContextEntry( old, MANAGED ) ); + } + } + return completedFuture( new CacheLoadHelper.PersistenceContextEntry( old, entityStatus ) ); + } + +} From 86b0af19ae2182ed8956a16b46ecf2a551ed9e7b Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 16 Jul 2025 11:10:47 +0200 Subject: [PATCH 081/185] [#2332] Add test for SqlException, Reactive doesn't use JDBC when locking previously loaded entity --- .../hibernate/reactive/LockOnLoadTest.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/LockOnLoadTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LockOnLoadTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LockOnLoadTest.java new file mode 100644 index 000000000..fe0c7e20e --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LockOnLoadTest.java @@ -0,0 +1,70 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.Collection; +import java.util.List; + +import org.hibernate.LockMode; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@Timeout(value = 10, timeUnit = MINUTES) +public class LockOnLoadTest extends BaseReactiveTest{ + @Override + protected Collection> annotatedEntities() { + return List.of( Person.class ); + } + + @Test + public void testLockOnLoad(VertxTestContext context) { + Person person = new Person( 1L, "Davide" ); + + test( context, getMutinySessionFactory() + .withTransaction( session -> session.persist( person ) ) + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( Person.class, person.getId() ) + // the issue occurred when trying to find the same entity but upgrading the lock mode + .chain( p -> session.find( Person.class, person.getId(), LockMode.PESSIMISTIC_WRITE ) ) + .invoke( p -> assertThat( p ).isNotNull() ) + ) ) + ); + } + + @Entity(name = "Person") + @Table(name = "LockOnLoadTest.Person") + public static class Person { + @Id + private Long id; + + private String name; + + public Person() { + } + + public Person(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } +} From 5858b427c97cddf00002ced02e76082422630ec1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:53:47 +0000 Subject: [PATCH 082/185] Bump org.assertj:assertj-core from 3.27.3 to 3.27.4 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.3 to 3.27.4. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.4) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-version: 3.27.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 91aee6af1..6c0ac1b78 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -assertjVersion = "3.27.3" +assertjVersion = "3.27.4" hibernateOrmVersion = "7.1.0.Final" hibernateOrmGradlePluginVersion = "7.1.0.Final" jacksonDatabindVersion = "2.19.2" From d1d27ee35683fbaa9bac08a75f0efebd262e0aeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:32:11 +0000 Subject: [PATCH 083/185] Bump org.hibernate.validator:hibernate-validator in the hibernate group Bumps the hibernate group with 1 update: [org.hibernate.validator:hibernate-validator](https://github.com/hibernate/hibernate-validator). Updates `org.hibernate.validator:hibernate-validator` from 8.0.2.Final to 8.0.3.Final - [Changelog](https://github.com/hibernate/hibernate-validator/blob/8.0.3.Final/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-validator/compare/8.0.2.Final...8.0.3.Final) --- updated-dependencies: - dependency-name: org.hibernate.validator:hibernate-validator dependency-version: 8.0.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6c0ac1b78..e68772135 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ org-glassfish-expressly-expressly = { group = "org.glassfish.expressly", name = org-hibernate-orm-hibernate-core = { group = "org.hibernate.orm", name = "hibernate-core", version.ref = "hibernateOrmVersion" } org-hibernate-orm-hibernate-jcache = { group = "org.hibernate.orm", name = "hibernate-jcache", version.ref = "hibernateOrmVersion" } org-hibernate-orm-hibernate-jpamodelgen = { group = "org.hibernate.orm", name = "hibernate-jpamodelgen", version.ref = "hibernateOrmVersion" } -org-hibernate-validator-hibernate-validator = { group = "org.hibernate.validator", name = "hibernate-validator", version = "8.0.2.Final" } +org-hibernate-validator-hibernate-validator = { group = "org.hibernate.validator", name = "hibernate-validator", version = "8.0.3.Final" } org-jboss-logging-jboss-logging = { group = "org.jboss.logging", name = "jboss-logging", version.ref = "jbossLoggingVersion" } org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", name = "jboss-logging-annotations", version.ref = "jbossLoggingAnnotationVersion" } org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" } From e020de64a0bc642399d6ac9afeaa26be6def2bf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:32:33 +0000 Subject: [PATCH 084/185] Bump org.mariadb.jdbc:mariadb-java-client in the testcontainers group Bumps the testcontainers group with 1 update: [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j). Updates `org.mariadb.jdbc:mariadb-java-client` from 3.5.4 to 3.5.5 - [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases) - [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/main/CHANGELOG.md) - [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.5.4...3.5.5) --- updated-dependencies: - dependency-name: org.mariadb.jdbc:mariadb-java-client dependency-version: 3.5.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e68772135..cfd2a8644 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,7 +44,7 @@ org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" } org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" } org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "junitPlatformVersion" } -org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.4" } +org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.5" } org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.7" } org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" } org-testcontainers-db2 = { group = "org.testcontainers", name = "db2", version.ref = "testcontainersVersion" } From 6cb99d9b3a0247f3fbe214cb47ab52d88bbda8d9 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 12 Aug 2025 16:45:01 +0200 Subject: [PATCH 085/185] Update Junit platform to 1.13.4 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cfd2a8644..da507aa81 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ jacksonDatabindVersion = "2.19.2" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" junitVersion = "5.13.4" -junitPlatformVersion = "1.13.3" +junitPlatformVersion = "1.13.4" log4jVersion = "2.25.1" testcontainersVersion = "1.21.3" vertxSqlClientVersion = "4.5.16" From dd2a69cfb0b267f95e0ec3628e2543f801c507ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:41:21 +0000 Subject: [PATCH 086/185] Bump mariadb from 11.8.2 to 12.0.2 in /tooling/docker Bumps mariadb from 11.8.2 to 12.0.2. --- updated-dependencies: - dependency-name: mariadb dependency-version: 12.0.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- tooling/docker/maria.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/maria.Dockerfile b/tooling/docker/maria.Dockerfile index fc954df6e..6aa9bf898 100644 --- a/tooling/docker/maria.Dockerfile +++ b/tooling/docker/maria.Dockerfile @@ -1,3 +1,3 @@ # MariaDB # See https://hub.docker.com/_/mariadb -FROM docker.io/mariadb:11.8.2 +FROM docker.io/mariadb:12.0.2 From 223770c96ad8e72a7305d2313c43eda10e16095e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:42:27 +0000 Subject: [PATCH 087/185] Bump the vertx group with 10 updates Bumps the vertx group with 10 updates: | Package | From | To | | --- | --- | --- | | [io.vertx:vertx-db2-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.16` | `4.5.17` | | [io.vertx:vertx-junit5](https://github.com/eclipse-vertx/vertx-junit5) | `4.5.16` | `4.5.17` | | [io.vertx:vertx-micrometer-metrics](https://github.com/vert-x3/vertx-micrometer-metrics) | `4.5.16` | `4.5.17` | | [io.vertx:vertx-mssql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.16` | `4.5.17` | | [io.vertx:vertx-mysql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.16` | `4.5.17` | | [io.vertx:vertx-oracle-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.16` | `4.5.17` | | [io.vertx:vertx-pg-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.16` | `4.5.17` | | [io.vertx:vertx-sql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.16` | `4.5.17` | | [io.vertx:vertx-web](https://github.com/vert-x3/vertx-web) | `4.5.16` | `4.5.17` | | [io.vertx:vertx-web-client](https://github.com/vert-x3/vertx-web) | `4.5.16` | `4.5.17` | Updates `io.vertx:vertx-db2-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-junit5` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-junit5/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-micrometer-metrics` from 4.5.16 to 4.5.17 - [Commits](https://github.com/vert-x3/vertx-micrometer-metrics/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-mssql-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-mysql-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-oracle-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-pg-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-sql-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-junit5` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-junit5/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-micrometer-metrics` from 4.5.16 to 4.5.17 - [Commits](https://github.com/vert-x3/vertx-micrometer-metrics/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-mssql-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-mysql-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-oracle-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-pg-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-sql-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-web` from 4.5.16 to 4.5.17 - [Commits](https://github.com/vert-x3/vertx-web/compare/4.5.16...4.5.17) Updates `io.vertx:vertx-web-client` from 4.5.16 to 4.5.17 - [Commits](https://github.com/vert-x3/vertx-web/compare/4.5.16...4.5.17) --- updated-dependencies: - dependency-name: io.vertx:vertx-db2-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-junit5 dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-micrometer-metrics dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mssql-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mysql-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-oracle-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-pg-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-sql-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-junit5 dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-micrometer-metrics dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mssql-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mysql-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-oracle-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-pg-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-sql-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-web dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-web-client dependency-version: 4.5.17 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index da507aa81..6630ba606 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,9 +9,9 @@ junitVersion = "5.13.4" junitPlatformVersion = "1.13.4" log4jVersion = "2.25.1" testcontainersVersion = "1.21.3" -vertxSqlClientVersion = "4.5.16" -vertxWebVersion= "4.5.16" -vertxWebClientVersion = "4.5.16" +vertxSqlClientVersion = "4.5.17" +vertxWebVersion= "4.5.17" +vertxWebClientVersion = "4.5.17" [libraries] com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } From abd3b73bb964d3c1e20690a8fdec85572ce97060 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:42:31 +0000 Subject: [PATCH 088/185] Bump io.smallrye.reactive:mutiny from 2.9.3 to 2.9.4 in the mutiny group Bumps the mutiny group with 1 update: [io.smallrye.reactive:mutiny](https://github.com/smallrye/smallrye-mutiny). Updates `io.smallrye.reactive:mutiny` from 2.9.3 to 2.9.4 - [Release notes](https://github.com/smallrye/smallrye-mutiny/releases) - [Commits](https://github.com/smallrye/smallrye-mutiny/compare/2.9.3...2.9.4) --- updated-dependencies: - dependency-name: io.smallrye.reactive:mutiny dependency-version: 2.9.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: mutiny ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6630ba606..c130372c7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" } com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.1.0.jre11-preview" } com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.3.0" } com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" } -io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.3" } +io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.4" } io-vertx-vertx-db2-client = { group = "io.vertx", name = "vertx-db2-client", version.ref = "vertxSqlClientVersion" } io-vertx-vertx-junit5 = { group = "io.vertx", name = "vertx-junit5", version.ref = "vertxSqlClientVersion" } io-vertx-vertx-micrometer-metrics = { group = "io.vertx", name = "vertx-micrometer-metrics", version.ref = "vertxSqlClientVersion" } From 027e045962c1eb863aac564dc3a1307ed86fd8b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:42:44 +0000 Subject: [PATCH 089/185] Bump the testcontainers group with 2 updates Bumps the testcontainers group with 2 updates: [com.microsoft.sqlserver:mssql-jdbc](https://github.com/Microsoft/mssql-jdbc) and [com.mysql:mysql-connector-j](https://github.com/mysql/mysql-connector-j). Updates `com.microsoft.sqlserver:mssql-jdbc` from 13.1.0.jre11-preview to 13.1.1.jre11-preview - [Release notes](https://github.com/Microsoft/mssql-jdbc/releases) - [Changelog](https://github.com/microsoft/mssql-jdbc/blob/main/CHANGELOG.md) - [Commits](https://github.com/Microsoft/mssql-jdbc/commits) Updates `com.mysql:mysql-connector-j` from 9.3.0 to 9.4.0 - [Changelog](https://github.com/mysql/mysql-connector-j/blob/release/9.x/CHANGES) - [Commits](https://github.com/mysql/mysql-connector-j/compare/9.3.0...9.4.0) --- updated-dependencies: - dependency-name: com.microsoft.sqlserver:mssql-jdbc dependency-version: 13.1.1.jre11-preview dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers - dependency-name: com.mysql:mysql-connector-j dependency-version: 9.4.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c130372c7..292d7a4de 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,8 +16,8 @@ vertxWebClientVersion = "4.5.17" [libraries] com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" } -com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.1.0.jre11-preview" } -com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.3.0" } +com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.1.1.jre11-preview" } +com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.4.0" } com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" } io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.4" } io-vertx-vertx-db2-client = { group = "io.vertx", name = "vertx-db2-client", version.ref = "vertxSqlClientVersion" } From 10570cef8a1e21c34379d7b17cc14d57cb258e7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:42:47 +0000 Subject: [PATCH 090/185] Bump org.junit.platform:junit-platform-launcher Bumps [org.junit.platform:junit-platform-launcher](https://github.com/junit-team/junit-framework) from junitPlatformVersion to 1.13.4. - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/commits) --- updated-dependencies: - dependency-name: org.junit.platform:junit-platform-launcher dependency-version: 1.13.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 292d7a4de..73f994eba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", nam org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" } org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" } org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" } -org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "junitPlatformVersion" } +org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "1.13.4" } org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.5" } org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.7" } org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" } From b83b00ed5ce792bc962c72a0e8f20f1b3ca80283 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Aug 2025 11:42:50 +0000 Subject: [PATCH 091/185] Bump com.diffplug.spotless from 7.1.0 to 7.2.1 Bumps com.diffplug.spotless from 7.1.0 to 7.2.1. --- updated-dependencies: - dependency-name: com.diffplug.spotless dependency-version: 7.2.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73f994eba..e355afca8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,6 +55,6 @@ org-testcontainers-oracle-xe = { group = "org.testcontainers", name = "oracle-xe org-testcontainers-postgresql = { group = "org.testcontainers", name = "postgresql", version.ref = "testcontainersVersion" } [plugins] -com-diffplug-spotless = { id = "com.diffplug.spotless", version = "7.1.0" } +com-diffplug-spotless = { id = "com.diffplug.spotless", version = "7.2.1" } org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.4" } org-hibernate-orm = { id = "org.hibernate.orm", version.ref = "hibernateOrmGradlePluginVersion" } From db20ca9e30679b1e6e797c90f95ceeceb52ae97d Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 17 Aug 2025 01:02:11 +0000 Subject: [PATCH 092/185] Update project version to : `3.1.1.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 261beff6f..773bf3e21 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.1-SNAPSHOT \ No newline at end of file +projectVersion=3.1.1.Final \ No newline at end of file From e22cf93a90b6a2ef1661726bbc87de20232115fa Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 17 Aug 2025 01:03:09 +0000 Subject: [PATCH 093/185] Update project version to : `3.1.2-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 773bf3e21..94803b2ab 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.1.Final \ No newline at end of file +projectVersion=3.1.2-SNAPSHOT \ No newline at end of file From 56252ebe194c2a58f3c13b7b4b569b3d8b09abb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 06:15:30 +0000 Subject: [PATCH 094/185] Bump postgres from 17.5 to 17.6 in /tooling/docker Bumps postgres from 17.5 to 17.6. --- updated-dependencies: - dependency-name: postgres dependency-version: '17.6' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tooling/docker/postgresql.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/postgresql.Dockerfile b/tooling/docker/postgresql.Dockerfile index fb36f48e8..d245286dc 100644 --- a/tooling/docker/postgresql.Dockerfile +++ b/tooling/docker/postgresql.Dockerfile @@ -1,3 +1,3 @@ # PostgreSQL # See https://hub.docker.com/_/postgres -FROM docker.io/postgres:17.5 +FROM docker.io/postgres:17.6 From 6554a2f494b89ace60a525efd66b9aace9c3eafb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:40:39 +0000 Subject: [PATCH 095/185] Bump com.microsoft.sqlserver:mssql-jdbc in the testcontainers group Bumps the testcontainers group with 1 update: [com.microsoft.sqlserver:mssql-jdbc](https://github.com/Microsoft/mssql-jdbc). Updates `com.microsoft.sqlserver:mssql-jdbc` from 13.1.1.jre11-preview to 13.2.0.jre11 - [Release notes](https://github.com/Microsoft/mssql-jdbc/releases) - [Changelog](https://github.com/microsoft/mssql-jdbc/blob/main/CHANGELOG.md) - [Commits](https://github.com/Microsoft/mssql-jdbc/commits) --- updated-dependencies: - dependency-name: com.microsoft.sqlserver:mssql-jdbc dependency-version: 13.2.0.jre11 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e355afca8..ee4464272 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ vertxWebClientVersion = "4.5.17" [libraries] com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" } -com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.1.1.jre11-preview" } +com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.2.0.jre11" } com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.4.0" } com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" } io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.4" } From 65b2c63365d432d3720df0d0d1007a833a4b5698 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:45:38 +0000 Subject: [PATCH 096/185] Bump the vertx group with 10 updates Bumps the vertx group with 10 updates: | Package | From | To | | --- | --- | --- | | [io.vertx:vertx-db2-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.17` | `4.5.18` | | [io.vertx:vertx-junit5](https://github.com/eclipse-vertx/vertx-junit5) | `4.5.17` | `4.5.18` | | [io.vertx:vertx-micrometer-metrics](https://github.com/vert-x3/vertx-micrometer-metrics) | `4.5.17` | `4.5.18` | | [io.vertx:vertx-mssql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.17` | `4.5.18` | | [io.vertx:vertx-mysql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.17` | `4.5.18` | | [io.vertx:vertx-oracle-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.17` | `4.5.18` | | [io.vertx:vertx-pg-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.17` | `4.5.18` | | [io.vertx:vertx-sql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.17` | `4.5.18` | | [io.vertx:vertx-web](https://github.com/vert-x3/vertx-web) | `4.5.17` | `4.5.18` | | [io.vertx:vertx-web-client](https://github.com/vert-x3/vertx-web) | `4.5.17` | `4.5.18` | Updates `io.vertx:vertx-db2-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-junit5` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-junit5/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-micrometer-metrics` from 4.5.17 to 4.5.18 - [Commits](https://github.com/vert-x3/vertx-micrometer-metrics/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-mssql-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-mysql-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-oracle-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-pg-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-sql-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-junit5` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-junit5/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-micrometer-metrics` from 4.5.17 to 4.5.18 - [Commits](https://github.com/vert-x3/vertx-micrometer-metrics/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-mssql-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-mysql-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-oracle-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-pg-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-sql-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-web` from 4.5.17 to 4.5.18 - [Commits](https://github.com/vert-x3/vertx-web/compare/4.5.17...4.5.18) Updates `io.vertx:vertx-web-client` from 4.5.17 to 4.5.18 - [Commits](https://github.com/vert-x3/vertx-web/compare/4.5.17...4.5.18) --- updated-dependencies: - dependency-name: io.vertx:vertx-db2-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-junit5 dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-micrometer-metrics dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mssql-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mysql-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-oracle-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-pg-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-sql-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-junit5 dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-micrometer-metrics dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mssql-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mysql-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-oracle-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-pg-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-sql-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-web dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-web-client dependency-version: 4.5.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ee4464272..eed69a3fa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,9 +9,9 @@ junitVersion = "5.13.4" junitPlatformVersion = "1.13.4" log4jVersion = "2.25.1" testcontainersVersion = "1.21.3" -vertxSqlClientVersion = "4.5.17" -vertxWebVersion= "4.5.17" -vertxWebClientVersion = "4.5.17" +vertxSqlClientVersion = "4.5.18" +vertxWebVersion= "4.5.18" +vertxWebClientVersion = "4.5.18" [libraries] com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } From 1362cc268eca1694ca71e95a8ebd960055a5ac74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 13:16:45 +0000 Subject: [PATCH 097/185] Bump org.ehcache:ehcache from 3.10.8 to 3.11.0 Bumps [org.ehcache:ehcache](https://github.com/ehcache/ehcache3) from 3.10.8 to 3.11.0. - [Release notes](https://github.com/ehcache/ehcache3/releases) - [Commits](https://github.com/ehcache/ehcache3/compare/v3.10.8...v3.11.0) --- updated-dependencies: - dependency-name: org.ehcache:ehcache dependency-version: 3.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eed69a3fa..42bd42ea0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ io-vertx-vertx-web = { group = "io.vertx", name = "vertx-web", version.ref = "ve io-vertx-vertx-web-client = { group = "io.vertx", name = "vertx-web-client", version.ref = "vertxWebClientVersion" } org-apache-logging-log4j-log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4jVersion" } org-assertj-assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertjVersion" } -org-ehcache-ehcache = { group = "org.ehcache", name = "ehcache", version = "3.10.8" } +org-ehcache-ehcache = { group = "org.ehcache", name = "ehcache", version = "3.11.0" } org-glassfish-expressly-expressly = { group = "org.glassfish.expressly", name = "expressly", version = "5.0.0" } org-hibernate-orm-hibernate-core = { group = "org.hibernate.orm", name = "hibernate-core", version.ref = "hibernateOrmVersion" } org-hibernate-orm-hibernate-jcache = { group = "org.hibernate.orm", name = "hibernate-jcache", version.ref = "hibernateOrmVersion" } From 7bbc208b0ce8b8af66f92ca5e6a9fefede836e28 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 14 Aug 2025 18:28:37 +0200 Subject: [PATCH 098/185] Remove warnings in BaseReactiveTest Replace class casts with pattern variables --- .../hibernate/reactive/BaseReactiveTest.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java index 07a79379b..fc8a7b8f4 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BaseReactiveTest.java @@ -308,38 +308,32 @@ protected CompletionStage cleanDb() { } protected static CompletionStage closeSession(Object closable) { - if ( closable instanceof CompletionStage ) { - CompletionStage closableStage = (CompletionStage) closable; + if ( closable instanceof CompletionStage closableStage ) { return closableStage.thenCompose( BaseReactiveTest::closeSession ); } - if ( closable instanceof Uni ) { - Uni closableUni = (Uni) closable; + if ( closable instanceof Uni closableUni ) { return closableUni.subscribeAsCompletionStage() .thenCompose( BaseReactiveTest::closeSession ); } - if ( closable instanceof ReactiveConnection ) { - return ( (ReactiveConnection) closable ).close(); + if ( closable instanceof ReactiveConnection reactiveConnection) { + return reactiveConnection.close(); } - if ( closable instanceof Mutiny.Session ) { - Mutiny.Session mutiny = (Mutiny.Session) closable; + if ( closable instanceof Mutiny.Session mutiny ) { if ( mutiny.isOpen() ) { return mutiny.close().subscribeAsCompletionStage(); } } - if ( closable instanceof Stage.Session ) { - Stage.Session stage = (Stage.Session) closable; + if ( closable instanceof Stage.Session stage ) { if ( stage.isOpen() ) { return stage.close(); } } - if ( closable instanceof Mutiny.StatelessSession ) { - Mutiny.StatelessSession mutiny = (Mutiny.StatelessSession) closable; + if ( closable instanceof Mutiny.StatelessSession mutiny ) { if ( mutiny.isOpen() ) { return mutiny.close().subscribeAsCompletionStage(); } } - if ( closable instanceof Stage.StatelessSession ) { - Stage.StatelessSession stage = (Stage.StatelessSession) closable; + if ( closable instanceof Stage.StatelessSession stage ) { if ( stage.isOpen() ) { return stage.close(); } From f78099a26bc1128e4ed21301c478d7e63a5ffc10 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 14 Aug 2025 18:31:38 +0200 Subject: [PATCH 099/185] [#2330] Test collection events with StatelessSession Add test from ORM issue HHH-19523 --- ...ollectionStatelessSessionListenerTest.java | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/CollectionStatelessSessionListenerTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CollectionStatelessSessionListenerTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CollectionStatelessSessionListenerTest.java new file mode 100644 index 000000000..0d88a6700 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CollectionStatelessSessionListenerTest.java @@ -0,0 +1,245 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.AbstractCollectionEvent; +import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.PostCollectionRecreateEvent; +import org.hibernate.event.spi.PostCollectionRecreateEventListener; +import org.hibernate.event.spi.PostCollectionRemoveEvent; +import org.hibernate.event.spi.PostCollectionRemoveEventListener; +import org.hibernate.event.spi.PreCollectionRecreateEvent; +import org.hibernate.event.spi.PreCollectionRecreateEventListener; +import org.hibernate.event.spi.PreCollectionRemoveEvent; +import org.hibernate.event.spi.PreCollectionRemoveEventListener; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Adapt test in ORM: CollectionStatelessSessionListenerTest + */ +public class CollectionStatelessSessionListenerTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return Set.of( EntityA.class, EntityB.class ); + } + + @Test + public void mutinyStatelessInsert(VertxTestContext context) { + final List events = new ArrayList<>(); + initializeListeners( events ); + + EntityA a = new EntityA(); + EntityB b = new EntityB(); + a.children.add( b ); + test( context, getMutinySessionFactory() + .withStatelessTransaction( statelessSession -> statelessSession + .insert( b ) + .chain( () -> statelessSession.insert( a ) ) + .chain( () -> statelessSession.delete( a ) ) + .chain( () -> statelessSession.delete( b ) ) + ) + .invoke( () -> assertEvents( events ) ) + ); + } + + @Test + public void mutinyStatelessInsertAll(VertxTestContext context) { + final List events = new ArrayList<>(); + initializeListeners( events ); + + EntityA a = new EntityA(); + EntityB b = new EntityB(); + a.children.add( b ); + test( context, getMutinySessionFactory() + .withStatelessTransaction( statelessSession -> statelessSession + .insertAll( b, a ) + .chain( () -> statelessSession.deleteAll( a, b ) ) + ) + .invoke( () -> assertEvents( events ) ) + ); + } + + @Test + public void stageStatelessInsert(VertxTestContext context) { + final List events = new ArrayList<>(); + initializeListeners( events ); + + EntityA a = new EntityA(); + EntityB b = new EntityB(); + a.children.add( b ); + test( context, getSessionFactory() + .withStatelessTransaction( statelessSession -> statelessSession + .insert( b ) + .thenCompose( v -> statelessSession.insert( a ) ) + .thenCompose( v -> statelessSession.delete( a ) ) + .thenCompose( v -> statelessSession.delete( b ) ) + ) + .thenAccept( v -> assertEvents( events ) ) + ); + } + + @Test + public void stageStatelessInsertAll(VertxTestContext context) { + final List events = new ArrayList<>(); + initializeListeners( events ); + + EntityA a = new EntityA(); + EntityB b = new EntityB(); + a.children.add( b ); + test( context, getSessionFactory() + .withStatelessTransaction( statelessSession -> statelessSession + .insert( b, a ) + .thenCompose( v -> statelessSession.delete( a, b ) ) + ) + .thenAccept( v -> assertEvents( events ) ) + ); + } + + private static void assertEvents(List events) { + assertThat( events ).hasSize( 4 ); + assertThat( events.get( 0 ) ) + .isInstanceOf( PreCollectionRecreateEvent.class ) + .extracting( AbstractCollectionEvent::getAffectedOwnerEntityName ).isEqualTo( EntityA.class.getName() ); + assertThat( events.get( 1 ) ) + .isInstanceOf( PostCollectionRecreateEvent.class ) + .extracting( AbstractCollectionEvent::getAffectedOwnerEntityName ).isEqualTo( EntityA.class.getName() ); + assertThat( events.get( 2 ) ) + .isInstanceOf( PreCollectionRemoveEvent.class ) + .extracting( AbstractCollectionEvent::getAffectedOwnerEntityName ).isEqualTo( EntityA.class.getName() ); + assertThat( events.get( 3 ) ) + .isInstanceOf( PostCollectionRemoveEvent.class ) + .extracting( AbstractCollectionEvent::getAffectedOwnerEntityName ).isEqualTo( EntityA.class.getName() ); + } + + private void initializeListeners(List events) { + final EventListenerRegistry registry = ( (SessionFactoryImplementor) factoryManager + .getHibernateSessionFactory() ) + .getEventListenerRegistry(); + + // Clear previous listeners + registry.getEventListenerGroup( EventType.PRE_COLLECTION_RECREATE ) + .clearListeners(); + registry.getEventListenerGroup( EventType.PRE_COLLECTION_REMOVE ) + .clearListeners(); + registry.getEventListenerGroup( EventType.POST_COLLECTION_RECREATE ) + .clearListeners(); + registry.getEventListenerGroup( EventType.POST_COLLECTION_REMOVE ) + .clearListeners(); + + // Add new listeners + registry.getEventListenerGroup( EventType.PRE_COLLECTION_RECREATE ) + .appendListener( new MyPreCollectionRecreateEventListener( events ) ); + registry.getEventListenerGroup( EventType.PRE_COLLECTION_REMOVE ) + .appendListener( new MyPreCollectionRemoveEventListener( events ) ); + registry.getEventListenerGroup( EventType.POST_COLLECTION_RECREATE ) + .appendListener( new MyPostCollectionRecreateEventListener( events ) ); + registry.getEventListenerGroup( EventType.POST_COLLECTION_REMOVE ) + .appendListener( new MyPostCollectionRemoveEventListener( events ) ); + } + + @Entity + @Table(name = "ENTITY_A") + public static class EntityA { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "ID") + Integer id; + + @OneToMany + @JoinColumn(name = "ENTITY_A") + Collection children = new ArrayList<>(); + } + + @Entity + @Table(name = "ENTITY_B") + public static class EntityB { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "ID") + Integer id; + } + + public static class MyPreCollectionRecreateEventListener implements PreCollectionRecreateEventListener { + + private final List events; + + + public MyPreCollectionRecreateEventListener(List events) { + this.events = events; + } + + @Override + public void onPreRecreateCollection(PreCollectionRecreateEvent event) { + events.add( event ); + } + + } + + public static class MyPreCollectionRemoveEventListener implements PreCollectionRemoveEventListener { + + private final List events; + + public MyPreCollectionRemoveEventListener(List events) { + this.events = events; + } + + @Override + public void onPreRemoveCollection(PreCollectionRemoveEvent event) { + events.add( event ); + } + + } + + public static class MyPostCollectionRecreateEventListener implements PostCollectionRecreateEventListener { + + private final List events; + + public MyPostCollectionRecreateEventListener(List events) { + this.events = events; + } + + @Override + public void onPostRecreateCollection(PostCollectionRecreateEvent event) { + events.add( event ); + } + } + + public static class MyPostCollectionRemoveEventListener implements PostCollectionRemoveEventListener { + + private final List events; + + public MyPostCollectionRemoveEventListener(List events) { + this.events = events; + } + + @Override + public void onPostRemoveCollection(PostCollectionRemoveEvent event) { + events.add( event ); + } + } +} From 6738a0e5b3aeea596f50825c71247b32dba48545 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 24 Aug 2025 01:02:19 +0000 Subject: [PATCH 100/185] Update project version to : `3.1.2.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 94803b2ab..8ad2ec82c 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.2-SNAPSHOT \ No newline at end of file +projectVersion=3.1.2.Final \ No newline at end of file From 39b50caf3aa07d656a4e0f6ecd0f00d749ef3a55 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 24 Aug 2025 01:03:16 +0000 Subject: [PATCH 101/185] Update project version to : `3.1.3-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 8ad2ec82c..315b96f0e 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.2.Final \ No newline at end of file +projectVersion=3.1.3-SNAPSHOT \ No newline at end of file From bf5c3b61e8576cec883a6042a7e6ca52d18f2821 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 05:13:59 +0000 Subject: [PATCH 102/185] Bump cockroachdb/cockroach from v25.2.4 to v25.3.0 in /tooling/docker Bumps cockroachdb/cockroach from v25.2.4 to v25.3.0. --- updated-dependencies: - dependency-name: cockroachdb/cockroach dependency-version: v25.3.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- tooling/docker/cockroachdb.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile index 3783d7f28..5f660daf4 100644 --- a/tooling/docker/cockroachdb.Dockerfile +++ b/tooling/docker/cockroachdb.Dockerfile @@ -1,3 +1,3 @@ # CockroachDB # See https://hub.docker.com/r/cockroachdb/cockroach -FROM docker.io/cockroachdb/cockroach:v25.2.4 +FROM docker.io/cockroachdb/cockroach:v25.3.0 From ca321b3bec033a92150e2e7b2fbe3daa2784c92d Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 21 Aug 2025 17:58:30 +0200 Subject: [PATCH 103/185] [#2464] Skip query parameter processing We used it to replace parameter markers for queries because Vert.x doesn't support the JDBC syntax and our own implementation of `ParameterMarkerStrategy` was not enough in the past. Now all tests pass even without the processing the query. --- .../sql/internal/ReactiveNativeNonSelectQueryPlan.java | 6 +----- .../internal/ReactiveDeferredResultSetAccess.java | 9 +-------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java index be5541c5d..470218e17 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.query.sql.internal; - import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -20,7 +19,6 @@ import org.hibernate.query.sql.spi.ParameterOccurrence; import org.hibernate.query.sqm.internal.SqmJdbcExecutionContextAdapter; import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; @@ -71,10 +69,8 @@ public CompletionStage executeReactiveUpdate(DomainQueryExecutionContex } final SQLQueryParser parser = new SQLQueryParser( sql, null, session.getFactory() ); - - Parameters parameters = Parameters.instance( session.getDialect() ); final JdbcOperationQueryMutation jdbcMutation = new JdbcOperationQueryMutationNative( - parameters.process( parser.process() ), + parser.process(), jdbcParameterBinders, affectedTableNames ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java index 8de288c0e..0d81a2f8b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java @@ -14,7 +14,6 @@ import org.hibernate.HibernateException; import org.hibernate.JDBCException; import org.hibernate.LockOptions; -import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; @@ -24,7 +23,6 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.pool.ReactiveConnection; -import org.hibernate.reactive.pool.impl.Parameters; import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.util.impl.CompletionStages; @@ -172,11 +170,6 @@ private CompletionStage executeQuery() { return completedFuture( logicalConnection ) .thenCompose( lg -> { LOG.tracef( "Executing query to retrieve ResultSet : %s", getFinalSql() ); - - Dialect dialect = executionContext.getSession().getJdbcServices().getDialect(); - // This must happen at the very last minute in order to process parameters - // added in org.hibernate.dialect.pagination.OffsetFetchLimitHandler.processSql - final String sql = Parameters.instance( dialect ).process( getFinalSql() ); Object[] parameters = PreparedStatementAdaptor.bind( super::bindParameters ); final SessionEventListenerManager eventListenerManager = executionContext @@ -186,7 +179,7 @@ private CompletionStage executeQuery() { eventListenerManager.jdbcExecuteStatementStart(); return connection() - .selectJdbc( sql, parameters ) + .selectJdbc( getFinalSql(), parameters ) .thenCompose( this::validateResultSet ) .whenComplete( (resultSet, throwable) -> { // FIXME: I don't know if this event makes sense for Vert.x From 973f80f899c605108075cb52b3b964ac47a341ff Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 21 Aug 2025 18:01:27 +0200 Subject: [PATCH 104/185] [#2464] Remove unused classes and related tests --- .../reactive/pool/impl/OracleParameters.java | 15 -- .../reactive/pool/impl/Parameters.java | 153 ------------------ .../pool/impl/PostgresParameters.java | 15 -- .../pool/impl/SQLServerParameters.java | 15 -- .../reactive/ParametersProcessorTest.java | 67 -------- 5 files changed, 265 deletions(-) delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/OracleParameters.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/Parameters.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/PostgresParameters.java delete mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SQLServerParameters.java delete mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/ParametersProcessorTest.java diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/OracleParameters.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/OracleParameters.java deleted file mode 100644 index 2f4dd34c1..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/OracleParameters.java +++ /dev/null @@ -1,15 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.pool.impl; - -public class OracleParameters extends Parameters { - - public static final OracleParameters INSTANCE = new OracleParameters(); - - private OracleParameters() { - super( ":" ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/Parameters.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/Parameters.java deleted file mode 100644 index d30826846..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/Parameters.java +++ /dev/null @@ -1,153 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.pool.impl; - -import java.util.function.IntConsumer; - -import org.hibernate.dialect.CockroachDialect; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.PostgreSQLDialect; -import org.hibernate.dialect.SQLServerDialect; - -/** - * Some databases have a different parameter syntax, which - * the Vert.x {@link io.vertx.sqlclient.SqlClient} does not abstract. - * This class converts JDBC/ODBC-style {@code ?} parameters generated - * by Hibernate ORM to the native format. - */ -public abstract class Parameters { - - private final String paramPrefix; - - private static final Parameters NO_PARSING = new Parameters( null ) { - @Override - public String process(String sql) { - return sql; - } - - @Override - public String process(String sql, int parameterCount) { - return sql; - } - }; - - protected Parameters(String paramPrefix) { - this.paramPrefix = paramPrefix; - } - - public static Parameters instance(Dialect dialect) { - if ( dialect instanceof PostgreSQLDialect || dialect instanceof CockroachDialect ) { - return PostgresParameters.INSTANCE; - } - if ( dialect instanceof SQLServerDialect ) { - return SQLServerParameters.INSTANCE; - } - return NO_PARSING; - } - - public static boolean isProcessingNotRequired(String sql) { - return sql == null - // There aren't any parameters - || sql.indexOf( '?' ) == -1; - } - - public String process(String sql) { - if ( isProcessingNotRequired( sql ) ) { - return sql; - } - return new Parser( sql, paramPrefix ).result(); - } - - /** - * Replace all JDBC-style {@code ?} parameters with Postgres-style - * {@code $n} parameters in the given SQL string. - */ - public String process(String sql, int parameterCount) { - if ( isProcessingNotRequired( sql ) ) { - return sql; - } - return new Parser( sql, parameterCount, paramPrefix ).result(); - } - - private static class Parser { - - private boolean inString; - private boolean inQuoted; - private boolean inSqlComment; - private boolean inCComment; - private boolean escaped; - private int count = 0; - private StringBuilder result; - private int previous; - - private Parser(String sql, String paramPrefix) { - this( sql, 10, paramPrefix ); - } - - private Parser(String sql, int parameterCount, final String paramPrefix) { - result = new StringBuilder( sql.length() + parameterCount ); - // We aren't using lambdas or method reference because of a bug in the JVM: - // https://bugs.openjdk.java.net/browse/JDK-8161588 - // Please, don't change this unless you've tested it with Quarkus - sql.codePoints().forEach( new IntConsumer() { - @Override - public void accept(int codePoint) { - if ( escaped ) { - escaped = false; - } - else { - switch ( codePoint ) { - case '\\': - escaped = true; - return; - case '"': - if ( !inString && !inSqlComment && !inCComment ) { - inQuoted = !inQuoted; - } - break; - case '\'': - if ( !inQuoted && !inSqlComment && !inCComment ) { - inString = !inString; - } - break; - case '-': - if ( !inQuoted && !inString && !inCComment && previous == '-' ) { - inSqlComment = true; - } - break; - case '\n': - inSqlComment = false; - break; - case '*': - if ( !inQuoted && !inString && !inSqlComment && previous == '/' ) { - inCComment = true; - } - break; - case '/': - if ( previous == '*' ) { - inCComment = false; - } - break; - //TODO: $$-quoted strings - case '?': - if ( !inQuoted && !inString ) { - result.append( paramPrefix ).append( ++count ); - previous = '?'; - return; - } - } - } - previous = codePoint; - result.appendCodePoint( codePoint ); - } - } ); - } - - public String result() { - return result.toString(); - } - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/PostgresParameters.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/PostgresParameters.java deleted file mode 100644 index b53e37839..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/PostgresParameters.java +++ /dev/null @@ -1,15 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.pool.impl; - -public class PostgresParameters extends Parameters { - - public static final PostgresParameters INSTANCE = new PostgresParameters(); - - private PostgresParameters() { - super( "$" ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SQLServerParameters.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SQLServerParameters.java deleted file mode 100644 index 232c001c1..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SQLServerParameters.java +++ /dev/null @@ -1,15 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.pool.impl; - -public class SQLServerParameters extends Parameters { - - public static final SQLServerParameters INSTANCE = new SQLServerParameters(); - - private SQLServerParameters() { - super( "@P" ); - } -} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ParametersProcessorTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ParametersProcessorTest.java deleted file mode 100644 index ed4b3323c..000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ParametersProcessorTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive; - -import java.util.stream.Stream; - -import org.hibernate.reactive.pool.impl.OracleParameters; -import org.hibernate.reactive.pool.impl.PostgresParameters; -import org.hibernate.reactive.pool.impl.SQLServerParameters; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -/** - * Test the {@link org.hibernate.reactive.pool.impl.Parameters} processor for each database - */ -public class ParametersProcessorTest { - - /** - * Each test will replace this placeholder with the correct parameter prefix for the selected database - */ - private static final String PARAM_PREFIX = "__paramPrefix__"; - - - /** - * Return the strings to process and the expected result for each one - */ - static Stream expectations() { - return Stream.of( - arguments( "/* One comment */ \\?", "/* One comment */ ?" ), - arguments( "/* One comment */ ?", "/* One comment */ " + PARAM_PREFIX + "1" ), - arguments( "'Sql text ?'", "'Sql text ?'" ), - arguments( "\\?", "?" ), - arguments( "???", PARAM_PREFIX + "1" + PARAM_PREFIX + "2" + PARAM_PREFIX + "3" ), - arguments( "\\?|?", "?|" + PARAM_PREFIX + "1" ), - arguments( " ? ", " " + PARAM_PREFIX + "1 " ) - ); - } - - @ParameterizedTest - @MethodSource("expectations") - public void testPostgreSQLProcessing(String unprocessed, String expected) { - assertThat( PostgresParameters.INSTANCE.process( unprocessed ) ) - .isEqualTo( expected.replaceAll( PARAM_PREFIX, "\\$" ) ); - } - - @ParameterizedTest - @MethodSource("expectations") - public void testSqlServerProcessing(String unprocessed, String expected) { - assertThat( SQLServerParameters.INSTANCE.process( unprocessed ) ) - .isEqualTo( expected.replaceAll( PARAM_PREFIX, "@P" ) ); - } - - @ParameterizedTest - @MethodSource("expectations") - public void testOracleProcessing(String unprocessed, String expected) { - assertThat( OracleParameters.INSTANCE.process( unprocessed ) ) - .isEqualTo( expected.replaceAll( PARAM_PREFIX, ":" ) ); - } -} From 62cd92ed0148588ebffff82c3b2297c88cbc08bb Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 25 Aug 2025 12:02:31 +0200 Subject: [PATCH 105/185] [#2012] Refactoring and clean up JsonTypeTest --- .../reactive/types/JsonTypeTest.java | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonTypeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonTypeTest.java index 30c5a8512..aaca7f438 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonTypeTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonTypeTest.java @@ -8,7 +8,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.concurrent.CompletionStage; import java.util.function.Consumer; import org.hibernate.reactive.BaseReactiveTest; @@ -26,19 +25,17 @@ import jakarta.persistence.Version; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.SQLSERVER; -import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; /** * Test types that we expect to work only on selected DBs. */ @Timeout(value = 10, timeUnit = MINUTES) - @DisabledFor(value = DB2, reason = "java.sql.SQLException: The object 'HREACT.JSONENTITY' provided is not defined, SQLCODE=-204 SQLSTATE=42704") @DisabledFor(value = SQLSERVER, reason = "java.lang.IllegalArgumentException: Unsupported value class: class io.vertx.core.json.JsonObject") @DisabledFor(value = ORACLE, reason = "java.sql.SQLException: ORA-17004: Invalid column type: https://docs.oracle.com/error-help/db/ora-17004/") @@ -50,15 +47,6 @@ protected Collection> annotatedEntities() { return List.of( Basic.class ); } - @Override - public CompletionStage deleteEntities(Class... types) { - return getSessionFactory() - .withTransaction( s -> loop( types, entityClass -> s - .createSelectionQuery( "from JsonEntity", entityClass ) - .getResultList() - .thenCompose( list -> loop( list, entity -> s.remove( entity ) ) ) ) ); - } - @Test public void testJsonType(VertxTestContext context) { Basic basic = new Basic(); @@ -79,16 +67,15 @@ public void testNullJsonType(VertxTestContext context) { * Persist the entity, find it and execute the assertions */ private void testField(VertxTestContext context, Basic original, Consumer consumer) { - test( - context, - getSessionFactory().withTransaction( (s, t) -> s.persist( original ) ) - .thenCompose( v -> openSession() ) - .thenCompose( s2 -> s2.find( Basic.class, original.id ) - .thenAccept( found -> { - assertNotNull( found ); - assertEquals( original, found ); - consumer.accept( found ); - } ) ) + test( context, getSessionFactory() + .withTransaction( s -> s.persist( original ) ) + .thenCompose( v -> getSessionFactory().withTransaction( s -> s + .find( Basic.class, original.id ) ) + ) + .thenAccept( found -> { + assertThat( found ).isEqualTo( original ); + consumer.accept( found ); + } ) ); } From 1754f2ebd0fd903c3eec072ef9d49ddfa7656eab Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 25 Aug 2025 11:56:52 +0200 Subject: [PATCH 106/185] [#2012] Add test for '?' PostgreSQL operator The issue was actually fixed when we removed parameters processing (see #2464). Now the `?` operator can be escaped with `\\?` (instead of `\\\\\\?`). --- hibernate-reactive-core/build.gradle | 3 ++ .../reactive/types/JsonTypeTest.java | 41 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index 1a6ad9efe..2a18518f0 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -35,6 +35,9 @@ dependencies { testImplementation(libs.io.vertx.vertx.mssql.client) testImplementation(libs.io.vertx.vertx.oracle.client) + // Some tests using JSON need a formatter + testRuntimeOnly(libs.com.fasterxml.jackson.core.jackson.databind) + // Metrics testImplementation(libs.io.vertx.vertx.micrometer.metrics) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonTypeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonTypeTest.java index aaca7f438..2a2d0bd2d 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonTypeTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/JsonTypeTest.java @@ -7,11 +7,15 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.function.Consumer; +import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.reactive.BaseReactiveTest; import org.hibernate.reactive.annotations.DisabledFor; +import org.hibernate.reactive.annotations.EnabledFor; +import org.hibernate.type.SqlTypes; import org.junit.jupiter.api.Test; @@ -29,6 +33,7 @@ import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.SQLSERVER; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -47,6 +52,39 @@ protected Collection> annotatedEntities() { return List.of( Basic.class ); } + @Test + @EnabledFor(POSTGRESQL) + public void nativeQuestionMarkOperatorForPostgres(VertxTestContext context) { + Basic basic = new Basic(); + basic.jsonAsMap = Map.of( "sport", "Cheese Rolling" ); + + test( context, getMutinySessionFactory() + .withTransaction( s -> s.persist( basic ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s + .createNativeQuery( "select id from JsonEntity where jsonAsMap -> 'sport' \\? 'Cheese Rolling'" ) + .getSingleResult() ) + ) + .invoke( result -> assertThat( result ).isEqualTo( basic.id ) ) + ); + } + + @Test + @EnabledFor(POSTGRESQL) + public void nativeQuestionMarkOperatorWithParameterForPostgres(VertxTestContext context) { + Basic basic = new Basic(); + basic.jsonAsMap = Map.of( "sport", "Chess boxing" ); + + test( context, getMutinySessionFactory() + .withTransaction( s -> s.persist( basic ) ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s + .createNativeQuery( "select id from JsonEntity where jsonAsMap -> 'sport' \\? ?" ) + .setParameter( 1, "Chess boxing" ) + .getSingleResult() ) + ) + .invoke( result -> assertThat( result ).isEqualTo( basic.id ) ) + ); + } + @Test public void testJsonType(VertxTestContext context) { Basic basic = new Basic(); @@ -92,6 +130,9 @@ private static class Basic { private JsonObject jsonObj; + @JdbcTypeCode(SqlTypes.JSON) + Map jsonAsMap; + public Basic() { } From 11e0182a9881068dda2578d2e9dcf0226d1b3dbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 22:02:23 +0000 Subject: [PATCH 107/185] Bump org.asciidoctor.jvm.convert from 4.0.4 to 4.0.5 Bumps org.asciidoctor.jvm.convert from 4.0.4 to 4.0.5. --- updated-dependencies: - dependency-name: org.asciidoctor.jvm.convert dependency-version: 4.0.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 42bd42ea0..0f77854c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -56,5 +56,5 @@ org-testcontainers-postgresql = { group = "org.testcontainers", name = "postgres [plugins] com-diffplug-spotless = { id = "com.diffplug.spotless", version = "7.2.1" } -org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.4" } +org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.5" } org-hibernate-orm = { id = "org.hibernate.orm", version.ref = "hibernateOrmGradlePluginVersion" } From b42a031c15e63f32830065cd14876f0f716e51a5 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 31 Aug 2025 01:02:13 +0000 Subject: [PATCH 108/185] Update project version to : `3.1.3.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 315b96f0e..f3dcdd7cf 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.3-SNAPSHOT \ No newline at end of file +projectVersion=3.1.3.Final \ No newline at end of file From 419a74fc7548d16ac40d1adc8a5732899f4a8704 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 31 Aug 2025 01:03:11 +0000 Subject: [PATCH 109/185] Update project version to : `3.1.4-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index f3dcdd7cf..a1adf688d 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.3.Final \ No newline at end of file +projectVersion=3.1.4-SNAPSHOT \ No newline at end of file From 6e85b638adeea8f498e3775787c9bc53a9bf3596 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 22:30:15 +0000 Subject: [PATCH 110/185] Bump org.ehcache:ehcache from 3.11.0 to 3.11.1 Bumps [org.ehcache:ehcache](https://github.com/ehcache/ehcache3) from 3.11.0 to 3.11.1. - [Release notes](https://github.com/ehcache/ehcache3/releases) - [Commits](https://github.com/ehcache/ehcache3/compare/v3.11.0...v3.11.1) --- updated-dependencies: - dependency-name: org.ehcache:ehcache dependency-version: 3.11.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0f77854c5..73076b232 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,7 +32,7 @@ io-vertx-vertx-web = { group = "io.vertx", name = "vertx-web", version.ref = "ve io-vertx-vertx-web-client = { group = "io.vertx", name = "vertx-web-client", version.ref = "vertxWebClientVersion" } org-apache-logging-log4j-log4j-core = { group = "org.apache.logging.log4j", name = "log4j-core", version.ref = "log4jVersion" } org-assertj-assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertjVersion" } -org-ehcache-ehcache = { group = "org.ehcache", name = "ehcache", version = "3.11.0" } +org-ehcache-ehcache = { group = "org.ehcache", name = "ehcache", version = "3.11.1" } org-glassfish-expressly-expressly = { group = "org.glassfish.expressly", name = "expressly", version = "5.0.0" } org-hibernate-orm-hibernate-core = { group = "org.hibernate.orm", name = "hibernate-core", version.ref = "hibernateOrmVersion" } org-hibernate-orm-hibernate-jcache = { group = "org.hibernate.orm", name = "hibernate-jcache", version.ref = "hibernateOrmVersion" } From cb3c0b03f989ae45589770a8a2a8ac7c9db9246e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 22:44:28 +0000 Subject: [PATCH 111/185] Bump com.fasterxml.jackson.core:jackson-databind Bumps the testcontainers group with 1 update: [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml.jackson.core:jackson-databind` from 2.19.2 to 2.20.0 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.20.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73076b232..a22de09b1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ assertjVersion = "3.27.4" hibernateOrmVersion = "7.1.0.Final" hibernateOrmGradlePluginVersion = "7.1.0.Final" -jacksonDatabindVersion = "2.19.2" +jacksonDatabindVersion = "2.20.0" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" junitVersion = "5.13.4" From 87e6c8e66bfbbfc626f853c35627ecdd240d73d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 03:46:18 +0000 Subject: [PATCH 112/185] Bump cockroachdb/cockroach from v25.3.0 to v25.3.1 in /tooling/docker Bumps cockroachdb/cockroach from v25.3.0 to v25.3.1. --- updated-dependencies: - dependency-name: cockroachdb/cockroach dependency-version: v25.3.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- tooling/docker/cockroachdb.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile index 5f660daf4..3bd8d82ab 100644 --- a/tooling/docker/cockroachdb.Dockerfile +++ b/tooling/docker/cockroachdb.Dockerfile @@ -1,3 +1,3 @@ # CockroachDB # See https://hub.docker.com/r/cockroachdb/cockroach -FROM docker.io/cockroachdb/cockroach:v25.3.0 +FROM docker.io/cockroachdb/cockroach:v25.3.1 From 7e745c59040e549a2a04ce27210505069f2abf71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 03:06:59 +0000 Subject: [PATCH 113/185] Bump the vertx group with 10 updates Bumps the vertx group with 10 updates: | Package | From | To | | --- | --- | --- | | [io.vertx:vertx-db2-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.18` | `4.5.20` | | [io.vertx:vertx-junit5](https://github.com/eclipse-vertx/vertx-junit5) | `4.5.18` | `4.5.20` | | [io.vertx:vertx-micrometer-metrics](https://github.com/vert-x3/vertx-micrometer-metrics) | `4.5.18` | `4.5.20` | | [io.vertx:vertx-mssql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.18` | `4.5.20` | | [io.vertx:vertx-mysql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.18` | `4.5.20` | | [io.vertx:vertx-oracle-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.18` | `4.5.20` | | [io.vertx:vertx-pg-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.18` | `4.5.20` | | [io.vertx:vertx-sql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.18` | `4.5.20` | | [io.vertx:vertx-web](https://github.com/vert-x3/vertx-web) | `4.5.18` | `4.5.20` | | [io.vertx:vertx-web-client](https://github.com/vert-x3/vertx-web) | `4.5.18` | `4.5.20` | Updates `io.vertx:vertx-db2-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-junit5` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-junit5/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-micrometer-metrics` from 4.5.18 to 4.5.20 - [Commits](https://github.com/vert-x3/vertx-micrometer-metrics/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-mssql-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-mysql-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-oracle-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-pg-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-sql-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-junit5` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-junit5/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-micrometer-metrics` from 4.5.18 to 4.5.20 - [Commits](https://github.com/vert-x3/vertx-micrometer-metrics/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-mssql-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-mysql-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-oracle-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-pg-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-sql-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-web` from 4.5.18 to 4.5.20 - [Commits](https://github.com/vert-x3/vertx-web/compare/4.5.18...4.5.20) Updates `io.vertx:vertx-web-client` from 4.5.18 to 4.5.20 - [Commits](https://github.com/vert-x3/vertx-web/compare/4.5.18...4.5.20) --- updated-dependencies: - dependency-name: io.vertx:vertx-db2-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-junit5 dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-micrometer-metrics dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mssql-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mysql-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-oracle-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-pg-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-sql-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-junit5 dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-micrometer-metrics dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mssql-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mysql-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-oracle-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-pg-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-sql-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-web dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-web-client dependency-version: 4.5.20 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a22de09b1..0d91ec0df 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,9 +9,9 @@ junitVersion = "5.13.4" junitPlatformVersion = "1.13.4" log4jVersion = "2.25.1" testcontainersVersion = "1.21.3" -vertxSqlClientVersion = "4.5.18" -vertxWebVersion= "4.5.18" -vertxWebClientVersion = "4.5.18" +vertxSqlClientVersion = "4.5.21" +vertxWebVersion= "4.5.21" +vertxWebClientVersion = "4.5.21" [libraries] com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } From 89a17f1b22bb1da3907a941ffdb0e7f156f03137 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 03:06:45 +0000 Subject: [PATCH 114/185] Bump the testcontainers group with 2 updates Bumps the testcontainers group with 2 updates: [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) and [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc). Updates `org.mariadb.jdbc:mariadb-java-client` from 3.5.5 to 3.5.6 - [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases) - [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/main/CHANGELOG.md) - [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.5.5...3.5.6) Updates `org.postgresql:postgresql` from 42.7.7 to 42.7.8 - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/compare/REL42.7.7...REL42.7.8) --- updated-dependencies: - dependency-name: org.mariadb.jdbc:mariadb-java-client dependency-version: 3.5.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers - dependency-name: org.postgresql:postgresql dependency-version: 42.7.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0d91ec0df..ccbf0acb7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,8 +44,8 @@ org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" } org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" } org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "1.13.4" } -org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.5" } -org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.7" } +org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.6" } +org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.8" } org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" } org-testcontainers-db2 = { group = "org.testcontainers", name = "db2", version.ref = "testcontainersVersion" } org-testcontainers-mariadb = { group = "org.testcontainers", name = "mariadb", version.ref = "testcontainersVersion" } From 094145e9708b02a55c1aaf1e849ea69db8926edb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 03:06:50 +0000 Subject: [PATCH 115/185] Bump org.assertj:assertj-core from 3.27.4 to 3.27.6 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.4 to 3.27.6. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.4...assertj-build-3.27.6) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-version: 3.27.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ccbf0acb7..ee7e98206 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -assertjVersion = "3.27.4" +assertjVersion = "3.27.6" hibernateOrmVersion = "7.1.0.Final" hibernateOrmGradlePluginVersion = "7.1.0.Final" jacksonDatabindVersion = "2.20.0" From 7caf16876db22d05d1d99eb4b87a0f94e2187cd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 03:06:56 +0000 Subject: [PATCH 116/185] Bump org.apache.logging.log4j:log4j-core from 2.25.1 to 2.25.2 Bumps org.apache.logging.log4j:log4j-core from 2.25.1 to 2.25.2. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ee7e98206..39782704e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" junitVersion = "5.13.4" junitPlatformVersion = "1.13.4" -log4jVersion = "2.25.1" +log4jVersion = "2.25.2" testcontainersVersion = "1.21.3" vertxSqlClientVersion = "4.5.21" vertxWebVersion= "4.5.21" From ef31dca2a547bda4351284fea5164ec730372230 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Fri, 19 Sep 2025 13:57:22 +0200 Subject: [PATCH 117/185] [#2516] Use GA version of JDK 25 in the build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e81eae258..fd7ea4625 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -191,7 +191,7 @@ jobs: - { name: "20", java_version_numeric: 20, jvm_args: '--enable-preview' } - { name: "21", java_version_numeric: 21, jvm_args: '--enable-preview' } - { name: "24", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' } - - { name: "25-ea", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' } + - { name: "25", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' } - { name: "26-ea", java_version_numeric: 26, from: 'jdk.java.net', jvm_args: '--enable-preview' } steps: - name: Checkout ${{ inputs.branch }} From 0bc070847bc24b0da78a7be94fff3ffcbf616fe0 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Fri, 19 Sep 2025 14:40:05 +0200 Subject: [PATCH 118/185] [#2516] Use temurin distribution for JDK 24 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd7ea4625..7e3559931 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -190,7 +190,7 @@ jobs: # and it's useful to test that. - { name: "20", java_version_numeric: 20, jvm_args: '--enable-preview' } - { name: "21", java_version_numeric: 21, jvm_args: '--enable-preview' } - - { name: "24", java_version_numeric: 24, from: 'jdk.java.net', jvm_args: '--enable-preview' } + - { name: "24", java_version_numeric: 24, jvm_args: '--enable-preview' } - { name: "25", java_version_numeric: 25, from: 'jdk.java.net', jvm_args: '--enable-preview' } - { name: "26-ea", java_version_numeric: 26, from: 'jdk.java.net', jvm_args: '--enable-preview' } steps: From ab8e5f7c4657ece9a60615a2aaf045a50d44e117 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 26 Sep 2025 11:54:19 +0200 Subject: [PATCH 119/185] [#2535] Fix operations order in BatchingConnection --- .../java/org/hibernate/reactive/pool/BatchingConnection.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java index ce7ee013d..557ec944b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java @@ -104,9 +104,8 @@ public CompletionStage update( return voidFuture(); } else { - CompletionStage lastBatch = executeBatch(); - newBatch( sql, paramValues, expectation ); - return lastBatch; + return executeBatch() + .thenAccept( v -> newBatch( sql, paramValues, expectation ) ); } } } From 52e75d93bd675b85fda6a746ba841f653fe67088 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 28 Sep 2025 01:02:23 +0000 Subject: [PATCH 120/185] Update project version to : `3.1.4.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index a1adf688d..350589424 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.4-SNAPSHOT \ No newline at end of file +projectVersion=3.1.4.Final \ No newline at end of file From 3fc2fe7ea66312dd5795cbeed0f6e4bae195ccd7 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 28 Sep 2025 01:03:25 +0000 Subject: [PATCH 121/185] Update project version to : `3.1.5-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 350589424..aec81340f 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.4.Final \ No newline at end of file +projectVersion=3.1.5-SNAPSHOT \ No newline at end of file From f9f6100fe0d1c8fb327a6f9ff9a62218f80e342f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 03:59:11 +0000 Subject: [PATCH 122/185] Bump postgres from 17.6 to 18.0 in /tooling/docker Bumps postgres from 17.6 to 18.0. --- updated-dependencies: - dependency-name: postgres dependency-version: '18.0' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- tooling/docker/postgresql.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/postgresql.Dockerfile b/tooling/docker/postgresql.Dockerfile index d245286dc..f2daff3c7 100644 --- a/tooling/docker/postgresql.Dockerfile +++ b/tooling/docker/postgresql.Dockerfile @@ -1,3 +1,3 @@ # PostgreSQL # See https://hub.docker.com/_/postgres -FROM docker.io/postgres:17.6 +FROM docker.io/postgres:18.0 From 3a217ce9fb019698c3dd77c65201bd0f1fdaf95e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 03:57:55 +0000 Subject: [PATCH 123/185] Bump cockroachdb/cockroach from v25.3.1 to v25.3.2 in /tooling/docker Bumps cockroachdb/cockroach from v25.3.1 to v25.3.2. --- updated-dependencies: - dependency-name: cockroachdb/cockroach dependency-version: v25.3.2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- tooling/docker/cockroachdb.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile index 3bd8d82ab..484d65520 100644 --- a/tooling/docker/cockroachdb.Dockerfile +++ b/tooling/docker/cockroachdb.Dockerfile @@ -1,3 +1,3 @@ # CockroachDB # See https://hub.docker.com/r/cockroachdb/cockroach -FROM docker.io/cockroachdb/cockroach:v25.3.1 +FROM docker.io/cockroachdb/cockroach:v25.3.2 From 2dedc5c21fee715416e3aea2526fd6629bc35379 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 03:26:25 +0000 Subject: [PATCH 124/185] Bump the hibernate group with 4 updates Bumps the hibernate group with 4 updates: [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm), [org.hibernate.orm:hibernate-jcache](https://github.com/hibernate/hibernate-orm), [org.hibernate.orm:hibernate-jpamodelgen](https://github.com/hibernate/hibernate-orm) and org.hibernate.orm. Updates `org.hibernate.orm:hibernate-core` from 7.1.0.Final to 7.1.2.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.2/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.0...7.1.2) Updates `org.hibernate.orm:hibernate-jcache` from 7.1.0.Final to 7.1.2.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.2/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.0...7.1.2) Updates `org.hibernate.orm:hibernate-jpamodelgen` from 7.1.0.Final to 7.1.2.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.2/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.0...7.1.2) Updates `org.hibernate.orm:hibernate-jcache` from 7.1.0.Final to 7.1.2.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.2/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.0...7.1.2) Updates `org.hibernate.orm:hibernate-jpamodelgen` from 7.1.0.Final to 7.1.2.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.2/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.0...7.1.2) Updates `org.hibernate.orm` from 7.1.0.Final to 7.1.2.Final --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 7.1.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jcache dependency-version: 7.1.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jpamodelgen dependency-version: 7.1.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jcache dependency-version: 7.1.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jpamodelgen dependency-version: 7.1.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm dependency-version: 7.1.2.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39782704e..d01c229e0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.6" -hibernateOrmVersion = "7.1.0.Final" -hibernateOrmGradlePluginVersion = "7.1.0.Final" +hibernateOrmVersion = "7.1.2.Final" +hibernateOrmGradlePluginVersion = "7.1.2.Final" jacksonDatabindVersion = "2.20.0" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" From 9daf7d4e4fbcddcc7a5ba1cbded3d056dddb288e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 03:28:41 +0000 Subject: [PATCH 125/185] Bump com.diffplug.spotless from 7.2.1 to 8.0.0 Bumps com.diffplug.spotless from 7.2.1 to 8.0.0. --- updated-dependencies: - dependency-name: com.diffplug.spotless dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d01c229e0..70bcf7f47 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,6 +55,6 @@ org-testcontainers-oracle-xe = { group = "org.testcontainers", name = "oracle-xe org-testcontainers-postgresql = { group = "org.testcontainers", name = "postgresql", version.ref = "testcontainersVersion" } [plugins] -com-diffplug-spotless = { id = "com.diffplug.spotless", version = "7.2.1" } +com-diffplug-spotless = { id = "com.diffplug.spotless", version = "8.0.0" } org-asciidoctor-jvm-convert = { id = "org.asciidoctor.jvm.convert", version = "4.0.5" } org-hibernate-orm = { id = "org.hibernate.orm", version.ref = "hibernateOrmGradlePluginVersion" } From f72f2c4fd54a5d5dc15e0c123868628ce4907480 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 29 Sep 2025 18:04:58 +0200 Subject: [PATCH 126/185] [#2534] session.find() will not respect NOWAIT locks starting from HR 3.1 --- .../mutiny/impl/MutinySessionImpl.java | 10 +++--- .../reactive/session/ReactiveSession.java | 17 ++++++++++ .../session/impl/ReactiveSessionImpl.java | 34 ++++++++++++++++++- .../reactive/stage/impl/StageSessionImpl.java | 10 +++--- 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java index c80be96ed..5f4ea40f4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java @@ -221,7 +221,7 @@ public SelectionQuery createNativeQuery(String queryString, ResultSetMapp @Override public Uni find(Class entityClass, Object primaryKey) { - return uni( () -> delegate.reactiveFind( entityClass, primaryKey, null, null ) ); + return uni( () -> delegate.reactiveFind( entityClass, primaryKey ) ); } @Override @@ -236,7 +236,7 @@ public Uni find(Class entityClass, Identifier id) { @Override public Uni find(Class entityClass, Object primaryKey, LockMode lockMode) { - return uni( () -> delegate.reactiveFind( entityClass, primaryKey, new LockOptions( lockMode ), null ) ); + return uni( () -> delegate.reactiveFind( entityClass, primaryKey, lockMode, null ) ); } @Override @@ -252,7 +252,7 @@ public Uni find(Class entityClass, Object primaryKey, LockOptions lock @Override public Uni find(EntityGraph entityGraph, Object id) { Class entityClass = ( (RootGraph) entityGraph ).getGraphedType().getJavaType(); - return uni( () -> delegate.reactiveFind( entityClass, id, null, entityGraph ) ); + return uni( () -> delegate.reactiveFind( entityClass, id, (LockMode) null, entityGraph ) ); } @Override @@ -297,7 +297,7 @@ public Uni refresh(Object entity) { @Override public Uni refresh(Object entity, LockMode lockMode) { - return uni( () -> delegate.reactiveRefresh( entity, new LockOptions( lockMode ) ) ); + return uni( () -> delegate.reactiveRefresh( entity, lockMode ) ); } @Override @@ -317,7 +317,7 @@ public Uni refreshAll(Object... entity) { @Override public Uni lock(Object entity, LockMode lockMode) { - return uni( () -> delegate.reactiveLock( entity, new LockOptions( lockMode ) ) ); + return uni( () -> delegate.reactiveLock( entity, lockMode ) ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java index ab2089c0d..db141aadd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveSession.java @@ -75,16 +75,33 @@ public interface ReactiveSession extends ReactiveQueryProducer, ReactiveSharedSe CompletionStage reactiveRefresh(Object entity, LockOptions lockMode); + default CompletionStage reactiveRefresh(Object entity, LockMode lockMode) { + return reactiveRefresh( entity, new LockOptions( lockMode ) ); + } + CompletionStage reactiveRefresh(Object child, RefreshContext refreshedAlready); CompletionStage reactiveLock(Object entity, LockOptions lockMode); + default CompletionStage reactiveLock(Object entity, LockMode lockMode){ + return reactiveLock( entity, new LockOptions( lockMode ) ); + } + CompletionStage reactiveLock(String entityName, Object entity, LockOptions lockMode); CompletionStage reactiveGet(Class entityClass, Object id); CompletionStage reactiveFind(Class entityClass, Object id, LockOptions lockOptions, EntityGraph fetchGraph); + default CompletionStage reactiveFind(Class entityClass, Object id){ + return reactiveFind( entityClass, id, (LockOptions) null, null ); + } + + default CompletionStage reactiveFind(Class entityClass, Object id, LockMode lockMode, EntityGraph fetchGraph ){ + return reactiveFind( entityClass, id, new LockOptions( lockMode ), fetchGraph ); + } + + CompletionStage> reactiveFind(Class entityClass, Object... ids); CompletionStage reactiveFind(Class entityClass, Map naturalIds); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 7b0bd112a..b6f795a1e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -1133,6 +1133,11 @@ public CompletionStage reactiveRefresh(Object entity, LockOptions lockOpti return fireRefresh( new RefreshEvent( entity, lockOptions, this ) ); } + @Override + public CompletionStage reactiveRefresh(Object entity, LockMode lockMode) { + return reactiveRefresh( entity, toLockOptions( lockMode ) ); + } + @Override public CompletionStage reactiveRefresh(Object object, RefreshContext refreshedAlready) { checkOpenOrWaitingForAutoClose(); @@ -1193,6 +1198,11 @@ public CompletionStage reactiveLock(Object object, LockOptions lockOptions return fireLock( new LockEvent( object, lockOptions, this ) ); } + @Override + public CompletionStage reactiveLock(Object entity, LockMode lockMode) { + return reactiveLock( entity, toLockOptions( lockMode ) ); + } + @Override public CompletionStage reactiveLock(String entityName, Object object, LockOptions lockOptions) { checkOpen(); @@ -1254,6 +1264,11 @@ public CompletionStage reactiveFind( } ); } + @Override + public CompletionStage reactiveFind(Class entityClass, Object id, LockMode lockMode, EntityGraph fetchGraph){ + return reactiveFind( entityClass, id, toLockOptions( lockMode ), fetchGraph ); + } + private CompletionStage handleReactiveFindException( Class entityClass, Object primaryKey, @@ -1318,7 +1333,7 @@ public CompletionStage reactiveFind(Class entityClass, Map( this, persister, requireEntityPersister( entityClass ) ) .resolveNaturalId( normalizedIdValues ) - .thenCompose( id -> reactiveFind( entityClass, id, null, null ) ); + .thenCompose( id -> reactiveFind( entityClass, id ) ); } private ReactiveEntityPersister entityPersister(Class entityClass) { @@ -1811,4 +1826,21 @@ public RootGraphImplementor getEntityGraph(Class entity, String name) } return (RootGraphImplementor) entityGraph; } + + /** + * Convert a {@link LockMode} into a {@link LockOptions} object. + *

+ * We need to make sure that we use the method {@link LockOptions#setLockMode(LockMode)} for the conversion + * because it also set a {@link LockOptions#timeout} that will affect the way SQL queries are generated. + * There's also the constructor {@link LockOptions#LockOptions(LockMode)}, but it doesn't set a time-out + * causing some generated SQL queries to not have the expected syntax (for example, it won't apply + * the "nowait" clause in PostgreSQL, even if set to {@link LockMode#UPGRADE_NOWAIT} ). + *

+ * @see Hibernate Reactive issue 2534 + */ + private static LockOptions toLockOptions(LockMode lockMode) { + return lockMode == null + ? null + : new LockOptions().setLockMode( lockMode ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java index da09a284b..daf467b2e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java @@ -135,7 +135,7 @@ public MutationQuery createMutationQuery(JpaCriteriaInsert insert) { @Override public CompletionStage find(Class entityClass, Object primaryKey) { - return delegate.reactiveFind( entityClass, primaryKey, null, null ); + return delegate.reactiveFind( entityClass, primaryKey ); } @Override @@ -150,7 +150,7 @@ public CompletionStage find(Class entityClass, Identifier id) { @Override public CompletionStage find(Class entityClass, Object primaryKey, LockMode lockMode) { - return delegate.reactiveFind( entityClass, primaryKey, new LockOptions( lockMode ), null ); + return delegate.reactiveFind( entityClass, primaryKey, lockMode, null ); } @Override @@ -167,7 +167,7 @@ public CompletionStage find(Class entityClass, Object primaryKey, Lock @Override public CompletionStage find(EntityGraph entityGraph, Object id) { Class entityClass = ( (RootGraph) entityGraph ).getGraphedType().getJavaType(); - return delegate.reactiveFind( entityClass, id, null, entityGraph ); + return delegate.reactiveFind( entityClass, id, (LockOptions) null, entityGraph ); } @Override @@ -212,7 +212,7 @@ public CompletionStage refresh(Object entity) { @Override public CompletionStage refresh(Object entity, LockMode lockMode) { - return delegate.reactiveRefresh( entity, new LockOptions(lockMode) ); + return delegate.reactiveRefresh( entity, lockMode ); } @Override @@ -232,7 +232,7 @@ public CompletionStage refresh(Object... entity) { @Override public CompletionStage lock(Object entity, LockMode lockMode) { - return delegate.reactiveLock( entity, new LockOptions(lockMode) ); + return delegate.reactiveLock( entity, lockMode ); } @Override From 966a99acd352a52372cd6a2e66ede2815c63ae14 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 29 Sep 2025 17:58:01 +0200 Subject: [PATCH 127/185] [#2534] Add test for session.find() will not respect NOWAIT locks starting from HR 3.1 --- .../reactive/FindByIdWithLockTest.java | 86 ++++++++++++++++--- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java index e78c549ca..51eece400 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java @@ -5,11 +5,17 @@ */ package org.hibernate.reactive; +import org.hibernate.LockMode; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.testing.SqlStatementTracker; + import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -22,11 +28,38 @@ import jakarta.persistence.OneToMany; import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; @Timeout(value = 10, timeUnit = TimeUnit.MINUTES) public class FindByIdWithLockTest extends BaseReactiveTest { private static final Long CHILD_ID = 1L; + private static SqlStatementTracker sqlTracker; + + @Override + protected Configuration constructConfiguration() { + Configuration configuration = super.constructConfiguration(); + + // Construct a tracker that collects query statements via the SqlStatementLogger framework. + // Pass in configuration properties to hand off any actual logging properties + sqlTracker = new SqlStatementTracker( FindByIdWithLockTest::selectQueryFilter, configuration.getProperties() ); + return configuration; + } + + @BeforeEach + public void clearTracker() { + sqlTracker.clear(); + } + + @Override + protected void addServices(StandardServiceRegistryBuilder builder) { + sqlTracker.registerService( builder ); + } + + private static boolean selectQueryFilter(String s) { + return s.toLowerCase().startsWith( "select " ); + } + @Override protected Collection> annotatedEntities() { return List.of( Parent.class, Child.class ); @@ -50,6 +83,44 @@ context, getMutinySessionFactory() ); } + @Test + public void testFindUpgradeNoWait(VertxTestContext context) { + Child child = new Child( CHILD_ID, "And" ); + test( + context, getMutinySessionFactory() + .withTransaction( session -> session.persistAll( child ) ) + .invoke( () -> sqlTracker.clear() ) + .chain( () -> getMutinySessionFactory().withTransaction( session -> session + .find( Child.class, CHILD_ID, LockMode.UPGRADE_NOWAIT ) + .invoke( c -> { + assertThat( c ).isNotNull(); + assertThat( c.getId() ).isEqualTo( CHILD_ID ); + String selectQuery = sqlTracker.getLoggedQueries().get( 0 ); + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 1 ); + assertThat( selectQuery ) + .matches( this::noWaitLockingPredicate, "SQL query with nowait lock for " + dbType().name() ); + } + ) ) ) + ); + } + + /** + * @return true if the query contains the expected nowait keyword for the selected database + */ + private boolean noWaitLockingPredicate(String selectQuery) { + return switch ( dbType() ) { + case POSTGRESQL -> selectQuery.endsWith( "for no key update of c1_0 nowait" ); + case COCKROACHDB -> selectQuery.endsWith( "for update of c1_0 nowait" ); + case SQLSERVER -> selectQuery.contains( "with (updlock,holdlock,rowlock,nowait)" ); + case ORACLE -> selectQuery.contains( "for update of c1_0.id nowait" ); + // DB2 does not support nowait + case DB2 -> selectQuery.contains( "for read only with rs use and keep update locks" ); + case MARIA -> selectQuery.contains( "for update nowait" ); + case MYSQL -> selectQuery.contains( "for update of c1_0 nowait" ); + default -> throw new IllegalArgumentException( "Database not recognized: " + dbType().name() ); + }; + } + @Entity(name = "Parent") public static class Parent { @@ -59,7 +130,7 @@ public static class Parent { private String name; @OneToMany(fetch = FetchType.EAGER) - public List children; + public List children = new ArrayList<>(); public Parent() { } @@ -70,9 +141,6 @@ public Parent(Long id, String name) { } public void add(Child child) { - if ( children == null ) { - children = new ArrayList<>(); - } children.add( child ); } @@ -89,7 +157,6 @@ public List getChildren() { } } - @Entity(name = "Child") public static class Child { @@ -109,13 +176,6 @@ public Child(Long id, String name) { this.name = name; } - public Child(Long id, String name, Parent parent) { - this.id = id; - this.name = name; - this.parent = parent; - parent.add( this ); - } - public Long getId() { return id; } @@ -128,6 +188,4 @@ public Parent getParent() { return parent; } } - - } From 7e80f923caa45e00a48d116819723881c7db0e1e Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 24 Sep 2025 11:52:57 +0200 Subject: [PATCH 128/185] [#2519] Migrate to release scripts for documentation publishing --- ci/release/Jenkinsfile | 2 +- release/build.gradle | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 590b1e47c..73948feab 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -211,7 +211,7 @@ pipeline { string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') ]) { - sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { + sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'jenkins.in.relation.to', 'hibernate-ci.frs.sourceforge.net']) { // performs documentation upload and Sonatype release // push to github withEnv([ diff --git a/release/build.gradle b/release/build.gradle index 690534750..00b43d2f4 100644 --- a/release/build.gradle +++ b/release/build.gradle @@ -153,11 +153,21 @@ def updateDocumentationTask = tasks.register( 'updateDocumentation' ) { into docWebsiteReactiveFolder.dir("javadocs") } + copy { + from documentationDir.dir("javadocs") + into rootProject.layout.buildDirectory.dir("staging-deploy/documentation/javadocs") + } + // Reference Documentation copy { from documentationDir.dir("asciidoc/reference/html_single") into docWebsiteReactiveFolder.dir("reference/html_single") - } + } + + copy { + from documentationDir.dir("asciidoc/reference/html_single") + into rootProject.layout.buildDirectory.dir("staging-deploy/documentation/reference/html_single") + } } } From 271ac5eb8ebe9a83649e3df1236e19bfe4f37d8c Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 5 Oct 2025 01:02:11 +0000 Subject: [PATCH 129/185] Update project version to : `3.1.5.Final` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index aec81340f..a90c6eb1e 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.5-SNAPSHOT \ No newline at end of file +projectVersion=3.1.5.Final \ No newline at end of file From 4a9d7a752154e12ff2ead6d20419014a28cad1b4 Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Sun, 5 Oct 2025 01:03:11 +0000 Subject: [PATCH 130/185] Update project version to : `3.1.6-SNAPSHOT` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index a90c6eb1e..8fbf4e5c6 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.5.Final \ No newline at end of file +projectVersion=3.1.6-SNAPSHOT \ No newline at end of file From 29a1f8fef622362c28972239f0ab45f9c7df70bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:14:55 +0000 Subject: [PATCH 131/185] Bump junitVersion from 5.13.4 to 6.0.0 Bumps `junitVersion` from 5.13.4 to 6.0.0. Updates `org.junit.jupiter:junit-jupiter-api` from 5.13.4 to 6.0.0 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.4...r6.0.0) Updates `org.junit.jupiter:junit-jupiter-engine` from 5.13.4 to 6.0.0 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.4...r6.0.0) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter-api dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: org.junit.jupiter:junit-jupiter-engine dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70bcf7f47..562647713 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ hibernateOrmGradlePluginVersion = "7.1.2.Final" jacksonDatabindVersion = "2.20.0" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" -junitVersion = "5.13.4" +junitVersion = "6.0.0" junitPlatformVersion = "1.13.4" log4jVersion = "2.25.2" testcontainersVersion = "1.21.3" From 4801ea97e90dfb64e8e4bb3bf40ca3b652d7bf4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:17:17 +0000 Subject: [PATCH 132/185] Bump org.junit.platform:junit-platform-launcher from 1.13.4 to 6.0.0 Bumps [org.junit.platform:junit-platform-launcher](https://github.com/junit-team/junit-framework) from 1.13.4 to 6.0.0. - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/commits/r6.0.0) --- updated-dependencies: - dependency-name: org.junit.platform:junit-platform-launcher dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 562647713..128f719a7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", nam org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" } org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" } org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" } -org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "1.13.4" } +org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "6.0.0" } org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.6" } org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.8" } org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" } From a9d2fb460226d274f7e77863ec12af30fca82831 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 15:10:19 +0000 Subject: [PATCH 133/185] Bump the hibernate group with 4 updates Bumps the hibernate group with 4 updates: [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm), [org.hibernate.orm:hibernate-jcache](https://github.com/hibernate/hibernate-orm), [org.hibernate.orm:hibernate-jpamodelgen](https://github.com/hibernate/hibernate-orm) and org.hibernate.orm. Updates `org.hibernate.orm:hibernate-core` from 7.1.2.Final to 7.1.3.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.3/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.2...7.1.3) Updates `org.hibernate.orm:hibernate-jcache` from 7.1.2.Final to 7.1.3.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.3/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.2...7.1.3) Updates `org.hibernate.orm:hibernate-jpamodelgen` from 7.1.2.Final to 7.1.3.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.3/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.2...7.1.3) Updates `org.hibernate.orm:hibernate-jcache` from 7.1.2.Final to 7.1.3.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.3/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.2...7.1.3) Updates `org.hibernate.orm:hibernate-jpamodelgen` from 7.1.2.Final to 7.1.3.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.3/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.2...7.1.3) Updates `org.hibernate.orm` from 7.1.2.Final to 7.1.3.Final --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 7.1.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jcache dependency-version: 7.1.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jpamodelgen dependency-version: 7.1.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jcache dependency-version: 7.1.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jpamodelgen dependency-version: 7.1.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm dependency-version: 7.1.3.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 128f719a7..2911d8d1c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.6" -hibernateOrmVersion = "7.1.2.Final" -hibernateOrmGradlePluginVersion = "7.1.2.Final" +hibernateOrmVersion = "7.1.3.Final" +hibernateOrmGradlePluginVersion = "7.1.3.Final" jacksonDatabindVersion = "2.20.0" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" From f83cea2559f3a899b573802de99bf6a549614013 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 03:05:31 +0000 Subject: [PATCH 134/185] Bump the hibernate group with 4 updates Bumps the hibernate group with 4 updates: [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm), [org.hibernate.orm:hibernate-jcache](https://github.com/hibernate/hibernate-orm), [org.hibernate.orm:hibernate-jpamodelgen](https://github.com/hibernate/hibernate-orm) and org.hibernate.orm. Updates `org.hibernate.orm:hibernate-core` from 7.1.3.Final to 7.1.4.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.4/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.3...7.1.4) Updates `org.hibernate.orm:hibernate-jcache` from 7.1.3.Final to 7.1.4.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.4/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.3...7.1.4) Updates `org.hibernate.orm:hibernate-jpamodelgen` from 7.1.3.Final to 7.1.4.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.4/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.3...7.1.4) Updates `org.hibernate.orm:hibernate-jcache` from 7.1.3.Final to 7.1.4.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.4/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.3...7.1.4) Updates `org.hibernate.orm:hibernate-jpamodelgen` from 7.1.3.Final to 7.1.4.Final - [Release notes](https://github.com/hibernate/hibernate-orm/releases) - [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.1.4/changelog.txt) - [Commits](https://github.com/hibernate/hibernate-orm/compare/7.1.3...7.1.4) Updates `org.hibernate.orm` from 7.1.3.Final to 7.1.4.Final --- updated-dependencies: - dependency-name: org.hibernate.orm:hibernate-core dependency-version: 7.1.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jcache dependency-version: 7.1.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jpamodelgen dependency-version: 7.1.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jcache dependency-version: 7.1.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm:hibernate-jpamodelgen dependency-version: 7.1.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate - dependency-name: org.hibernate.orm dependency-version: 7.1.4.Final dependency-type: direct:production update-type: version-update:semver-patch dependency-group: hibernate ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2911d8d1c..aaeeee198 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.6" -hibernateOrmVersion = "7.1.3.Final" -hibernateOrmGradlePluginVersion = "7.1.3.Final" +hibernateOrmVersion = "7.1.4.Final" +hibernateOrmGradlePluginVersion = "7.1.4.Final" jacksonDatabindVersion = "2.20.0" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" From 1bb2e1fbacc22bb445d044f0c73e2d9fce713b11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 03:05:55 +0000 Subject: [PATCH 135/185] Bump com.microsoft.sqlserver:mssql-jdbc in the testcontainers group Bumps the testcontainers group with 1 update: [com.microsoft.sqlserver:mssql-jdbc](https://github.com/Microsoft/mssql-jdbc). Updates `com.microsoft.sqlserver:mssql-jdbc` from 13.2.0.jre11 to 13.2.1.jre11 - [Release notes](https://github.com/Microsoft/mssql-jdbc/releases) - [Changelog](https://github.com/microsoft/mssql-jdbc/blob/main/CHANGELOG.md) - [Commits](https://github.com/Microsoft/mssql-jdbc/commits) --- updated-dependencies: - dependency-name: com.microsoft.sqlserver:mssql-jdbc dependency-version: 13.2.1.jre11 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aaeeee198..60a37d496 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ vertxWebClientVersion = "4.5.21" [libraries] com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" } -com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.2.0.jre11" } +com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.2.1.jre11" } com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.4.0" } com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" } io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.4" } From b826666dad0292995a628c40b33461487a5947d6 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 15 Sep 2025 14:54:00 +0200 Subject: [PATCH 136/185] [#1770] Fix better error message when transparently loading lazy fields --- gradle/libs.versions.toml | 1 + hibernate-reactive-core/build.gradle | 1 + .../spi/ReactiveBootstrapContextAdapter.java | 187 ++++++++++++++++++ ...EnhancementAsProxyLazinessInterceptor.java | 42 ++++ ...activeLazyAttributeLoadingInterceptor.java | 40 ++++ ...odeEnhancementMetadataPojoImplAdapter.java | 107 ++++++++++ .../hibernate/reactive/logging/impl/Log.java | 3 + ...activeEntityInstantiatorPojoOptimized.java | 42 ++++ ...eactiveEntityInstantiatorPojoStandard.java | 41 ++++ ...ityRepresentationStrategyPojoStandard.java | 47 +++++ .../ReactiveRuntimeModelCreationContext.java | 5 +- ...tiveManagedTypeRepresentationResolver.java | 40 ++++ .../tuple/entity/ReactiveEntityMetamodel.java | 16 ++ 13 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/boot/spi/ReactiveBootstrapContextAdapter.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/enhance/spi/interceptor/ReactiveEnhancementAsProxyLazinessInterceptor.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/enhance/spi/internal/ReactiveLazyAttributeLoadingInterceptor.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/spi/ReactiveBytecodeEnhancementMetadataPojoImplAdapter.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoOptimized.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoStandard.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityRepresentationStrategyPojoStandard.java create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/spi/ReactiveManagedTypeRepresentationResolver.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 60a37d496..9fd826897 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,6 +38,7 @@ org-hibernate-orm-hibernate-core = { group = "org.hibernate.orm", name = "hibern org-hibernate-orm-hibernate-jcache = { group = "org.hibernate.orm", name = "hibernate-jcache", version.ref = "hibernateOrmVersion" } org-hibernate-orm-hibernate-jpamodelgen = { group = "org.hibernate.orm", name = "hibernate-jpamodelgen", version.ref = "hibernateOrmVersion" } org-hibernate-validator-hibernate-validator = { group = "org.hibernate.validator", name = "hibernate-validator", version = "8.0.3.Final" } +org-hibernate-models = { group = "org.hibernate.models", name = "hibernate-models", version = "1.0.1" } org-jboss-logging-jboss-logging = { group = "org.jboss.logging", name = "jboss-logging", version.ref = "jbossLoggingVersion" } org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", name = "jboss-logging-annotations", version.ref = "jbossLoggingAnnotationVersion" } org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" } diff --git a/hibernate-reactive-core/build.gradle b/hibernate-reactive-core/build.gradle index 2a18518f0..c8ff30fbf 100644 --- a/hibernate-reactive-core/build.gradle +++ b/hibernate-reactive-core/build.gradle @@ -9,6 +9,7 @@ apply from: publishScript dependencies { api(libs.org.hibernate.orm.hibernate.core) + compileOnly(libs.org.hibernate.models) api(libs.io.smallrye.reactive.mutiny) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/boot/spi/ReactiveBootstrapContextAdapter.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/boot/spi/ReactiveBootstrapContextAdapter.java new file mode 100644 index 000000000..8134378ee --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/boot/spi/ReactiveBootstrapContextAdapter.java @@ -0,0 +1,187 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.boot.spi; + +import org.hibernate.boot.CacheRegionDefinition; +import org.hibernate.boot.archive.scan.spi.ScanEnvironment; +import org.hibernate.boot.archive.scan.spi.ScanOptions; +import org.hibernate.boot.archive.spi.ArchiveDescriptorFactory; +import org.hibernate.boot.model.convert.spi.ConverterDescriptor; +import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.BootstrapContext; +import org.hibernate.boot.spi.ClassLoaderAccess; +import org.hibernate.boot.spi.ClassmateContext; +import org.hibernate.boot.spi.MetadataBuildingOptions; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.jpa.spi.MutableJpaCompliance; +import org.hibernate.metamodel.spi.ManagedTypeRepresentationResolver; +import org.hibernate.models.spi.ModelsContext; +import org.hibernate.query.sqm.function.SqmFunctionDescriptor; +import org.hibernate.query.sqm.function.SqmFunctionRegistry; +import org.hibernate.reactive.metamodel.spi.ReactiveManagedTypeRepresentationResolver; +import org.hibernate.resource.beans.spi.BeanInstanceProducer; +import org.hibernate.resource.beans.spi.ManagedBeanRegistry; +import org.hibernate.type.BasicType; +import org.hibernate.type.spi.TypeConfiguration; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Adapt {@link BootstrapContext#getRepresentationStrategySelector()} to return a {@link ReactiveManagedTypeRepresentationResolver} + */ +public class ReactiveBootstrapContextAdapter implements BootstrapContext { + + private final BootstrapContext delegate; + + public ReactiveBootstrapContextAdapter(BootstrapContext bootstrapContext) { + this.delegate = bootstrapContext; + } + + @Override + public StandardServiceRegistry getServiceRegistry() { + return delegate.getServiceRegistry(); + } + + @Override + public MutableJpaCompliance getJpaCompliance() { + return delegate.getJpaCompliance(); + } + + @Override + public TypeConfiguration getTypeConfiguration() { + return delegate.getTypeConfiguration(); + } + + @Override + public ModelsContext getModelsContext() { + return delegate.getModelsContext(); + } + + @Override + public SqmFunctionRegistry getFunctionRegistry() { + return delegate.getFunctionRegistry(); + } + + @Override + public BeanInstanceProducer getCustomTypeProducer() { + return delegate.getCustomTypeProducer(); + } + + @Override + public MetadataBuildingOptions getMetadataBuildingOptions() { + return delegate.getMetadataBuildingOptions(); + } + + @Override + public ClassLoaderService getClassLoaderService() { + return delegate.getClassLoaderService(); + } + + @Override + public ManagedBeanRegistry getManagedBeanRegistry() { + return delegate.getManagedBeanRegistry(); + } + + @Override + public ConfigurationService getConfigurationService() { + return delegate.getConfigurationService(); + } + + @Override + public boolean isJpaBootstrap() { + return delegate.isJpaBootstrap(); + } + + @Override + public void markAsJpaBootstrap() { + delegate.markAsJpaBootstrap(); + } + + @Override + public ClassLoader getJpaTempClassLoader() { + return delegate.getJpaTempClassLoader(); + } + + @Override + public ClassLoaderAccess getClassLoaderAccess() { + return delegate.getClassLoaderAccess(); + } + + @Override + public ClassmateContext getClassmateContext() { + return delegate.getClassmateContext(); + } + + @Override + public ArchiveDescriptorFactory getArchiveDescriptorFactory() { + return delegate.getArchiveDescriptorFactory(); + } + + @Override + public ScanOptions getScanOptions() { + return delegate.getScanOptions(); + } + + @Override + public ScanEnvironment getScanEnvironment() { + return delegate.getScanEnvironment(); + } + + @Override + public Object getScanner() { + return delegate.getScanner(); + } + + @Override + public Object getJandexView() { + return delegate.getJandexView(); + } + + @Override + public Map getSqlFunctions() { + return delegate.getSqlFunctions(); + } + + @Override + public Collection getAuxiliaryDatabaseObjectList() { + return List.of(); + } + + @Override + public Collection> getAttributeConverters() { + return delegate.getAttributeConverters(); + } + + @Override + public Collection getCacheRegionDefinitions() { + return delegate.getCacheRegionDefinitions(); + } + + @Override + public ManagedTypeRepresentationResolver getRepresentationStrategySelector() { + return ReactiveManagedTypeRepresentationResolver.INSTANCE; + } + + @Override + public void release() { + delegate.release(); + } + + @Override + public void registerAdHocBasicType(BasicType basicType) { + delegate.registerAdHocBasicType( basicType ); + } + + @Override + public BasicType resolveAdHocBasicType(String key) { + return delegate.resolveAdHocBasicType( key ); + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/enhance/spi/interceptor/ReactiveEnhancementAsProxyLazinessInterceptor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/enhance/spi/interceptor/ReactiveEnhancementAsProxyLazinessInterceptor.java new file mode 100644 index 000000000..2b759400b --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/enhance/spi/interceptor/ReactiveEnhancementAsProxyLazinessInterceptor.java @@ -0,0 +1,42 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.bythecode.enhance.spi.interceptor; + +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; + +import java.lang.invoke.MethodHandles; + +/** + * Reactive version of {@link EnhancementAsProxyLazinessInterceptor}. + * + * It throws a {@link org.hibernate.LazyInitializationException} when a lazy attribute + * is not fetched using {@link org.hibernate.reactive.mutiny.Mutiny#fetch(Object)} + * or {@link org.hibernate.reactive.stage.Stage#fetch(Object)} but transparently + */ +public class ReactiveEnhancementAsProxyLazinessInterceptor extends EnhancementAsProxyLazinessInterceptor { + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + public ReactiveEnhancementAsProxyLazinessInterceptor( + EntityRelatedState meta, + EntityKey entityKey, + SharedSessionContractImplementor session) { + super( meta, entityKey, session ); + } + + @Override + protected Object handleRead(Object target, String attributeName, Object value) { + if ( isIdentifier( attributeName ) ) { + return super.handleRead( target, attributeName, value ); + } + else { + throw LOG.lazyFieldInitializationException( attributeName, getEntityName() ); + } + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/enhance/spi/internal/ReactiveLazyAttributeLoadingInterceptor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/enhance/spi/internal/ReactiveLazyAttributeLoadingInterceptor.java new file mode 100644 index 000000000..7db34f6fe --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/enhance/spi/internal/ReactiveLazyAttributeLoadingInterceptor.java @@ -0,0 +1,40 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.bythecode.enhance.spi.internal; + +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; + +import java.lang.invoke.MethodHandles; + +/** + * Reactive version of {@link LazyAttributeLoadingInterceptor}. + * + * It throws a {@link org.hibernate.LazyInitializationException} when a lazy attribute + * is not fetched using {@link org.hibernate.reactive.mutiny.Mutiny#fetch(Object)} + * or {@link org.hibernate.reactive.stage.Stage#fetch(Object)} but transparently + */ +public class ReactiveLazyAttributeLoadingInterceptor extends LazyAttributeLoadingInterceptor { + + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + + public ReactiveLazyAttributeLoadingInterceptor( + EntityRelatedState entityMeta, + Object identifier, + SharedSessionContractImplementor session) { + super( entityMeta, identifier, session ); + } + + @Override + protected Object handleRead(Object target, String attributeName, Object value) { + if ( !isAttributeLoaded( attributeName ) ) { + throw LOG.lazyFieldInitializationException( attributeName, getEntityName() ); + } + return value; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/spi/ReactiveBytecodeEnhancementMetadataPojoImplAdapter.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/spi/ReactiveBytecodeEnhancementMetadataPojoImplAdapter.java new file mode 100644 index 000000000..5c8b94b2d --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/bythecode/spi/ReactiveBytecodeEnhancementMetadataPojoImplAdapter.java @@ -0,0 +1,107 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.bythecode.spi; + +import org.hibernate.boot.Metadata; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata; +import org.hibernate.bytecode.internal.BytecodeEnhancementMetadataPojoImpl; +import org.hibernate.bytecode.spi.NotInstrumentedException; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.reactive.bythecode.enhance.spi.interceptor.ReactiveEnhancementAsProxyLazinessInterceptor; +import org.hibernate.reactive.bythecode.enhance.spi.internal.ReactiveLazyAttributeLoadingInterceptor; +import org.hibernate.type.CompositeType; + +import java.util.Set; + +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptableType; + +/** + * Extends {@link BytecodeEnhancementMetadataPojoImpl} to inject Reactive versions of {@link BytecodeLazyAttributeInterceptor} + */ +public class ReactiveBytecodeEnhancementMetadataPojoImplAdapter extends BytecodeEnhancementMetadataPojoImpl { + + public static BytecodeEnhancementMetadataPojoImpl from( + PersistentClass persistentClass, + Set identifierAttributeNames, + CompositeType nonAggregatedCidMapper, + boolean collectionsInDefaultFetchGroupEnabled, + Metadata metadata) { + final Class mappedClass = persistentClass.getMappedClass(); + final boolean enhancedForLazyLoading = isPersistentAttributeInterceptableType( mappedClass ); + final LazyAttributesMetadata lazyAttributesMetadata = enhancedForLazyLoading + ? LazyAttributesMetadata.from( persistentClass, true, collectionsInDefaultFetchGroupEnabled, metadata ) + : LazyAttributesMetadata.nonEnhanced( persistentClass.getEntityName() ); + + return new ReactiveBytecodeEnhancementMetadataPojoImplAdapter( + persistentClass.getEntityName(), + mappedClass, + identifierAttributeNames, + nonAggregatedCidMapper, + enhancedForLazyLoading, + lazyAttributesMetadata + ); + } + + ReactiveBytecodeEnhancementMetadataPojoImplAdapter(String entityName, Class mappedClass, Set identifierAttributeNames, CompositeType nonAggregatedCidMapper, boolean enhancedForLazyLoading, LazyAttributesMetadata lazyAttributesMetadata) { + super( + entityName, + mappedClass, + identifierAttributeNames, + nonAggregatedCidMapper, + enhancedForLazyLoading, + lazyAttributesMetadata + ); + } + + @Override + public LazyAttributeLoadingInterceptor injectInterceptor( + Object entity, + Object identifier, + SharedSessionContractImplementor session) throws NotInstrumentedException { + if ( !isEnhancedForLazyLoading() ) { + throw new NotInstrumentedException( "Entity class [" + getEntityClass() + .getName() + "] is not enhanced for lazy loading" ); + } + + if ( !getEntityClass().isInstance( entity ) ) { + throw new IllegalArgumentException( + String.format( + "Passed entity instance [%s] is not of expected type [%s]", + entity, + getEntityName() + ) + ); + } + final LazyAttributeLoadingInterceptor interceptor = new ReactiveLazyAttributeLoadingInterceptor( + getLazyAttributeLoadingInterceptorState(), + identifier, + session + ); + + injectInterceptor( entity, interceptor, session ); + + return interceptor; + } + + @Override + public void injectEnhancedEntityAsProxyInterceptor( + Object entity, + EntityKey entityKey, + SharedSessionContractImplementor session) { + final EnhancementAsProxyLazinessInterceptor.EntityRelatedState meta = + getEnhancementAsProxyLazinessInterceptorMetastate( session ); + injectInterceptor( + entity, + new ReactiveEnhancementAsProxyLazinessInterceptor( meta, entityKey, session ), + session + ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java index fa29cd8b5..5645e73b9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java @@ -270,6 +270,9 @@ public interface Log extends BasicLogger { @Message(id = 84, value = "The application requested a JDBC connection, but Hibernate Reactive doesn't use JDBC. This could be caused by a bug or the use of an unsupported feature in Hibernate Reactive") SQLException notUsingJdbc(); + @Message(id = 85, value = "Reactive sessions do not support transparent lazy fetching - use Stage.fetch() or Mutiny.fetch() (property '%1$S' of entity '%2$s' was not loaded)") + LazyInitializationException lazyFieldInitializationException(String fieldName, String entityName); + // Same method that exists in CoreMessageLogger @LogMessage(level = WARN) @Message(id = 104, value = "firstResult/maxResults specified with collection fetch; applying in memory!" ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoOptimized.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoOptimized.java new file mode 100644 index 000000000..252f39e8c --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoOptimized.java @@ -0,0 +1,42 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.metamodel.internal; + +import org.hibernate.bytecode.spi.ReflectionOptimizer; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.metamodel.internal.EntityInstantiatorPojoOptimized; +import org.hibernate.reactive.bythecode.enhance.spi.internal.ReactiveLazyAttributeLoadingInterceptor; +import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.type.descriptor.java.JavaType; + +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; + +/** + * Extends {@link EntityInstantiatorPojoOptimized} to apply a {@link ReactiveLazyAttributeLoadingInterceptor} + */ +public class ReactiveEntityInstantiatorPojoOptimized extends EntityInstantiatorPojoOptimized { + + public ReactiveEntityInstantiatorPojoOptimized( + EntityMetamodel entityMetamodel, + PersistentClass persistentClass, + JavaType javaType, + ReflectionOptimizer.InstantiationOptimizer instantiationOptimizer) { + super( entityMetamodel, persistentClass, javaType, instantiationOptimizer ); + } + + @Override + protected Object applyInterception(Object entity) { + if ( isApplyBytecodeInterception() ) { + asPersistentAttributeInterceptable( entity ) + .$$_hibernate_setInterceptor( new ReactiveLazyAttributeLoadingInterceptor( + getLoadingInterceptorState(), + null, + null + ) ); + } + return entity; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoStandard.java new file mode 100644 index 000000000..a30a9fc53 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoStandard.java @@ -0,0 +1,41 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.metamodel.internal; + +import org.hibernate.mapping.PersistentClass; +import org.hibernate.metamodel.internal.EntityInstantiatorPojoStandard; +import org.hibernate.reactive.bythecode.enhance.spi.internal.ReactiveLazyAttributeLoadingInterceptor; +import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.type.descriptor.java.JavaType; + +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; + +/** + * Extends {@link EntityInstantiatorPojoStandard} to apply a {@link ReactiveLazyAttributeLoadingInterceptor} + */ +public class ReactiveEntityInstantiatorPojoStandard extends EntityInstantiatorPojoStandard { + + public ReactiveEntityInstantiatorPojoStandard( + EntityMetamodel entityMetamodel, + PersistentClass persistentClass, + JavaType javaType) { + super( entityMetamodel, persistentClass, javaType ); + } + + @Override + protected Object applyInterception(Object entity) { + if ( isApplyBytecodeInterception() ) { + asPersistentAttributeInterceptable( entity ) + .$$_hibernate_setInterceptor( new ReactiveLazyAttributeLoadingInterceptor( + getLoadingInterceptorState(), + null, + null + ) ); + } + return entity; + + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityRepresentationStrategyPojoStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityRepresentationStrategyPojoStandard.java new file mode 100644 index 000000000..90d336fb5 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityRepresentationStrategyPojoStandard.java @@ -0,0 +1,47 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.metamodel.internal; + +import org.hibernate.bytecode.spi.ReflectionOptimizer; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.metamodel.internal.EntityRepresentationStrategyPojoStandard; +import org.hibernate.metamodel.spi.EntityInstantiator; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.tuple.entity.EntityMetamodel; + +/** + * Extends {@link EntityRepresentationStrategyPojoStandard} + * to create {@link ReactiveEntityInstantiatorPojoOptimized} and {@link ReactiveEntityInstantiatorPojoStandard} + */ +public class ReactiveEntityRepresentationStrategyPojoStandard extends EntityRepresentationStrategyPojoStandard { + + public ReactiveEntityRepresentationStrategyPojoStandard( + PersistentClass bootDescriptor, + EntityPersister runtimeDescriptor, + RuntimeModelCreationContext creationContext) { + super( bootDescriptor, runtimeDescriptor, creationContext ); + } + + @Override + protected EntityInstantiator determineInstantiator( + PersistentClass bootDescriptor, + EntityMetamodel entityMetamodel) { + final ReflectionOptimizer reflectionOptimizer = getReflectionOptimizer(); + if ( reflectionOptimizer != null && reflectionOptimizer.getInstantiationOptimizer() != null ) { + return new ReactiveEntityInstantiatorPojoOptimized( + entityMetamodel, + bootDescriptor, + getMappedJavaType(), + reflectionOptimizer.getInstantiationOptimizer() + ); + } + else { + return new ReactiveEntityInstantiatorPojoStandard( entityMetamodel, bootDescriptor, getMappedJavaType() ); + } + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java index 876312b47..b3d89ebd9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java @@ -22,6 +22,7 @@ import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.sqm.function.SqmFunctionRegistry; +import org.hibernate.reactive.boot.spi.ReactiveBootstrapContextAdapter; import org.hibernate.reactive.tuple.entity.ReactiveEntityMetamodel; import org.hibernate.service.ServiceRegistry; import org.hibernate.tuple.entity.EntityMetamodel; @@ -31,9 +32,11 @@ public class ReactiveRuntimeModelCreationContext implements RuntimeModelCreationContext { private final RuntimeModelCreationContext delegate; + private final BootstrapContext bootstrapContext; public ReactiveRuntimeModelCreationContext(RuntimeModelCreationContext delegate) { this.delegate = delegate; + bootstrapContext = new ReactiveBootstrapContextAdapter( delegate.getBootstrapContext() ); } @Override @@ -48,7 +51,7 @@ public SessionFactoryImplementor getSessionFactory() { @Override public BootstrapContext getBootstrapContext() { - return delegate.getBootstrapContext(); + return bootstrapContext; } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/spi/ReactiveManagedTypeRepresentationResolver.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/spi/ReactiveManagedTypeRepresentationResolver.java new file mode 100644 index 000000000..8641cf728 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/spi/ReactiveManagedTypeRepresentationResolver.java @@ -0,0 +1,40 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.metamodel.spi; + +import org.hibernate.mapping.PersistentClass; +import org.hibernate.metamodel.internal.EntityRepresentationStrategyMap; +import org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard; +import org.hibernate.metamodel.spi.EntityRepresentationStrategy; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.metamodel.internal.ReactiveEntityRepresentationStrategyPojoStandard; + +/** + * Extends {@link ManagedTypeRepresentationResolverStandard} to create a {@link ReactiveEntityRepresentationStrategyPojoStandard} + */ +public class ReactiveManagedTypeRepresentationResolver extends ManagedTypeRepresentationResolverStandard { + + public static final ManagedTypeRepresentationResolverStandard INSTANCE = new ReactiveManagedTypeRepresentationResolver(); + + @Override + public EntityRepresentationStrategy resolveStrategy( + PersistentClass bootDescriptor, + EntityPersister runtimeDescriptor, + RuntimeModelCreationContext creationContext) { + if ( bootDescriptor.getMappedClass() == null ) { // i.e. RepresentationMode.MAP; + return new EntityRepresentationStrategyMap( bootDescriptor, creationContext ); + } + else { + return new ReactiveEntityRepresentationStrategyPojoStandard( + bootDescriptor, + runtimeDescriptor, + creationContext + ); + } + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java index 14e227358..763992b2e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java @@ -5,11 +5,13 @@ */ package org.hibernate.reactive.tuple.entity; +import java.util.Set; import java.util.function.Function; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.generator.Generator; import org.hibernate.generator.GeneratorCreationContext; import org.hibernate.id.CompositeNestedGeneratedValueGenerator; @@ -28,6 +30,7 @@ import org.hibernate.mapping.SimpleValue; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.bythecode.spi.ReactiveBytecodeEnhancementMetadataPojoImplAdapter; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.id.impl.EmulatedSequenceReactiveIdentifierGenerator; import org.hibernate.reactive.id.impl.ReactiveCompositeNestedGeneratedValueGenerator; @@ -37,6 +40,7 @@ import org.hibernate.reactive.logging.impl.Log; import org.hibernate.service.ServiceRegistry; import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.type.CompositeType; import org.hibernate.type.Type; import static java.lang.invoke.MethodHandles.lookup; @@ -196,4 +200,16 @@ public Type getType() { return identifier.getType(); } } + + @Override + protected BytecodeEnhancementMetadata getBytecodeEnhancementMetadataPojo(PersistentClass persistentClass, RuntimeModelCreationContext creationContext, Set idAttributeNames, CompositeType nonAggregatedCidMapper, boolean collectionsInDefaultFetchGroupEnabled) { + return ReactiveBytecodeEnhancementMetadataPojoImplAdapter.from( + persistentClass, + idAttributeNames, + nonAggregatedCidMapper, + collectionsInDefaultFetchGroupEnabled, + creationContext.getMetadata() + ); + } + } From b90a769ef75cbeee83020d8e0ad8f59a8331bc4c Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 15 Sep 2025 14:53:17 +0200 Subject: [PATCH 137/185] [#1770] Add test for better error message when transparently loading lazy fields --- .../hibernate/reactive/LazyPropertyTest.java | 2 +- .../reactive/it/LazyBasicFieldTest.java | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java index 73233b5e9..acff15717 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyPropertyTest.java @@ -153,7 +153,7 @@ public Book(String isbn, String title, Author author) { } public Book() { - super( new EntityRelatedState( "Book", singleton( "isbn" ) ), 1, null ); + super( new EntityRelatedState( Book.class.getName(), singleton( "isbn" ) ), 1, null ); } @Override diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java index 68f234b26..cb07324cc 100644 --- a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java @@ -5,6 +5,9 @@ */ package org.hibernate.reactive.it; +import org.hibernate.LazyInitializationException; + +import io.smallrye.mutiny.Uni; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; @@ -47,4 +50,46 @@ public void testFetchBasicField(VertxTestContext context) { ) ) ) ); } + + @Test + public void testTransparentLazyFetching(VertxTestContext context) { + final Crew emily = new Crew(); + emily.setId( 21L ); + emily.setName( "Emily Jackson" ); + emily.setRole( "Passenger" ); + emily.setFate( "Unknown" ); + + test( context, assertThrown( LazyInitializationException.class, getMutinySessionFactory() + .withTransaction( session -> session.persist( emily ) ) + .call( () -> getMutinySessionFactory().withSession( session -> session.find( Crew.class, emily.getId() ) + .invoke( Crew::getRole ) ) ) + ).invoke( exception -> assertThat( exception.getMessage() ).contains( "Reactive sessions do not support transparent lazy fetching" ) ) + ); + } + + @Test + public void testGetReferenceAndTransparentLazyFetching(VertxTestContext context) { + final Crew emily = new Crew(); + emily.setId( 21L ); + emily.setName( "Emily Jackson" ); + emily.setRole( "Passenger" ); + emily.setFate( "Unknown" ); + + test( context, assertThrown( LazyInitializationException.class, getMutinySessionFactory() + .withTransaction( session -> session.persist( emily ) ) + .chain( () -> getMutinySessionFactory().withSession( session -> { + Crew crew = session.getReference( Crew.class, emily.getId() ); + String role = crew.getRole(); + return session.flush(); + } ) ) + ).invoke( exception -> assertThat( exception.getMessage() ).contains( "Reactive sessions do not support transparent lazy fetching" ) ) + ); + } + + public static Uni assertThrown(Class expectedException, Uni uni) { + return uni.onItemOrFailure().transform( (s, e) -> { + assertThat( e ).isInstanceOf( expectedException ); + return (U) e; + } ); + } } From 0112a8915b3d0691434e3dcef214facbc522cdff Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 6 Oct 2025 11:29:05 +0200 Subject: [PATCH 138/185] [#1770] Add test for initializing field after fetching it --- .../reactive/it/LazyBasicFieldTest.java | 57 ++++++++++ .../testing/SqlStatementTracker.java | 107 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/testing/SqlStatementTracker.java diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java index cb07324cc..681455c5c 100644 --- a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java @@ -6,12 +6,17 @@ package org.hibernate.reactive.it; import org.hibernate.LazyInitializationException; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.SqlStatementTracker; import io.smallrye.mutiny.Uni; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -25,6 +30,33 @@ */ @Timeout(value = 10, timeUnit = TimeUnit.MINUTES) public class LazyBasicFieldTest extends BaseReactiveIT { + + private static SqlStatementTracker sqlTracker; + + @Override + protected Configuration constructConfiguration() { + Configuration configuration = super.constructConfiguration(); + + // Construct a tracker that collects query statements via the SqlStatementLogger framework. + // Pass in configuration properties to hand off any actual logging properties + sqlTracker = new SqlStatementTracker( LazyBasicFieldTest::selectQueryFilter, configuration.getProperties() ); + return configuration; + } + + @BeforeEach + public void clearTracker() { + sqlTracker.clear(); + } + + @Override + protected void addServices(StandardServiceRegistryBuilder builder) { + sqlTracker.registerService( builder ); + } + + private static boolean selectQueryFilter(String s) { + return s.toLowerCase().startsWith( "select " ); + } + @Override protected Collection> annotatedEntities() { return List.of( Crew.class ); @@ -51,6 +83,31 @@ public void testFetchBasicField(VertxTestContext context) { ); } + @Test + public void testFetchBasicFieldAlsoInitializesIt(VertxTestContext context) { + final Crew emily = new Crew(); + emily.setId( 21L ); + emily.setName( "Emily Jackson" ); + emily.setRole( "Passenger" ); + emily.setFate( "Unknown" ); + + test( context, getMutinySessionFactory() + .withTransaction( session -> session.persist( emily ) ) + .chain( () -> getMutinySessionFactory() + .withSession( session -> session + .find( Crew.class, emily.getId() ) + .call( crew -> session.fetch( crew, Crew_.role ) + .invoke( role -> assertThat( role ).isEqualTo( emily.getRole() ) ) ) + .invoke( () -> sqlTracker.clear() ) + .call( crew -> session.fetch( crew, Crew_.role ) + .invoke( role -> { + // No select query expected, the previous fetch must have initialized the role attribute + assertThat(sqlTracker.getLoggedQueries()).hasSize( 0 ); + assertThat( role ).isEqualTo( emily.getRole() );} ) + ) ) ) + ); + } + @Test public void testTransparentLazyFetching(VertxTestContext context) { final Crew emily = new Crew(); diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/testing/SqlStatementTracker.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/testing/SqlStatementTracker.java new file mode 100644 index 000000000..bfe415809 --- /dev/null +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/testing/SqlStatementTracker.java @@ -0,0 +1,107 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.testing; + +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.engine.jdbc.internal.Formatter; +import org.hibernate.engine.jdbc.spi.SqlStatementLogger; +import org.hibernate.reactive.provider.Settings; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.function.Predicate; + +/** + * Track sql queries. + *

+ * Check {@link #registerService(StandardServiceRegistryBuilder)} to register an instance of this class. + * It's not thread-safe. + *

+ * @see #registerService(StandardServiceRegistryBuilder) + */ +public class SqlStatementTracker extends SqlStatementLogger { + + private final Predicate filter; + + private final List statements = new ArrayList<>(); + + public SqlStatementTracker(Predicate predicate) { + this.filter = predicate; + } + + public SqlStatementTracker(Predicate predicate, Properties properties) { + super( + toBoolean( properties.get( Settings.SHOW_SQL ) ), + toBoolean( properties.get( Settings.FORMAT_SQL ) ), + toBoolean( properties.get( Settings.HIGHLIGHT_SQL ) ), + toLong( properties.get( Settings.LOG_SLOW_QUERY ) ) + ); + this.filter = predicate; + } + + private static boolean toBoolean(Object obj) { + return Boolean.parseBoolean( (String) obj ); + } + + private static long toLong(Object obj) { + if ( obj == null ) { + return 0; + } + return Long.parseLong( (String) obj ); + } + + @Override + public void logStatement(String statement, Formatter formatter) { + addSql( statement ); + super.logStatement( statement, formatter ); + } + + private void addSql(String sql) { + if ( filter.test( sql ) ) { + statements.add( sql ); + } + } + + public List getLoggedQueries() { + return statements; + } + + public void clear() { + statements.clear(); + } + + /** + * Register the current sql tracker to the {@link StandardServiceRegistryBuilder}. + * + * @param builder the {@link StandardServiceRegistryBuilder} for the creation of the factory + */ + public void registerService(StandardServiceRegistryBuilder builder) { + Initiator initiator = new Initiator( this ); + builder.addInitiator( initiator ); + } + + private static class Initiator implements StandardServiceInitiator { + private final SqlStatementTracker sqlStatementTracker; + + public Initiator(SqlStatementTracker sqlStatementTracker) { + this.sqlStatementTracker = sqlStatementTracker; + } + + @Override + public SqlStatementLogger initiateService(Map configurationValues, ServiceRegistryImplementor registry) { + return sqlStatementTracker; + } + + @Override + public Class getServiceInitiated() { + return SqlStatementLogger.class; + } + } +} From ca05c588c326818bd51700e43e7d380779f092a6 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 6 Oct 2025 11:57:44 +0200 Subject: [PATCH 139/185] [#1770] Fix initializing field after fetching it --- .../session/impl/ReactiveSessionImpl.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index b6f795a1e..96e303905 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -18,6 +18,7 @@ import org.hibernate.UnknownEntityTypeException; import org.hibernate.UnresolvableObjectException; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.dialect.Dialect; import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; @@ -359,8 +360,20 @@ else if ( isPersistentAttributeInterceptable( association ) ) { @Override public CompletionStage reactiveFetch(E entity, Attribute field) { - return ( (ReactiveEntityPersister) getEntityPersister( null, entity ) ) - .reactiveInitializeLazyProperty( field, entity, this ); + final ReactiveEntityPersister entityPersister = (ReactiveEntityPersister) getEntityPersister( null, entity ); + LazyAttributeLoadingInterceptor lazyAttributeLoadingInterceptor = entityPersister.getBytecodeEnhancementMetadata() + .extractInterceptor( entity ); + final String attributeName = field.getName(); + if ( !lazyAttributeLoadingInterceptor.isAttributeLoaded( attributeName ) ) { + return ( (CompletionStage) lazyAttributeLoadingInterceptor.fetchAttribute( entity, field.getName() ) ) + .thenApply( value -> { + lazyAttributeLoadingInterceptor.attributeInitialized( attributeName ); + return value; + } ); + } + else { + return completedFuture( (T) entityPersister.getPropertyValue( entity, attributeName ) ); + } } @Override From d372f96f677e93173e52400e8022428fd1f49e95 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 22 Oct 2025 16:51:09 +0200 Subject: [PATCH 140/185] Change project version to 3.2.0-SNAPSHOT --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 8fbf4e5c6..d21ddfc58 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -projectVersion=3.1.6-SNAPSHOT \ No newline at end of file +projectVersion=3.2.0-SNAPSHOT From bfb56006c9c85da3167f4398b88f21623169aef7 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 13 Oct 2025 15:32:49 +0200 Subject: [PATCH 141/185] [#2640] Upgrade Hibernate ORM to 7.2.0.CR1 --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9fd826897..a0d7d0570 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.6" -hibernateOrmVersion = "7.1.4.Final" -hibernateOrmGradlePluginVersion = "7.1.4.Final" +hibernateOrmVersion = "7.2.0.CR1" +hibernateOrmGradlePluginVersion = "7.2.0.CR1" jacksonDatabindVersion = "2.20.0" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" From ff67acd5f19e6976a37f176a9d8b035d4c125f83 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 8 Sep 2025 12:46:46 +0200 Subject: [PATCH 142/185] [#2495] Adapt to changes to AbstractEntityPersister in 7.2 --- .../impl/ReactiveEntityUpdateAction.java | 2 +- .../ReactiveGeneratedValuesHelper.java | 89 +++++++++++++-- ...ompositeNestedGeneratedValueGenerator.java | 4 +- .../ReactiveRuntimeModelCreationContext.java | 103 ++++++++++++++++-- .../NoJdbcConnectionProviderInitiator.java | 4 +- .../session/impl/ReactiveSessionImpl.java | 4 +- .../spi/ReactiveListResultsConsumer.java | 2 +- .../tuple/entity/ReactiveEntityMetamodel.java | 11 +- .../jdbc/ReactiveArrayJdbcType.java | 24 +++- 9 files changed, 206 insertions(+), 37 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityUpdateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityUpdateAction.java index 4a6b5e149..4d212ee72 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityUpdateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityUpdateAction.java @@ -94,7 +94,7 @@ public CompletionStage reactiveExecute() throws HibernateException { .thenAccept( v -> { handleDeleted( entry ); updateCacheItem( persister, ck, entry ); - handleNaturalIdResolutions( persister, session, id ); + handleNaturalIdSharedResolutions( id, persister, session.getPersistenceContext() ); postUpdate(); final StatisticsImplementor statistics = session.getFactory().getStatistics(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java index e842adf11..eeb9065a1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java @@ -5,27 +5,50 @@ */ package org.hibernate.reactive.generator.values.internal; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + import org.hibernate.HibernateException; import org.hibernate.Internal; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.EventType; +import org.hibernate.generator.Generator; +import org.hibernate.generator.GeneratorCreationContext; import org.hibernate.generator.values.GeneratedValueBasicResultBuilder; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.generator.values.GeneratedValuesMutationDelegate; import org.hibernate.generator.values.internal.GeneratedValuesHelper; import org.hibernate.generator.values.internal.GeneratedValuesImpl; import org.hibernate.generator.values.internal.GeneratedValuesMappingProducer; -import org.hibernate.id.IdentifierGeneratorHelper; +import org.hibernate.id.CompositeNestedGeneratedValueGenerator; +import org.hibernate.id.Configurable; +import org.hibernate.id.IdentifierGenerator; +import org.hibernate.id.SelectGenerator; +import org.hibernate.id.enhanced.DatabaseStructure; +import org.hibernate.id.enhanced.SequenceStructure; +import org.hibernate.id.enhanced.SequenceStyleGenerator; +import org.hibernate.id.enhanced.TableGenerator; +import org.hibernate.id.enhanced.TableStructure; import org.hibernate.id.insert.GetGeneratedKeysDelegate; import org.hibernate.id.insert.UniqueKeySelectingDelegate; -import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.query.spi.QueryOptions; +import org.hibernate.reactive.id.ReactiveIdentifierGenerator; +import org.hibernate.reactive.id.impl.EmulatedSequenceReactiveIdentifierGenerator; +import org.hibernate.reactive.id.impl.ReactiveCompositeNestedGeneratedValueGenerator; +import org.hibernate.reactive.id.impl.ReactiveGeneratorWrapper; +import org.hibernate.reactive.id.impl.ReactiveSequenceIdentifierGenerator; +import org.hibernate.reactive.id.impl.TableReactiveIdentifierGenerator; import org.hibernate.reactive.id.insert.ReactiveInsertReturningDelegate; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; import org.hibernate.reactive.sql.results.internal.ReactiveDirectResultSetAccess; @@ -41,15 +64,10 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.type.descriptor.WrapperOptions; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletionStage; - +import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.generator.values.internal.GeneratedValuesHelper.noCustomSql; import static org.hibernate.internal.NaturalIdHelper.getNaturalIdPropertyNames; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer.UniqueSemantic.NONE; /** @@ -57,7 +75,7 @@ */ @Internal public class ReactiveGeneratedValuesHelper { - private static final CoreMessageLogger LOG = CoreLogging.messageLogger( IdentifierGeneratorHelper.class ); + private static final Log LOG = make( Log.class, lookup() ); /** * @@ -225,4 +243,53 @@ public boolean shouldReturnProxies() { return results.get( 0 ); } ); } + + public static Generator augmentWithReactiveGenerator( + Generator generator, + GeneratorCreationContext creationContext, + RuntimeModelCreationContext runtimeModelCreationContext) { + if ( generator instanceof SequenceStyleGenerator sequenceStyleGenerator) { + final DatabaseStructure structure = sequenceStyleGenerator.getDatabaseStructure(); + if ( structure instanceof TableStructure ) { + return initialize( (IdentifierGenerator) generator, new EmulatedSequenceReactiveIdentifierGenerator( (TableStructure) structure, runtimeModelCreationContext ), creationContext ); + } + if ( structure instanceof SequenceStructure ) { + return initialize( (IdentifierGenerator) generator, new ReactiveSequenceIdentifierGenerator( structure, runtimeModelCreationContext ), creationContext ); + } + throw LOG.unknownStructureType(); + } + if ( generator instanceof TableGenerator tableGenerator ) { + return initialize( + (IdentifierGenerator) generator, + new TableReactiveIdentifierGenerator( tableGenerator, runtimeModelCreationContext ), + creationContext + ); + } + if ( generator instanceof SelectGenerator ) { + throw LOG.selectGeneratorIsNotSupportedInHibernateReactive(); + } + if ( generator instanceof CompositeNestedGeneratedValueGenerator compositeNestedGeneratedValueGenerator ) { + final ReactiveCompositeNestedGeneratedValueGenerator reactiveCompositeNestedGeneratedValueGenerator = new ReactiveCompositeNestedGeneratedValueGenerator( + compositeNestedGeneratedValueGenerator, + creationContext, + runtimeModelCreationContext + ); + return initialize( + (IdentifierGenerator) generator, + reactiveCompositeNestedGeneratedValueGenerator, + creationContext + ); + } + //nothing to do + return generator; + } + + private static Generator initialize( + IdentifierGenerator idGenerator, + ReactiveIdentifierGenerator reactiveIdGenerator, + GeneratorCreationContext creationContext) { + ( (Configurable) reactiveIdGenerator ).initialize( creationContext.getSqlStringGenerationContext() ); + return new ReactiveGeneratorWrapper( reactiveIdGenerator, idGenerator ); + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java index cb6b03ab5..1da85ecc1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java @@ -13,9 +13,9 @@ import org.hibernate.id.IdentifierGenerationException; import org.hibernate.mapping.Component; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.reactive.generator.values.internal.ReactiveGeneratedValuesHelper; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.session.ReactiveConnectionSupplier; -import org.hibernate.reactive.tuple.entity.ReactiveEntityMetamodel; import java.util.ArrayList; import java.util.List; @@ -46,7 +46,7 @@ private static List reactivePlans( final List plans = new ArrayList<>(); for ( GenerationPlan plan : generator.getGenerationPlans() ) { final GenerationPlan reactivePlane = new Component.ValueGenerationPlan( - (BeforeExecutionGenerator) ReactiveEntityMetamodel.augmentWithReactiveGenerator( + (BeforeExecutionGenerator) ReactiveGeneratedValuesHelper.augmentWithReactiveGenerator( plan.getGenerator(), creationContext, runtimeModelCreationContext diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java index b3d89ebd9..b4c01cecc 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java @@ -7,6 +7,7 @@ import java.util.Map; +import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.MetadataImplementor; @@ -16,21 +17,30 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.generator.Generator; +import org.hibernate.generator.GeneratorCreationContext; import org.hibernate.mapping.GeneratorSettings; import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; +import org.hibernate.mapping.SimpleValue; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.reactive.boot.spi.ReactiveBootstrapContextAdapter; -import org.hibernate.reactive.tuple.entity.ReactiveEntityMetamodel; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.service.ServiceRegistry; -import org.hibernate.tuple.entity.EntityMetamodel; +import org.hibernate.type.Type; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.generator.values.internal.ReactiveGeneratedValuesHelper.augmentWithReactiveGenerator; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + public class ReactiveRuntimeModelCreationContext implements RuntimeModelCreationContext { + private static final Log LOG = make( Log.class, lookup() ); + private final RuntimeModelCreationContext delegate; private final BootstrapContext bootstrapContext; @@ -39,11 +49,6 @@ public ReactiveRuntimeModelCreationContext(RuntimeModelCreationContext delegate) bootstrapContext = new ReactiveBootstrapContextAdapter( delegate.getBootstrapContext() ); } - @Override - public EntityMetamodel createEntityMetamodel(PersistentClass persistentClass, EntityPersister persister) { - return new ReactiveEntityMetamodel( persistentClass, persister, delegate ); - } - @Override public SessionFactoryImplementor getSessionFactory() { return delegate.getSessionFactory(); @@ -128,4 +133,86 @@ public Map getGenerators() { public GeneratorSettings getGeneratorSettings() { return delegate.getGeneratorSettings(); } + + @Override + public Generator getOrCreateIdGenerator(String rootName, PersistentClass persistentClass){ + final Generator existing = getGenerators().get( rootName ); + if ( existing != null ) { + return existing; + } + else { + final SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier(); + final Generator idgenerator = augmentWithReactiveGenerator( + identifier.createGenerator( + getDialect(), + persistentClass.getRootClass(), + persistentClass.getIdentifierProperty(), + getGeneratorSettings() + ), + new IdGeneratorCreationContext( + persistentClass.getRootClass(), + persistentClass.getIdentifierProperty(), + getGeneratorSettings(), + identifier, + this + ), + this ); + getGenerators().put( rootName, idgenerator ); + return idgenerator; + } + } + + private record IdGeneratorCreationContext( + RootClass rootClass, + Property property, + GeneratorSettings defaults, + SimpleValue identifier, + RuntimeModelCreationContext buildingContext) implements GeneratorCreationContext { + + @Override + public Database getDatabase() { + return buildingContext.getBootModel().getDatabase(); + } + + @Override + public ServiceRegistry getServiceRegistry() { + return buildingContext.getBootstrapContext().getServiceRegistry(); + } + + @Override + public SqlStringGenerationContext getSqlStringGenerationContext() { + return defaults.getSqlStringGenerationContext(); + } + + @Override + public String getDefaultCatalog() { + return defaults.getDefaultCatalog(); + } + + @Override + public String getDefaultSchema() { + return defaults.getDefaultSchema(); + } + + @Override + public RootClass getRootClass() { + return rootClass; + } + + @Override + public PersistentClass getPersistentClass() { + return rootClass; + } + + @Override + public Property getProperty() { + return property; + } + + @Override + public Type getType() { + return identifier.getType(); + } + } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java index a57f72b16..6e32d5e7f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcConnectionProviderInitiator.java @@ -7,7 +7,7 @@ import org.hibernate.boot.registry.StandardServiceInitiator; import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator; -import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl; +import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.service.spi.ServiceRegistryImplementor; @@ -28,7 +28,7 @@ public class NoJdbcConnectionProviderInitiator implements StandardServiceInitiat @Override public ConnectionProvider initiateService(Map configurationValues, ServiceRegistryImplementor registry) { ConnectionProvider provider = ConnectionProviderInitiator.INSTANCE.initiateService(configurationValues, registry); - if (provider instanceof DriverManagerConnectionProviderImpl) { + if ( provider instanceof DriverManagerConnectionProvider ) { return NoJdbcConnectionProvider.INSTANCE; } return provider; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 96e303905..02e1191bb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -196,8 +196,8 @@ private void threadCheck() { } @Override - protected PersistenceContext createPersistenceContext() { - return new ReactivePersistenceContextAdapter( super.createPersistenceContext() ); + protected PersistenceContext createPersistenceContext(SessionCreationOptions options) { + return new ReactivePersistenceContextAdapter( super.createPersistenceContext( options ) ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java index e7283647f..108920f1a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java @@ -271,7 +271,7 @@ private JavaType resolveDomainResultJavaType( return (JavaType) resultJavaTypes.get( 0 ); } - return javaTypeRegistry.resolveDescriptor( Object[].class ); + return javaTypeRegistry.getDescriptor( Object[].class ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java index 763992b2e..59d745dc1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java @@ -8,7 +8,6 @@ import java.util.Set; import java.util.function.Function; - import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; @@ -29,7 +28,6 @@ import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SimpleValue; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; -import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.bythecode.spi.ReactiveBytecodeEnhancementMetadataPojoImplAdapter; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.id.impl.EmulatedSequenceReactiveIdentifierGenerator; @@ -46,17 +44,19 @@ import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; +/** + * @deprecated No Longer used + */ +@Deprecated(since = "4.2", forRemoval = true) public class ReactiveEntityMetamodel extends EntityMetamodel { private static final Log LOG = make( Log.class, lookup() ); public ReactiveEntityMetamodel( PersistentClass persistentClass, - EntityPersister persister, RuntimeModelCreationContext creationContext) { this( persistentClass, - persister, creationContext, s -> buildIdGenerator( s, persistentClass, creationContext ) ); @@ -64,10 +64,9 @@ public ReactiveEntityMetamodel( public ReactiveEntityMetamodel( PersistentClass persistentClass, - EntityPersister persister, RuntimeModelCreationContext creationContext, Function generatorSupplier) { - super( persistentClass, persister, creationContext, generatorSupplier ); + super( persistentClass, creationContext, generatorSupplier ); } private static Generator buildIdGenerator( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcType.java index 99d7efaf7..5aee97bc7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcType.java @@ -6,6 +6,7 @@ package org.hibernate.reactive.type.descriptor.jdbc; import java.lang.reflect.Array; +import java.lang.reflect.Type; import java.sql.CallableStatement; import java.sql.Date; import java.sql.PreparedStatement; @@ -20,6 +21,7 @@ import org.hibernate.reactive.adaptor.impl.ArrayAdaptor; import org.hibernate.reactive.adaptor.impl.ResultSetAdaptor; +import org.hibernate.type.BasicPluralType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; @@ -32,8 +34,10 @@ import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.internal.JdbcLiteralFormatterArray; +import org.hibernate.type.internal.ParameterizedTypeImpl; import org.hibernate.type.spi.TypeConfiguration; +import static java.lang.reflect.Array.newInstance; /** @@ -63,10 +67,22 @@ public JavaType getJdbcRecommendedJavaTypeMapping( Integer precision, Integer scale, TypeConfiguration typeConfiguration) { - final JavaType elementJavaType = elementJdbcType - .getJdbcRecommendedJavaTypeMapping( precision, scale, typeConfiguration ); - return typeConfiguration.getJavaTypeRegistry() - .resolveDescriptor( Array.newInstance( elementJavaType.getJavaTypeClass(), 0 ).getClass() ); + final JavaType elementJavaType = + elementJdbcType.getJdbcRecommendedJavaTypeMapping( precision, scale, typeConfiguration ); + final var javaType = + typeConfiguration.getJavaTypeRegistry() + .resolveDescriptor( newInstance( elementJavaType.getJavaTypeClass(), 0 ).getClass() ); + if ( javaType instanceof BasicPluralType ) { + //noinspection unchecked + return (JavaType) javaType; + } + else { + //noinspection unchecked + return (JavaType) javaType.createJavaType( + new ParameterizedTypeImpl( javaType.getJavaTypeClass(), new Type[0], null ), + typeConfiguration + ); + } } @Override From 0a35fbe4313890bd3475dfdd695a29e737336ba2 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 10 Sep 2025 15:22:55 +0200 Subject: [PATCH 143/185] [#2495] Adapt to changes to ActionQueque, SessionFactoryImpl and SessionCreationOptions in 7.2 --- .../DefaultReactiveRefreshEventListener.java | 4 ++-- .../mutiny/impl/MutinySessionFactoryImpl.java | 7 ++++--- .../session/impl/ReactiveSessionImpl.java | 20 ++++++++++--------- .../impl/ReactiveStatelessSessionImpl.java | 13 ++++++------ .../stage/impl/StageSessionFactoryImpl.java | 7 ++++--- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java index ba3edda4a..82964597b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java @@ -200,7 +200,7 @@ private static void evictEntity(Object entity, EntityPersister persister, Object ); final SoftLock lock = cache.lockItem( source, ck, previousVersion ); cache.remove(source, ck ); - source.getActionQueue().registerProcess( (success, session) -> cache.unlockItem( session, ck, lock ) ); + source.getActionQueue().registerCallback( (success, session) -> cache.unlockItem( session, ck, lock ) ); } } @@ -314,7 +314,7 @@ private void evictCachedCollections(Type[] types, Object id, EventSource source) ); final SoftLock lock = cache.lockItem( source, ck, null ); cache.remove( source, ck ); - actionQueue.registerProcess( (success, session) -> cache.unlockItem( session, ck, lock ) ); + actionQueue.registerCallback( (success, session) -> cache.unlockItem( session, ck, lock ) ); } } else if ( type.isComponentType() ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java index 46a71b0b8..e69a040ce 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java @@ -13,7 +13,8 @@ import java.util.function.Supplier; import org.hibernate.Cache; -import org.hibernate.internal.SessionCreationOptions; +import org.hibernate.engine.creation.internal.SessionBuilderImpl; +import org.hibernate.engine.creation.internal.SessionCreationOptions; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.reactive.common.spi.Implementor; @@ -133,11 +134,11 @@ public Uni openStatelessSession(String tenantId) { } private SessionCreationOptions options() { - return new SessionFactoryImpl.SessionBuilderImpl( delegate ); + return new SessionBuilderImpl( delegate ); } private SessionCreationOptions options(String tenantIdentifier) { - return new SessionFactoryImpl.SessionBuilderImpl( delegate ) + return (SessionCreationOptions) new SessionBuilderImpl( delegate ) .tenantIdentifier( tenantIdentifier ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 02e1191bb..85fffc15d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -5,6 +5,14 @@ */ package org.hibernate.reactive.session.impl; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.HibernateException; @@ -21,6 +29,7 @@ import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.creation.internal.SessionCreationOptions; import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.engine.spi.EffectiveEntityGraph; import org.hibernate.engine.spi.EntityEntry; @@ -49,7 +58,6 @@ import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.internal.SessionCreationOptions; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.SessionImpl; import org.hibernate.jpa.spi.NativeQueryTupleTransformer; @@ -124,13 +132,6 @@ import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaUpdate; import jakarta.persistence.metamodel.Attribute; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; import static java.lang.Boolean.TRUE; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; @@ -168,7 +169,8 @@ public class ReactiveSessionImpl extends SessionImpl implements ReactiveSession, private transient ExceptionConverter exceptionConverter; public ReactiveSessionImpl( - SessionFactoryImpl delegate, SessionCreationOptions options, + SessionFactoryImpl delegate, + SessionCreationOptions options, ReactiveConnection connection) { super( delegate, options ); InternalStateAssertions.assertUseOnEventLoop(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index 51cf19747..40047ee1d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -5,6 +5,12 @@ */ package org.hibernate.reactive.session.impl; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.SessionException; @@ -14,6 +20,7 @@ import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.creation.internal.SessionCreationOptions; import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -27,7 +34,6 @@ import org.hibernate.graph.internal.RootGraphImpl; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.id.IdentifierGenerationException; -import org.hibernate.internal.SessionCreationOptions; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.StatelessSessionImpl; import org.hibernate.jpa.spi.NativeQueryTupleTransformer; @@ -82,11 +88,6 @@ import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaUpdate; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.BiConsumer; -import java.util.function.Supplier; import static java.lang.Boolean.TRUE; import static java.lang.invoke.MethodHandles.lookup; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java index 56c574ce4..f0d4afefe 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java @@ -7,7 +7,8 @@ import jakarta.persistence.metamodel.Metamodel; import org.hibernate.Cache; -import org.hibernate.internal.SessionCreationOptions; +import org.hibernate.engine.creation.internal.SessionBuilderImpl; +import org.hibernate.engine.creation.internal.SessionCreationOptions; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.reactive.common.spi.Implementor; @@ -124,11 +125,11 @@ private CompletionStage create(ReactiveConnection connection, Supplier } private SessionCreationOptions options() { - return new SessionFactoryImpl.SessionBuilderImpl( delegate ); + return new SessionBuilderImpl( delegate ); } private SessionCreationOptions options(String tenantIdentifier) { - return new SessionFactoryImpl.SessionBuilderImpl( delegate ) + return (SessionCreationOptions) new SessionBuilderImpl( delegate ) .tenantIdentifier( tenantIdentifier ); } From c7c47d6409c7637c6d842016e1fcf845fe86414f Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 22 Sep 2025 18:30:00 +0200 Subject: [PATCH 144/185] [#2640] org.hibernate.tool.schema.JdbcMetadaAccessStrategy class name changed to JdbcMetadataAccessStrategy --- .../reactive/schema/SchemaUpdateCockroachDBTestBase.java | 4 ++-- .../reactive/schema/SchemaUpdateMariaDBTestBase.java | 4 ++-- .../hibernate/reactive/schema/SchemaUpdateMySqlTestBase.java | 4 ++-- .../hibernate/reactive/schema/SchemaUpdateOracleTestBase.java | 4 ++-- .../reactive/schema/SchemaUpdatePostgreSqlTestBase.java | 4 ++-- .../reactive/schema/SchemaUpdateSqlServerTestBase.java | 4 ++-- .../java/org/hibernate/reactive/schema/SchemaUpdateTest.java | 4 ++-- .../org/hibernate/reactive/schema/SchemaValidationTest.java | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateCockroachDBTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateCockroachDBTestBase.java index 44b34c154..24e8921dd 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateCockroachDBTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateCockroachDBTestBase.java @@ -35,8 +35,8 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.COCKROACHDB; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.GROUPED; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateMariaDBTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateMariaDBTestBase.java index 7afed941e..dde639cda 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateMariaDBTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateMariaDBTestBase.java @@ -34,8 +34,8 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.GROUPED; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateMySqlTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateMySqlTestBase.java index 501a72282..064de5cff 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateMySqlTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateMySqlTestBase.java @@ -34,8 +34,8 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MYSQL; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.GROUPED; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java index 4f46641a1..42c8e592d 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java @@ -34,8 +34,8 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.GROUPED; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdatePostgreSqlTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdatePostgreSqlTestBase.java index ded7f0904..7ae699b65 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdatePostgreSqlTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdatePostgreSqlTestBase.java @@ -34,8 +34,8 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.GROUPED; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateSqlServerTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateSqlServerTestBase.java index 1ebcf85d7..3a1aa8fb6 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateSqlServerTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateSqlServerTestBase.java @@ -35,8 +35,8 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.SQLSERVER; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.GROUPED; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTest.java index febcda93b..776a53bc4 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateTest.java @@ -32,8 +32,8 @@ import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.GROUPED; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.params.provider.Arguments.arguments; /** diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java index 3132b8498..674467b76 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java @@ -34,8 +34,8 @@ import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MYSQL; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.GROUPED; -import static org.hibernate.tool.schema.JdbcMetadaAccessStrategy.INDIVIDUALLY; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.GROUPED; +import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.params.provider.Arguments.arguments; /** From 75a934558fc9b0943a21b942b4dd8788ac4f0c20 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 22 Sep 2025 18:31:38 +0200 Subject: [PATCH 145/185] [#2640] Fix compilation errors due to changes in ORM Loggers --- .../entity/impl/ReactiveEntityPersister.java | 4 ++-- ...leSqlReactiveInformationExtractorImpl.java | 3 --- ...reSqlReactiveInformationExtractorImpl.java | 4 ---- .../internal/ReactiveStandardRowReader.java | 24 +++++++++---------- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java index 16d6237f2..0899bbbc1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java @@ -10,7 +10,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; -import org.hibernate.bytecode.BytecodeLogging; +import org.hibernate.bytecode.enhance.internal.BytecodeEnhancementLogging; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; @@ -183,7 +183,7 @@ static CompletionStage forceInitialize( Object entityId, String entityName, SharedSessionContractImplementor session) { - BytecodeLogging.LOGGER.tracef( + BytecodeEnhancementLogging.ENHANCEMENT_LOGGER.tracef( "EnhancementAsProxyLazinessInterceptor#forceInitialize : %s#%s -> %s )", entityName, entityId, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java index d85b74d1e..62fb4af20 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java @@ -11,13 +11,10 @@ import java.util.List; import org.hibernate.boot.model.naming.Identifier; -import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.tool.schema.extract.spi.ExtractionContext; public class OracleSqlReactiveInformationExtractorImpl extends AbstractReactiveInformationSchemaBasedExtractorImpl { - private static final CoreMessageLogger LOG = CoreLogging.messageLogger( PostgreSqlReactiveInformationExtractorImpl.class ); public OracleSqlReactiveInformationExtractorImpl(ExtractionContext extractionContext) { super( extractionContext ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java index 62fff7ecc..fd5676ab9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java @@ -12,8 +12,6 @@ import java.util.List; import org.hibernate.boot.model.naming.Identifier; -import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.tool.schema.extract.spi.ExtractionContext; import org.hibernate.type.SqlTypes; @@ -26,8 +24,6 @@ */ public class PostgreSqlReactiveInformationExtractorImpl extends AbstractReactiveInformationSchemaBasedExtractorImpl { - private static final CoreMessageLogger LOG = CoreLogging.messageLogger( PostgreSqlReactiveInformationExtractorImpl.class ); - public PostgreSqlReactiveInformationExtractorImpl(ExtractionContext extractionContext) { super( extractionContext ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java index 87aa57103..f19031a55 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java @@ -29,7 +29,7 @@ import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; -import static org.hibernate.sql.results.LoadingLogger.LOGGER; +import static org.hibernate.sql.results.LoadingLogger.LOADING_LOGGER; /** @@ -121,7 +121,7 @@ public R readRow(RowProcessingState processingState) { @Override public CompletionStage reactiveReadRow(ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { - LOGGER.trace( "ReactiveStandardRowReader#readRow" ); + LOADING_LOGGER.trace( "ReactiveStandardRowReader#readRow" ); return coordinateInitializers( rowProcessingState ) .thenCompose( v -> { @@ -160,7 +160,7 @@ private CompletionStage booleanComponent( final boolean[] resultRow = new boolean[resultAssemblers.length]; return loop( 0, assemblerCount, i -> { final DomainResultAssembler assembler = resultAssemblers[i]; - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + LOADING_LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); if ( assembler instanceof ReactiveDomainResultsAssembler ) { return ( (ReactiveDomainResultsAssembler) assembler ) .reactiveAssemble( rowProcessingState, options ) @@ -181,7 +181,7 @@ private CompletionStage byteComponent( final byte[] resultRow = new byte[resultAssemblers.length]; return loop( 0, assemblerCount, i -> { final DomainResultAssembler assembler = resultAssemblers[i]; - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + LOADING_LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); if ( assembler instanceof ReactiveDomainResultsAssembler ) { return ( (ReactiveDomainResultsAssembler) assembler ) .reactiveAssemble( rowProcessingState, options ) @@ -202,7 +202,7 @@ private CompletionStage charComponent( final char[] resultRow = new char[resultAssemblers.length]; return loop( 0, assemblerCount, i -> { final DomainResultAssembler assembler = resultAssemblers[i]; - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + LOADING_LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); if ( assembler instanceof ReactiveDomainResultsAssembler ) { return ( (ReactiveDomainResultsAssembler) assembler ) .reactiveAssemble( rowProcessingState, options ) @@ -223,7 +223,7 @@ private CompletionStage shortComponent( final short[] resultRow = new short[resultAssemblers.length]; return loop( 0, assemblerCount, i -> { final DomainResultAssembler assembler = resultAssemblers[i]; - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + LOADING_LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); if ( assembler instanceof ReactiveDomainResultsAssembler ) { return ( (ReactiveDomainResultsAssembler) assembler ) .reactiveAssemble( rowProcessingState, options ) @@ -244,7 +244,7 @@ private CompletionStage intComponent( final int[] resultRow = new int[resultAssemblers.length]; return loop( 0, assemblerCount, i -> { final DomainResultAssembler assembler = resultAssemblers[i]; - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + LOADING_LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); if ( assembler instanceof ReactiveDomainResultsAssembler ) { return ( (ReactiveDomainResultsAssembler) assembler ) .reactiveAssemble( rowProcessingState, options ) @@ -265,7 +265,7 @@ private CompletionStage longComponent( final long[] resultRow = new long[resultAssemblers.length]; return loop( 0, assemblerCount, i -> { final DomainResultAssembler assembler = resultAssemblers[i]; - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + LOADING_LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); if ( assembler instanceof ReactiveDomainResultsAssembler ) { return ( (ReactiveDomainResultsAssembler) assembler ) .reactiveAssemble( rowProcessingState, options ) @@ -286,7 +286,7 @@ private CompletionStage floatComponent( final float[] resultRow = new float[resultAssemblers.length]; return loop( 0, assemblerCount, i -> { final DomainResultAssembler assembler = resultAssemblers[i]; - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + LOADING_LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); if ( assembler instanceof ReactiveDomainResultsAssembler ) { return ( (ReactiveDomainResultsAssembler) assembler ) .reactiveAssemble( rowProcessingState, options ) @@ -307,7 +307,7 @@ private CompletionStage doubleComponent( final double[] resultRow = new double[resultAssemblers.length]; return loop( 0, assemblerCount, i -> { final DomainResultAssembler assembler = resultAssemblers[i]; - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + LOADING_LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); if ( assembler instanceof ReactiveDomainResultsAssembler ) { return ( (ReactiveDomainResultsAssembler) assembler ) .reactiveAssemble( rowProcessingState, options ) @@ -328,7 +328,7 @@ private CompletionStage objectComponent( final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, resultAssemblers.length ); return loop( 0, assemblerCount, i -> { final DomainResultAssembler assembler = resultAssemblers[i]; - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + LOADING_LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); if ( assembler instanceof ReactiveDomainResultsAssembler ) { return ( (ReactiveDomainResultsAssembler) assembler ) .reactiveAssemble( rowProcessingState, options ) @@ -362,7 +362,7 @@ public List> getResultJavaTypes() { } private void afterRow(RowProcessingState rowProcessingState) { - LOGGER.trace( "ReactiveStandardRowReader#afterRow" ); + LOADING_LOGGER.trace( "ReactiveStandardRowReader#afterRow" ); finishUpRow(); } From 07c2240f088051c75b2d528bcf7186dd2a3f12fc Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 22 Sep 2025 18:33:09 +0200 Subject: [PATCH 146/185] [#2640] Fix compilation errors related to changes in ORM SessionBuilderImpl and RuntimeModelCreationContext --- .../ReactiveRuntimeModelCreationContext.java | 6 ++++ .../mutiny/impl/MutinySessionFactoryImpl.java | 27 +++++++++++++--- .../stage/impl/StageSessionFactoryImpl.java | 32 +++++++++++++++---- .../tuple/entity/ReactiveEntityMetamodel.java | 6 ++++ 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java index b4c01cecc..2f3719890 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveRuntimeModelCreationContext.java @@ -23,6 +23,7 @@ import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SimpleValue; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.sqm.function.SqmFunctionRegistry; @@ -209,6 +210,11 @@ public Property getProperty() { return property; } + @Override + public Value getValue() { + return identifier; + } + @Override public Type getType() { return identifier.getType(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java index e69a040ce..93d5d5386 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java @@ -15,7 +15,9 @@ import org.hibernate.Cache; import org.hibernate.engine.creation.internal.SessionBuilderImpl; import org.hibernate.engine.creation.internal.SessionCreationOptions; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.SessionFactoryImpl; +import org.hibernate.internal.SessionImpl; import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.reactive.common.spi.Implementor; import org.hibernate.reactive.context.Context; @@ -89,7 +91,7 @@ public Context getContext() { @Override public Uni openSession() { SessionCreationOptions options = options(); - return uni( () -> connection( options.getTenantIdentifier() ) ) + return uni( () -> connection( getTenantIdentifier( options ) ) ) .chain( reactiveConnection -> create( reactiveConnection, () -> new ReactiveSessionImpl( delegate, options, reactiveConnection ) ) ) .map( s -> new MutinySessionImpl(s, this) ); @@ -119,7 +121,7 @@ private static Uni close(ReactiveConnection connection) { @Override public Uni openStatelessSession() { SessionCreationOptions options = options(); - return uni( () -> connection( options.getTenantIdentifier() ) ) + return uni( () -> connection( getTenantIdentifier( options ) ) ) .chain( reactiveConnection -> create( reactiveConnection, () -> new ReactiveStatelessSessionImpl( delegate, options, reactiveConnection ) ) ) .map( s -> new MutinyStatelessSessionImpl(s, this) ); @@ -134,12 +136,22 @@ public Uni openStatelessSession(String tenantId) { } private SessionCreationOptions options() { - return new SessionBuilderImpl( delegate ); + return new SessionBuilderImpl( delegate ) { + @Override + protected SessionImplementor createSession() { + return new SessionImpl( delegate, this ); + } + }; } private SessionCreationOptions options(String tenantIdentifier) { - return (SessionCreationOptions) new SessionBuilderImpl( delegate ) - .tenantIdentifier( tenantIdentifier ); + SessionBuilderImpl sessionBuilder = new SessionBuilderImpl( delegate ) { + @Override + protected SessionImplementor createSession() { + return new SessionImpl( delegate, this ); + } + }; + return (SessionCreationOptions) sessionBuilder.tenantIdentifier( tenantIdentifier ); } private CompletionStage connection(String tenantId) { @@ -284,4 +296,9 @@ public void close() { public boolean isOpen() { return delegate.isOpen(); } + + private String getTenantIdentifier(SessionCreationOptions options) { + return options.getTenantIdentifierValue() == null ? null : delegate.getTenantIdentifierJavaType().toString( + options.getTenantIdentifierValue() ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java index f0d4afefe..2c8a3c473 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java @@ -9,7 +9,9 @@ import org.hibernate.Cache; import org.hibernate.engine.creation.internal.SessionBuilderImpl; import org.hibernate.engine.creation.internal.SessionCreationOptions; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.SessionFactoryImpl; +import org.hibernate.internal.SessionImpl; import org.hibernate.query.criteria.HibernateCriteriaBuilder; import org.hibernate.reactive.common.spi.Implementor; import org.hibernate.reactive.context.Context; @@ -77,7 +79,7 @@ public Context getContext() { @Override public CompletionStage openSession() { SessionCreationOptions options = options(); - return connection( options.getTenantIdentifier() ) + return connection( getTenantIdentifier( options ) ) .thenCompose( connection -> create( connection, () -> new ReactiveSessionImpl( delegate, options, connection ) ) ) .thenApply( StageSessionImpl::new ); @@ -94,7 +96,7 @@ public CompletionStage openSession(String tenantId) { @Override public CompletionStage openStatelessSession() { SessionCreationOptions options = options(); - return connection( options.getTenantIdentifier() ) + return connection( getTenantIdentifier( options ) ) .thenCompose( connection -> create( connection, () -> new ReactiveStatelessSessionImpl( delegate, options, connection ) ) ) .thenApply( StageStatelessSessionImpl::new ); @@ -125,12 +127,26 @@ private CompletionStage create(ReactiveConnection connection, Supplier } private SessionCreationOptions options() { - return new SessionBuilderImpl( delegate ); + return new SessionBuilderImpl( delegate ) { + @Override + protected SessionImplementor createSession() { + return new SessionImpl( delegate, this ); + } + }; } - private SessionCreationOptions options(String tenantIdentifier) { - return (SessionCreationOptions) new SessionBuilderImpl( delegate ) - .tenantIdentifier( tenantIdentifier ); + private SessionCreationOptions options(String tenantId) { + return new SessionBuilderImpl( delegate ) { + @Override + protected SessionImplementor createSession() { + return new SessionImpl( delegate, this ); + } + + @Override + public Object getTenantIdentifierValue() { + return tenantId; + } + }; } private CompletionStage connection(String tenantId) { @@ -289,4 +305,8 @@ public HibernateCriteriaBuilder getCriteriaBuilder() { return delegate.getCriteriaBuilder(); } + private String getTenantIdentifier(SessionCreationOptions options) { + return options.getTenantIdentifierValue() == null ? null : delegate.getTenantIdentifierJavaType().toString( + options.getTenantIdentifierValue() ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java index 59d745dc1..44b2badaf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java @@ -27,6 +27,7 @@ import org.hibernate.mapping.Property; import org.hibernate.mapping.RootClass; import org.hibernate.mapping.SimpleValue; +import org.hibernate.mapping.Value; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.reactive.bythecode.spi.ReactiveBytecodeEnhancementMetadataPojoImplAdapter; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; @@ -194,6 +195,11 @@ public Property getProperty() { return property; } + @Override + public Value getValue() { + return identifier; + } + @Override public Type getType() { return identifier.getType(); From fc3047d2aa2881c14d661012b70d89547e3ff43a Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 10 Oct 2025 19:00:57 +0200 Subject: [PATCH 147/185] [#2640] Fix compilation errors --- .../internal/DatabaseSnapshotExecutor.java | 2 +- ...activeCollectionBatchLoaderArrayParam.java | 2 +- ...ctiveCollectionBatchLoaderInPredicate.java | 2 +- .../ReactiveCollectionLoaderSingleKey.java | 2 +- ...eactiveCollectionLoaderSubSelectFetch.java | 2 +- .../ReactiveEntityBatchLoaderArrayParam.java | 2 +- .../ReactiveEntityBatchLoaderInPredicate.java | 2 +- .../ast/internal/ReactiveLoaderHelper.java | 2 +- ...ReactiveMultiIdEntityLoaderArrayParam.java | 2 +- .../ReactiveMultiIdEntityLoaderStandard.java | 2 +- .../internal/ReactiveMultiKeyLoadChunker.java | 2 +- .../ReactiveNaturalIdLoaderDelegate.java | 2 +- ...veSingleUniqueKeyEntityLoaderStandard.java | 2 +- ...activeEntityInstantiatorPojoOptimized.java | 6 ++-- ...eactiveEntityInstantiatorPojoStandard.java | 6 ++-- ...ityRepresentationStrategyPojoStandard.java | 10 ++---- .../ReactiveGeneratedValuesProcessor.java | 2 +- ...MySqlReactiveInformationExtractorImpl.java | 10 ++++++ ...leSqlReactiveInformationExtractorImpl.java | 23 +++++++++++++ ...reSqlReactiveInformationExtractorImpl.java | 8 +++++ ...erverReactiveInformationExtractorImpl.java | 9 +++++ .../ReactiveNativeNonSelectQueryPlan.java | 2 +- .../ReactiveNativeSelectQueryPlanImpl.java | 2 +- .../ConcreteSqmSelectReactiveQueryPlan.java | 18 +++++----- .../ReactiveAbstractCteMutationHandler.java | 2 +- .../cte/ReactiveCteDeleteHandler.java | 2 +- .../cte/ReactiveCteSoftDeleteHandler.java | 2 +- .../cte/ReactiveCteUpdateHandler.java | 2 +- .../session/impl/ReactiveSessionImpl.java | 34 ++++++++++--------- .../StandardReactiveSelectExecutor.java | 24 ++++++------- .../sql/exec/spi/ReactiveSelectExecutor.java | 7 ++-- .../ReactiveDeferredResultSetAccess.java | 4 +-- .../reactive/schema/SchemaValidationTest.java | 2 +- 33 files changed, 125 insertions(+), 76 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java index 33e8465b2..7a9b3e70f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java @@ -36,9 +36,9 @@ import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.BaseExecutionContext; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcParameterImpl; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.graph.DomainResult; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderArrayParam.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderArrayParam.java index 0960a2ebc..40ec2ccb0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderArrayParam.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderArrayParam.java @@ -27,10 +27,10 @@ import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcParameterImpl; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderInPredicate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderInPredicate.java index 230ba5ef4..0371e7285 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderInPredicate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionBatchLoaderInPredicate.java @@ -22,7 +22,7 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSingleKey.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSingleKey.java index 90edf7d0d..7d243df2c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSingleKey.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSingleKey.java @@ -27,8 +27,8 @@ import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.BaseExecutionContext; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSubSelectFetch.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSubSelectFetch.java index cee82c475..b0c2403ae 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSubSelectFetch.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveCollectionLoaderSubSelectFetch.java @@ -25,7 +25,7 @@ import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.SqlAstTranslatorFactory; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.internal.ResultsHelper; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java index 04f971ffd..ebcd5c1d8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderArrayParam.java @@ -26,8 +26,8 @@ import org.hibernate.reactive.loader.ast.spi.ReactiveSingleIdEntityLoader; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterImpl; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import static org.hibernate.loader.ast.internal.MultiKeyLoadLogging.MULTI_KEY_LOAD_LOGGER; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderInPredicate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderInPredicate.java index 0cbfda1c3..f1deb7615 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderInPredicate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveEntityBatchLoaderInPredicate.java @@ -24,7 +24,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java index e15c42956..a4f1bb6f0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveLoaderHelper.java @@ -30,9 +30,9 @@ import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java index ef81c828e..dc9798f70 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderArrayParam.java @@ -34,10 +34,10 @@ import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcParameterImpl; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderStandard.java index 5661c3493..dec1485d7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiIdEntityLoaderStandard.java @@ -39,8 +39,8 @@ import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiKeyLoadChunker.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiKeyLoadChunker.java index e5ec58e5d..f5f4b2dc8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiKeyLoadChunker.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveMultiKeyLoadChunker.java @@ -12,9 +12,9 @@ import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveNaturalIdLoaderDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveNaturalIdLoaderDelegate.java index 08b83cd47..af8c98899 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveNaturalIdLoaderDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveNaturalIdLoaderDelegate.java @@ -39,9 +39,9 @@ import org.hibernate.sql.ast.tree.select.QuerySpec; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.BaseExecutionContext; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; import org.hibernate.sql.results.graph.DomainResult; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleUniqueKeyEntityLoaderStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleUniqueKeyEntityLoaderStandard.java index 09490cdad..99bbbd100 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleUniqueKeyEntityLoaderStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleUniqueKeyEntityLoaderStandard.java @@ -31,9 +31,9 @@ import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.BaseExecutionContext; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.Callback; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoOptimized.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoOptimized.java index 252f39e8c..42edfad31 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoOptimized.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoOptimized.java @@ -8,8 +8,8 @@ import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.mapping.PersistentClass; import org.hibernate.metamodel.internal.EntityInstantiatorPojoOptimized; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.bythecode.enhance.spi.internal.ReactiveLazyAttributeLoadingInterceptor; -import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.descriptor.java.JavaType; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; @@ -20,11 +20,11 @@ public class ReactiveEntityInstantiatorPojoOptimized extends EntityInstantiatorPojoOptimized { public ReactiveEntityInstantiatorPojoOptimized( - EntityMetamodel entityMetamodel, + EntityPersister persister, PersistentClass persistentClass, JavaType javaType, ReflectionOptimizer.InstantiationOptimizer instantiationOptimizer) { - super( entityMetamodel, persistentClass, javaType, instantiationOptimizer ); + super( persister, persistentClass, javaType, instantiationOptimizer ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoStandard.java index a30a9fc53..7653fdad6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityInstantiatorPojoStandard.java @@ -7,8 +7,8 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.metamodel.internal.EntityInstantiatorPojoStandard; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.bythecode.enhance.spi.internal.ReactiveLazyAttributeLoadingInterceptor; -import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.descriptor.java.JavaType; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; @@ -19,10 +19,10 @@ public class ReactiveEntityInstantiatorPojoStandard extends EntityInstantiatorPojoStandard { public ReactiveEntityInstantiatorPojoStandard( - EntityMetamodel entityMetamodel, + EntityPersister persister, PersistentClass persistentClass, JavaType javaType) { - super( entityMetamodel, persistentClass, javaType ); + super( persister, persistentClass, javaType ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityRepresentationStrategyPojoStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityRepresentationStrategyPojoStandard.java index 90d336fb5..34e34e189 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityRepresentationStrategyPojoStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/internal/ReactiveEntityRepresentationStrategyPojoStandard.java @@ -11,7 +11,6 @@ import org.hibernate.metamodel.spi.EntityInstantiator; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.tuple.entity.EntityMetamodel; /** * Extends {@link EntityRepresentationStrategyPojoStandard} @@ -27,21 +26,18 @@ public ReactiveEntityRepresentationStrategyPojoStandard( } @Override - protected EntityInstantiator determineInstantiator( - PersistentClass bootDescriptor, - EntityMetamodel entityMetamodel) { + protected EntityInstantiator determineInstantiator(PersistentClass bootDescriptor, EntityPersister persister) { final ReflectionOptimizer reflectionOptimizer = getReflectionOptimizer(); if ( reflectionOptimizer != null && reflectionOptimizer.getInstantiationOptimizer() != null ) { return new ReactiveEntityInstantiatorPojoOptimized( - entityMetamodel, + persister, bootDescriptor, getMappedJavaType(), reflectionOptimizer.getInstantiationOptimizer() ); } else { - return new ReactiveEntityInstantiatorPojoStandard( entityMetamodel, bootDescriptor, getMappedJavaType() ); + return new ReactiveEntityInstantiatorPojoStandard( persister, bootDescriptor, getMappedJavaType() ); } } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveGeneratedValuesProcessor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveGeneratedValuesProcessor.java index bb4ecef88..b28e8975b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveGeneratedValuesProcessor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveGeneratedValuesProcessor.java @@ -16,8 +16,8 @@ import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/MySqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/MySqlReactiveInformationExtractorImpl.java index 0504e44ee..b6a9920e8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/MySqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/MySqlReactiveInformationExtractorImpl.java @@ -64,6 +64,15 @@ protected T processPrimaryKeysResultSet( throw new UnsupportedOperationException(); } + @Override + protected T processPrimaryKeysResultSet( + String catalogFilter, + String schemaFilter, + String tableName, + ExtractionContext.ResultSetProcessor processor) throws SQLException { + throw new UnsupportedOperationException(); + } + @Override protected T processCatalogsResultSet(ExtractionContext.ResultSetProcessor processor) throws SQLException { // MySQL does not implement information_schema.information_schema_catalog_name @@ -193,4 +202,5 @@ protected T processImportedKeysResultSet( return getExtractionContext().getQueryResults( sb.toString(), parameters.toArray(), processor ); } + } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java index 62fb4af20..a6cd34af0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java @@ -195,4 +195,27 @@ protected int dataTypeCode(String typeName) { } return super.dataTypeCode( typeName ); } + + @Override + protected T processPrimaryKeysResultSet( + String catalogFilter, + String schemaFilter, + String tableName, + ExtractionContext.ResultSetProcessor processor) throws SQLException { + final StringBuilder sb = new StringBuilder() + .append( "SELECT NULL AS table_cat, " ) + .append( "c.owner AS table_schem, " ) + .append( "c.table_name, " ) + .append( "c.column_name, " ) + .append( "c.position AS key_seq, " ) + .append( "c.constraint_name AS pk_name " ) + .append( "FROM all_cons_columns c, all_constraints k " ) + .append( "WHERE k.constraint_type = 'P' AND k.table_name = :1 AND k.owner like :2 escape '/' ") + .append( "AND k.constraint_name = c.constraint_name AND k.table_name = c.table_name AND k.owner = c.owner ORDER BY column_name"); + + List parameterValues = new ArrayList<>(2); + parameterValues.add( tableName ); + parameterValues.add( schemaFilter == null ? "%" : schemaFilter ); + return getExtractionContext().getQueryResults( sb.toString(), parameterValues.toArray(), processor ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java index fd5676ab9..8f0d285cd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/PostgreSqlReactiveInformationExtractorImpl.java @@ -38,6 +38,14 @@ protected T processPrimaryKeysResultSet( throw new UnsupportedOperationException(); } + @Override + protected T processPrimaryKeysResultSet( + String catalogFilter, + String schemaFilter, + String tableName, + ExtractionContext.ResultSetProcessor processor) throws SQLException { + throw new UnsupportedOperationException(); + } @Override protected T processIndexInfoResultSet( String catalog, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/SqlServerReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/SqlServerReactiveInformationExtractorImpl.java index eea7fc477..10d290388 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/SqlServerReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/SqlServerReactiveInformationExtractorImpl.java @@ -162,6 +162,15 @@ protected T processPrimaryKeysResultSet( throw new UnsupportedOperationException(); } + @Override + protected T processPrimaryKeysResultSet( + String catalogFilter, + String schemaFilter, + String tableName, + ExtractionContext.ResultSetProcessor processor) throws SQLException { + throw new UnsupportedOperationException(); + } + @Override protected T processIndexInfoResultSet( String catalog, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java index 470218e17..c5bd2e849 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeNonSelectQueryPlan.java @@ -21,9 +21,9 @@ import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor; import org.hibernate.reactive.query.sql.spi.ReactiveNonSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveJdbcMutationExecutor; +import org.hibernate.sql.exec.internal.JdbcOperationQueryMutationNative; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation; -import org.hibernate.sql.exec.spi.JdbcOperationQueryMutationNative; import org.hibernate.sql.exec.spi.JdbcParameterBinder; import org.hibernate.sql.exec.spi.JdbcParameterBindings; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java index bae87b8b3..037f4d17d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeSelectQueryPlanImpl.java @@ -26,8 +26,8 @@ import org.hibernate.reactive.query.spi.ReactiveNativeSelectQueryPlan; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBinder; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java index 4f499f5cf..3c69c0d74 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java @@ -27,9 +27,9 @@ import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.select.SelectStatement; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.TupleMetadata; import org.hibernate.sql.results.spi.RowTransformer; @@ -53,7 +53,7 @@ public class ConcreteSqmSelectReactiveQueryPlan extends ConcreteSqmSelectQuer private final SqmSelectStatement sqm; private final DomainParameterXref domainParameterXref; - private volatile CacheableSqmInterpretation cacheableSqmInterpretation; + private volatile CacheableSqmInterpretation cacheableSqmInterpretation; public ConcreteSqmSelectReactiveQueryPlan( SqmSelectStatement sqm, @@ -76,11 +76,11 @@ private static CompletionStage> listInterpreter( String hql, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, RowTransformer rowTransformer) { final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); + final JdbcSelect jdbcSelect = sqmInterpretation.jdbcOperation(); return CompletionStages .supplyStage( () -> { @@ -123,12 +123,12 @@ private static CompletionStage executeQueryInterpreter( String hql, DomainParameterXref domainParameterXref, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings, RowTransformer rowTransformer, ReactiveResultsConsumer resultsConsumer) { final ReactiveSharedSessionContractImplementor session = (ReactiveSharedSessionContractImplementor) executionContext.getSession(); - final JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.jdbcOperation(); + final JdbcSelect jdbcSelect = sqmInterpretation.jdbcOperation(); return CompletionStages .supplyStage( () -> { final var subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler( @@ -161,7 +161,7 @@ private static CompletionStage executeQueryInterpreter( } private static int resultCountEstimate( - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings) { final Expression fetchExpression = sqmInterpretation.statement().getQueryPart() .getFetchClauseExpression(); @@ -199,7 +199,7 @@ private CompletionStage withCacheableSqmInterpretation(DomainQueryExec // to protect access. However, synchronized is much simpler here. We will verify // during throughput testing whether this is an issue and consider changes then - CacheableSqmInterpretation localCopy = cacheableSqmInterpretation; + CacheableSqmInterpretation localCopy = cacheableSqmInterpretation; JdbcParameterBindings jdbcParameterBindings = null; if ( localCopy == null ) { @@ -240,7 +240,7 @@ private interface SqmInterpreter { CompletionStage interpret( X context, DomainQueryExecutionContext executionContext, - CacheableSqmInterpretation sqmInterpretation, + CacheableSqmInterpretation sqmInterpretation, JdbcParameterBindings jdbcParameterBindings); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java index c0eb4ee3a..cba5e09aa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveAbstractCteMutationHandler.java @@ -13,7 +13,7 @@ import org.hibernate.reactive.query.sqm.mutation.spi.ReactiveAbstractMutationHandler; import org.hibernate.reactive.sql.exec.internal.StandardReactiveSelectExecutor; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import java.util.concurrent.CompletionStage; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java index 67c3641b8..5aaf573a2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteDeleteHandler.java @@ -14,7 +14,7 @@ import org.hibernate.query.sqm.mutation.internal.cte.CteMutationStrategy; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java index 8e0740d14..bf42b0dfb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteSoftDeleteHandler.java @@ -13,7 +13,7 @@ import org.hibernate.query.sqm.mutation.internal.cte.CteSoftDeleteHandler; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; public class ReactiveCteSoftDeleteHandler extends CteSoftDeleteHandler implements ReactiveAbstractCteMutationHandler { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java index c855f35ad..811f9d436 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/cte/ReactiveCteUpdateHandler.java @@ -13,7 +13,7 @@ import org.hibernate.query.sqm.mutation.internal.cte.CteUpdateHandler; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.ast.tree.cte.CteTable; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 85fffc15d..f7b784af8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -22,6 +22,9 @@ import org.hibernate.MappingException; import org.hibernate.ObjectDeletedException; import org.hibernate.ObjectNotFoundException; +import org.hibernate.OrderingMode; +import org.hibernate.RemovalsMode; +import org.hibernate.SessionCheckMode; import org.hibernate.TypeMismatchException; import org.hibernate.UnknownEntityTypeException; import org.hibernate.UnresolvableObjectException; @@ -1476,10 +1479,9 @@ private class ReactiveMultiIdentifierLoadAccessImpl implements MultiIdLoadOpt private GraphSemantic graphSemantic; private Integer batchSize; - private boolean sessionCheckingEnabled; - private boolean returnOfDeletedEntitiesEnabled; - private boolean orderedReturnEnabled = true; - private boolean readOnly; + private SessionCheckMode sessionCheckMode = SessionCheckMode.DISABLED; + private RemovalsMode removalsMode = RemovalsMode.REPLACE; + protected OrderingMode orderingMode = OrderingMode.ORDERED; public ReactiveMultiIdentifierLoadAccessImpl(EntityPersister entityPersister) { this.entityPersister = (ReactiveEntityPersister) entityPersister; @@ -1526,8 +1528,8 @@ public ReactiveMultiIdentifierLoadAccessImpl withBatchSize(int batchSize) { } @Override - public boolean isSessionCheckingEnabled() { - return sessionCheckingEnabled; + public SessionCheckMode getSessionCheckMode() { + return sessionCheckMode; } @Override @@ -1536,27 +1538,27 @@ public boolean isSecondLevelCacheCheckingEnabled() { } public ReactiveMultiIdentifierLoadAccessImpl enableSessionCheck(boolean enabled) { - this.sessionCheckingEnabled = enabled; + this.sessionCheckMode = enabled ? SessionCheckMode.ENABLED : SessionCheckMode.DISABLED; return this; } @Override - public boolean isReturnOfDeletedEntitiesEnabled() { - return returnOfDeletedEntitiesEnabled; + public RemovalsMode getRemovalsMode() { + return removalsMode; } - public ReactiveMultiIdentifierLoadAccessImpl enableReturnOfDeletedEntities(boolean enabled) { - this.returnOfDeletedEntitiesEnabled = enabled; - return this; + @Override + public OrderingMode getOrderingMode() { + return orderingMode; } - @Override - public boolean isOrderReturnEnabled() { - return orderedReturnEnabled; + public ReactiveMultiIdentifierLoadAccessImpl enableReturnOfDeletedEntities(boolean enabled) { + this.removalsMode = enabled ? RemovalsMode.INCLUDE : RemovalsMode.REPLACE; + return this; } public ReactiveMultiIdentifierLoadAccessImpl enableOrderedReturn(boolean enabled) { - this.orderedReturnEnabled = enabled; + this.orderingMode = enabled ? OrderingMode.ORDERED : OrderingMode.UNORDERED; return this; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java index 5993d1893..bd50726b1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java @@ -35,8 +35,8 @@ import org.hibernate.sql.exec.SqlExecLogger; import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; @@ -66,7 +66,7 @@ private StandardReactiveSelectExecutor() { } public CompletionStage> list( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, @@ -75,7 +75,7 @@ public CompletionStage> list( } public CompletionStage> list( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, @@ -96,7 +96,7 @@ public CompletionStage> list( * @since 2.4 (and ORM 6.6) */ public CompletionStage> list( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, @@ -119,7 +119,7 @@ public CompletionStage> list( * @since 2.4 (and Hibernate ORM 6.6) */ public CompletionStage executeQuery( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, @@ -140,7 +140,7 @@ public CompletionStage executeQuery( @Override public CompletionStage executeQuery( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, @@ -172,7 +172,7 @@ public CompletionStage executeQuery( } private CompletionStage doExecuteQuery( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer transformer, @@ -281,7 +281,7 @@ private static RowTransformer rowTransformer( public CompletionStage resolveJdbcValuesSource( String queryIdentifier, - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, boolean canBeCached, ExecutionContext executionContext, ReactiveDeferredResultSetAccess resultSetAccess) { @@ -368,7 +368,7 @@ public CompletionStage resolveJdbcValuesSource( null, queryIdentifier, queryOptions, - resultSetAccess.usesFollowOnLocking(), + false, jdbcValuesMapping, null, executionContext @@ -383,7 +383,7 @@ public CompletionStage resolveJdbcValuesSource( queryResultsCacheKey, queryIdentifier, queryOptions, - resultSetAccess.usesFollowOnLocking(), + false, jdbcValuesMapping, capturingMetadata.resolveMetadataForCache(), executionContext @@ -404,7 +404,7 @@ public CompletionStage resolveJdbcValuesSource( queryResultsCacheKey, queryIdentifier, queryOptions, - resultSetAccess.usesFollowOnLocking(), + false, jdbcValuesMapping, capturingMetadata.resolveMetadataForCache(), executionContext @@ -535,7 +535,7 @@ public Statistics(ExecutionContext executionContext, ReactiveValuesResultSet jdb } } - public void end(JdbcOperationQuerySelect jdbcSelect, T result) { + public void end(JdbcSelect jdbcSelect, T result) { if ( enabled ) { final long endTime = System.nanoTime(); final long milliseconds = TimeUnit.MILLISECONDS diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java index ab68e34be..80e83a5f9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java @@ -10,10 +10,11 @@ import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; +import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.spi.RowTransformer; @@ -46,7 +47,7 @@ default CompletionStage executeQuery( } CompletionStage executeQuery( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, @@ -56,7 +57,7 @@ CompletionStage executeQuery( ReactiveResultsConsumer resultsConsumer); CompletionStage> list( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java index 0d81a2f8b..d7595a902 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java @@ -29,8 +29,8 @@ import org.hibernate.resource.jdbc.spi.JdbcSessionContext; import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; @@ -54,7 +54,7 @@ public class ReactiveDeferredResultSetAccess extends DeferredResultSetAccess imp private ResultSet resultSet; public ReactiveDeferredResultSetAccess( - JdbcOperationQuerySelect jdbcSelect, + JdbcSelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, JdbcSelectExecutor.StatementCreator statementCreator, diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java index 674467b76..6e27e2eaf 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java @@ -118,7 +118,7 @@ context, setupFactory( strategy, type ) @MethodSource("settings") @Timeout(value = 10, timeUnit = MINUTES) public void testValidationFails(String strategy, String type, VertxTestContext context) { - final String errorMessage = "Schema-validation: missing table [" + Extra.TABLE_NAME + "]"; + final String errorMessage = "Schema validation: missing table [" + Extra.TABLE_NAME + "]"; test( context, setupFactory( strategy, type ) .thenCompose( v -> { From 0a4664612a6ef77c7600a59f33ca4b6063b33f1d Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 17 Oct 2025 11:17:49 +0200 Subject: [PATCH 148/185] [#2640] Fix ReactiveEntityInitializerImpl Fixes * return the right lock (different than ORM) * Fix postLoad issue --- .../ReactiveEntityInitializerImpl.java | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java index c6ee36363..0ae83243f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -216,7 +216,7 @@ else if ( lazyInitializer.isUninitialized() ) { if ( data.getState() == State.INITIALIZED ) { registerReloadedEntity( data ); resolveInstanceSubInitializers( data ); - if ( rowProcessingState.needsResolveState() ) { + if ( data.getState() == State.INITIALIZED && rowProcessingState.needsResolveState() ) { // We need to read result set values to correctly populate the query cache resolveState( data ); } @@ -336,6 +336,19 @@ public CompletionStage reactiveInitializeInstance(EntityInitializerData da assert consistentInstance( data ); return reactiveInitializeEntityInstance( (ReactiveEntityInitializerData) data ); } + else { + if ( data.getRowProcessingState().needsResolveState() ) { + // A sub-initializer might have taken responsibility for this entity, + // but we still need to resolve the state to correctly populate a query cache + resolveState( data ); + } + final ReactiveEntityInitializerData reactiveData = (ReactiveEntityInitializerData) data; + if ( getEntityDescriptor().getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() + && reactiveData.getEntityHolder().getEntityInitializer() != this + && reactiveData.getEntityHolder().isInitialized() ) { + updateInitializedEntityInstance( data ); + } + } data.setState( State.INITIALIZED ); return voidFuture(); } @@ -348,7 +361,7 @@ protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityI return reactiveExtractConcreteTypeStateValues( data ) .thenAccept( resolvedEntityState -> { - + rowProcessingState.getJdbcValuesSourceProcessingState().registerLoadingEntityHolder( data.getEntityHolder() ); preLoad( data, resolvedEntityState ); if ( isPersistentAttributeInterceptable( data.getEntityInstanceForNotify() ) ) { @@ -372,10 +385,6 @@ protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityI final Object version = getVersionAssembler() != null ? getVersionAssembler().assemble( rowProcessingState ) : null; final Object rowId = getRowIdAssembler() != null ? getRowIdAssembler().assemble( rowProcessingState ) : null; - // from the perspective of Hibernate, an entity is read locked as soon as it is read - // so regardless of the requested lock mode, we upgrade to at least the read level - final LockMode lockModeToAcquire = data.getLockMode() == LockMode.NONE ? LockMode.READ : data.getLockMode(); - final EntityEntry entityEntry = persistenceContext.addEntry( data.getEntityInstanceForNotify(), Status.LOADING, @@ -383,7 +392,7 @@ protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityI rowId, data.getEntityKey().getIdentifier(), version, - lockModeToAcquire, + lockModeToAcquire( data ), true, data.getConcreteDescriptor(), false @@ -404,16 +413,15 @@ protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityI statistics.loadEntity( data.getConcreteDescriptor().getEntityName() ); } } - updateCaches( - data, - session, - session.getPersistenceContextInternal(), - resolvedEntityState, - version - ); + updateCaches( data, session, session.getPersistenceContextInternal(), resolvedEntityState, version ); } ); } + // Hibernate ORM has a similar method, but it checks if we are in a transaction first + private static LockMode lockModeToAcquire(ReactiveEntityInitializerData data) { + return data.getLockMode() == LockMode.NONE ? LockMode.READ : data.getLockMode(); + } + protected CompletionStage reactiveExtractConcreteTypeStateValues(ReactiveEntityInitializerData data) { final RowProcessingState rowProcessingState = data.getRowProcessingState(); final Object[] values = new Object[data.getConcreteDescriptor().getNumberOfAttributeMappings()]; @@ -447,7 +455,9 @@ protected CompletionStage reactiveResolveEntityInstance1(ReactiveEntityIni else { data.setInstance( proxy ); if ( Hibernate.isInitialized( data.getInstance() ) ) { - data.setState( State.INITIALIZED ); + if ( data.getEntityHolder().isInitialized() ) { + data.setState( State.INITIALIZED ); + } data.setEntityInstanceForNotify( Hibernate.unproxy( data.getInstance() ) ); } else { From 9ed686930d014a7dfec413a840baa9dcb2ab0ba0 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 17 Oct 2025 11:18:19 +0200 Subject: [PATCH 149/185] [#2640] Add ReactiveConnection#isTransactionInProgress --- .../org/hibernate/reactive/pool/BatchingConnection.java | 5 +++++ .../org/hibernate/reactive/pool/ReactiveConnection.java | 2 ++ .../hibernate/reactive/pool/impl/SqlClientConnection.java | 5 +++++ .../reactive/session/impl/ReactiveSessionImpl.java | 6 ++++++ 4 files changed, 18 insertions(+) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java index 557ec944b..e467e9d26 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/BatchingConnection.java @@ -51,6 +51,11 @@ public DatabaseMetadata getDatabaseMetadata() { return delegate.getDatabaseMetadata(); } + @Override + public boolean isTransactionInProgress() { + return delegate.isTransactionInProgress(); + } + @Override public ReactiveConnection withBatchSize(int batchSize) { if ( batchSize <= 1 ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnection.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnection.java index 10e304116..193b3395e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnection.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnection.java @@ -30,6 +30,8 @@ @Incubating public interface ReactiveConnection { + boolean isTransactionInProgress(); + @FunctionalInterface interface Expectation { void verifyOutcome(int rowCount, int batchPosition, String sql); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java index 3e8647f6c..a58c19d06 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientConnection.java @@ -73,6 +73,11 @@ public DatabaseMetadata getDatabaseMetadata() { return client().databaseMetadata(); } + @Override + public boolean isTransactionInProgress() { + return connection.transaction() != null; + } + @Override public CompletionStage update(String sql, Object[] paramValues) { translateNulls( paramValues ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index f7b784af8..a86a2d68a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -190,6 +190,12 @@ public SessionImplementor getSharedContract() { return this; } + @Override + public boolean isTransactionInProgress() { + return isOpenOrWaitingForAutoClose() + && reactiveConnection.isTransactionInProgress(); + } + @Override public Dialect getDialect() { threadCheck(); From 53a9a27de0fc860e0ab04bb5e73ef21e869983f3 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 22 Oct 2025 12:54:43 +0200 Subject: [PATCH 150/185] [#2640] Override EntityMetamodel#getBytecodeEnhancementMetadataPojo Hibernate ORM 7.2 now calls this method when it needs to enable bytecode enhancements --- .../ReactiveJoinedSubclassEntityPersister.java | 15 +++++++++++++++ .../impl/ReactiveSingleTableEntityPersister.java | 15 +++++++++++++++ .../ReactiveUnionSubclassEntityPersister.java | 15 +++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java index e9be58295..a9b8e8759 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java @@ -6,6 +6,7 @@ package org.hibernate.reactive.persister.entity.impl; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletionStage; import org.hibernate.FetchMode; @@ -13,6 +14,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.spi.CascadeStyle; @@ -41,6 +43,7 @@ import org.hibernate.persister.entity.mutation.InsertCoordinator; import org.hibernate.persister.entity.mutation.UpdateCoordinator; import org.hibernate.property.access.spi.PropertyAccess; +import org.hibernate.reactive.bythecode.spi.ReactiveBytecodeEnhancementMetadataPojoImplAdapter; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader; import org.hibernate.reactive.logging.impl.Log; @@ -53,6 +56,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -88,6 +92,17 @@ public void initializeLazyProperty(Object entity, EntityEntry entry, Object prop super.initializeLazyProperty( entity, entry, propValue, index, type ); } + @Override + protected BytecodeEnhancementMetadata getBytecodeEnhancementMetadataPojo( + PersistentClass persistentClass, + RuntimeModelCreationContext creationContext, + Set idAttributeNames, + CompositeType nonAggregatedCidMapper, + boolean collectionsInDefaultFetchGroupEnabled) { + return ReactiveBytecodeEnhancementMetadataPojoImplAdapter + .from( persistentClass, idAttributeNames, nonAggregatedCidMapper, collectionsInDefaultFetchGroupEnabled, creationContext.getMetadata() ); + } + @Override protected SingleIdEntityLoader buildSingleIdEntityLoader() { return reactiveDelegate.buildSingleIdEntityLoader(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java index 787ef5b5c..ac9fb433f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java @@ -6,6 +6,7 @@ package org.hibernate.reactive.persister.entity.impl; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.function.Supplier; @@ -14,6 +15,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.spi.CascadeStyle; @@ -44,6 +46,7 @@ import org.hibernate.persister.entity.mutation.InsertCoordinator; import org.hibernate.persister.entity.mutation.UpdateCoordinator; import org.hibernate.property.access.spi.PropertyAccess; +import org.hibernate.reactive.bythecode.spi.ReactiveBytecodeEnhancementMetadataPojoImplAdapter; import org.hibernate.reactive.generator.values.GeneratedValuesMutationDelegateAdaptor; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader; @@ -57,6 +60,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -93,6 +97,17 @@ public void initializeLazyProperty(Object entity, EntityEntry entry, Object prop super.initializeLazyProperty( entity, entry, propValue, index, type ); } + @Override + protected BytecodeEnhancementMetadata getBytecodeEnhancementMetadataPojo( + PersistentClass persistentClass, + RuntimeModelCreationContext creationContext, + Set idAttributeNames, + CompositeType nonAggregatedCidMapper, + boolean collectionsInDefaultFetchGroupEnabled) { + return ReactiveBytecodeEnhancementMetadataPojoImplAdapter + .from( persistentClass, idAttributeNames, nonAggregatedCidMapper, collectionsInDefaultFetchGroupEnabled, creationContext.getMetadata() ); + } + @Override public GeneratedValuesMutationDelegate createInsertDelegate() { return ReactiveAbstractEntityPersister.super.createReactiveInsertDelegate(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java index 71b91d4a5..320f2d75e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java @@ -7,6 +7,7 @@ import java.lang.invoke.MethodHandles; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletionStage; import org.hibernate.FetchMode; @@ -15,6 +16,7 @@ import org.hibernate.LockOptions; import org.hibernate.MappingException; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor; +import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.engine.spi.CascadeStyle; @@ -44,6 +46,7 @@ import org.hibernate.persister.entity.mutation.InsertCoordinator; import org.hibernate.persister.entity.mutation.UpdateCoordinator; import org.hibernate.property.access.spi.PropertyAccess; +import org.hibernate.reactive.bythecode.spi.ReactiveBytecodeEnhancementMetadataPojoImplAdapter; import org.hibernate.reactive.loader.ast.internal.ReactiveSingleIdArrayLoadPlan; import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader; import org.hibernate.reactive.logging.impl.Log; @@ -57,6 +60,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -89,6 +93,17 @@ public void initializeLazyProperty(Object entity, EntityEntry entry, Object prop super.initializeLazyProperty( entity, entry, propValue, index, type ); } + @Override + protected BytecodeEnhancementMetadata getBytecodeEnhancementMetadataPojo( + PersistentClass persistentClass, + RuntimeModelCreationContext creationContext, + Set idAttributeNames, + CompositeType nonAggregatedCidMapper, + boolean collectionsInDefaultFetchGroupEnabled) { + return ReactiveBytecodeEnhancementMetadataPojoImplAdapter + .from( persistentClass, idAttributeNames, nonAggregatedCidMapper, collectionsInDefaultFetchGroupEnabled, creationContext.getMetadata() ); + } + @Override protected SingleIdEntityLoader buildSingleIdEntityLoader() { return reactiveDelegate.buildSingleIdEntityLoader(); From d48f5e0c034dc24d677be40c14dca2cd408a96cd Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 17 Oct 2025 11:20:09 +0200 Subject: [PATCH 151/185] [#2640] Disable FindByIdWithLockTest#testFindUpgradeNoWait --- .../test/java/org/hibernate/reactive/FindByIdWithLockTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java index 51eece400..9eb8bb8f8 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FindByIdWithLockTest.java @@ -16,6 +16,7 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -83,6 +84,7 @@ context, getMutinySessionFactory() ); } + @Disabled @Test public void testFindUpgradeNoWait(VertxTestContext context) { Child child = new Child( CHILD_ID, "And" ); From f51adf718b60387a422980dd5a734f331b61e308 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 20 Oct 2025 15:25:26 +0200 Subject: [PATCH 152/185] [#2640] Disable FetchModeSubSelectTest#testEagerFetchQuery --- .../org/hibernate/reactive/FetchModeSubselectEagerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java index 388baec9a..e593574a0 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java @@ -17,6 +17,7 @@ import org.hibernate.cfg.Configuration; import org.hibernate.reactive.util.impl.CompletionStages; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -117,6 +118,7 @@ public void testEagerParentFetch(VertxTestContext context) { } @Test + @Disabled("NullPointerException: see https://hibernate.atlassian.net/browse/HHH-19874") public void testEagerFetchQuery(VertxTestContext context) { Node basik = new Node( "Child" ); From 893787a8c78f5167c687babe8993d32c0f111843 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 20 Oct 2025 15:25:49 +0200 Subject: [PATCH 153/185] [#2640] Update and clean up existing tests --- .../hibernate/reactive/BatchFetchTest.java | 64 +- .../org/hibernate/reactive/CascadeTest.java | 232 +++--- .../reactive/FetchModeSubselectEagerTest.java | 63 +- .../hibernate/reactive/MutinySessionTest.java | 752 ++++++++---------- .../ReactiveMultitenantNoResolverTest.java | 43 +- .../reactive/ReactiveMultitenantTest.java | 36 +- .../reactive/ReactiveSessionTest.java | 46 +- .../BasicTypesAndCallbacksForAllDBsTest.java | 339 ++++---- .../reactive/it/LazyBasicFieldTest.java | 49 +- 9 files changed, 815 insertions(+), 809 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchFetchTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchFetchTest.java index 48032f684..95394f63a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchFetchTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchFetchTest.java @@ -38,12 +38,9 @@ import jakarta.persistence.Version; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; @Timeout(value = 10, timeUnit = MINUTES) - public class BatchFetchTest extends BaseReactiveTest { @Override @@ -70,43 +67,40 @@ public void testQuery(VertxTestContext context) { test( context, getSessionFactory() .withTransaction( s -> s.persist( basik ) ) - .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createSelectionQuery( "from Node n order by id", Node.class ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .createSelectionQuery( "from Node n order by id", Node.class ) .getResultList() .thenCompose( list -> { - assertEquals( list.size(), 2 ); + assertThat( list ).hasSize( 2 ); Node n1 = list.get( 0 ); Node n2 = list.get( 1 ); - assertFalse( Hibernate.isInitialized( n1.getElements() ), "'n1.elements' should not be initialize" ); - assertFalse( Hibernate.isInitialized( n2.getElements() ), "'n2.elements' should not be initialize" ); + assertThat( Hibernate.isInitialized( n1.getElements() ) ).as( "'n1.elements' should not be initialized" ).isFalse(); + assertThat( Hibernate.isInitialized( n2.getElements() ) ).as( "'n2.elements' should not be initialized" ).isFalse(); return s.fetch( n1.getElements() ) .thenAccept( elements -> { - assertTrue( Hibernate.isInitialized( elements ), "'elements' after fetch should not be initialize" ); - assertTrue( Hibernate.isInitialized( n1.getElements() ), "'n1.elements' after fetch should be initialize" ); - assertTrue( Hibernate.isInitialized( n2.getElements() ), "'n2.elements' after fetch should be initialize" ); + assertThat( Hibernate.isInitialized( elements ) ).as( "'elements' after fetch should not be initialize" ).isTrue(); + assertThat( Hibernate.isInitialized( n1.getElements() ) ).as( "'n1.elements' after fetch should be initialize" ).isTrue(); + assertThat( Hibernate.isInitialized( n2.getElements() ) ).as( "'n2.elements' after fetch should be initialize" ).isTrue(); } ); } ) - ) - .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createSelectionQuery( "from Element e order by id", Element.class ) + ) ) + .thenCompose( v -> getSessionFactory().withTransaction( s -> s + .createSelectionQuery( "from Element e order by id", Element.class ) .getResultList() .thenCompose( list -> { - assertEquals( list.size(), 5 ); - list.forEach( element -> assertFalse( Hibernate.isInitialized( element.node ) ) ); - list.forEach( element -> assertEquals( s.getLockMode( element.node ), LockMode.NONE ) ); + assertThat( list ).hasSize( 5 ); + list.forEach( element -> assertThat( Hibernate.isInitialized( element.node ) ).isFalse() ); + list.forEach( element -> assertThat( s.getLockMode( element.node ) ).isEqualTo( LockMode.NONE ) ); return s.fetch( list.get( 0 ).node ) .thenAccept( node -> { - assertTrue( Hibernate.isInitialized( node ) ); + assertThat( Hibernate.isInitialized( node ) ).isTrue(); //TODO: I would like to assert that they're all initialized // but apparently it doesn't set the proxies to init'd // so check the LockMode as a workaround - list.forEach( element -> assertEquals( - s.getLockMode( element.node ), - LockMode.READ - ) ); + list.forEach( element -> assertThat( s.getLockMode( element.node ) ).isEqualTo( LockMode.READ ) ); } ); } ) - ) + ) ) ); } @@ -125,11 +119,11 @@ public void testBatchLoad(VertxTestContext context) { .thenCompose( v -> openSession() ) .thenCompose( s -> s.find( Element.class, basik.elements.get( 1 ).id, basik.elements.get( 2 ).id, basik.elements.get( 0 ).id ) ) .thenAccept( elements -> { - assertFalse( elements.isEmpty() ); - assertEquals( 3, elements.size() ); - assertEquals( basik.elements.get( 1 ).id, elements.get( 0 ).id ); - assertEquals( basik.elements.get( 2 ).id, elements.get( 1 ).id ); - assertEquals( basik.elements.get( 0 ).id, elements.get( 2 ).id ); + assertThat( elements ).isNotEmpty(); + assertThat( elements ).hasSize( 3 ); + assertThat( elements.get( 0 ).id ).isEqualTo( basik.elements.get( 1 ).id ); + assertThat( elements.get( 1 ).id ).isEqualTo( basik.elements.get( 2 ).id ); + assertThat( elements.get( 2 ).id ).isEqualTo( basik.elements.get( 0 ).id ); } ) ); } @@ -150,12 +144,12 @@ public void testWithCollection(VertxTestContext context) { .createSelectionQuery( "from Node n order by id", Node.class ) .getResultList() .thenCompose( list -> { - assertEquals( list.size(), 1 ); + assertThat( list ).hasSize( 1 ); Node n1 = list.get( 0 ); - assertFalse( Hibernate.isInitialized( n1.elements ) ); + assertThat( Hibernate.isInitialized( n1.elements ) ).isFalse(); return s.fetch( n1.elements ).thenAccept( elements -> { - assertTrue( Hibernate.isInitialized( elements ) ); - assertTrue( Hibernate.isInitialized( n1.elements ) ); + assertThat( Hibernate.isInitialized( elements ) ).isTrue(); + assertThat( Hibernate.isInitialized( n1.elements ) ).isTrue(); } ); } ) ) @@ -177,7 +171,7 @@ public Element(Node node) { this.node = node; } - Element() { + public Element() { } public Node getNode() { @@ -227,7 +221,7 @@ public Node(String string) { this.string = string; } - Node() { + public Node() { } @PrePersist diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java index ec94d6c8a..3c09f4243 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java @@ -12,7 +12,6 @@ import org.hibernate.Hibernate; import org.hibernate.cfg.Configuration; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -36,11 +35,7 @@ import jakarta.persistence.Version; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - +import static org.assertj.core.api.Assertions.assertThat; @Timeout(value = 10, timeUnit = MINUTES) public class CascadeTest extends BaseReactiveTest { @@ -55,119 +50,129 @@ protected Configuration constructConfiguration() { @Test public void testQuery(VertxTestContext context) { - - Node basik = new Node("Child"); - basik.parent = new Node("Parent"); - basik.elements.add(new Element(basik)); - basik.elements.add(new Element(basik)); - basik.elements.add(new Element(basik)); - - test( context, + Node basik = new Node( "Child" ); + basik.parent = new Node( "Parent" ); + basik.elements.add( new Element( basik ) ); + basik.elements.add( new Element( basik ) ); + basik.elements.add( new Element( basik ) ); + + test( + context, openSession() - .thenCompose(s -> s.persist(basik).thenCompose(v -> s.flush())) + .thenCompose( s -> s.persist( basik ).thenCompose( v -> s.flush() ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createQuery("select distinct n from Node n left join fetch n.elements").getResultList()) - .thenAccept( list -> assertEquals( list.size(), 2 ) ) + .thenCompose( s -> s.createQuery( "select distinct n from Node n left join fetch n.elements", Node.class ).getResultList() ) + .thenAccept( list -> assertThat( list ).hasSize( 2 ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createQuery("select distinct n, e from Node n join n.elements e").getResultList()) - .thenAccept( list -> assertEquals( list.size(), 3 ) ) + .thenCompose( s -> s.createQuery( "select distinct n, e from Node n join n.elements e", Node.class ).getResultList() ) + .thenAccept( list -> assertThat( list ).hasSize( 3 ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createQuery("select distinct n.id, e.id from Node n join n.elements e").getResultList()) - .thenAccept( list -> assertEquals( list.size(), 3 ) ) + .thenCompose( s -> s.createQuery( "select distinct n.id, e.id from Node n join n.elements e", Node.class ).getResultList() ) + .thenAccept( list -> assertThat( list ).hasSize( 3 ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createQuery("select max(e.id), min(e.id), sum(e.id) from Node n join n.elements e group by n.id order by n.id").getResultList()) - .thenAccept( list -> assertEquals( list.size(), 1 ) ) + .thenCompose( s -> s.createQuery( "select max(e.id), min(e.id), sum(e.id) from Node n join n.elements e group by n.id order by n.id", Node.class ).getResultList() ) + .thenAccept( list -> assertThat( list ).hasSize( 1 ) ) ); } @Test public void testCascade(VertxTestContext context) { - - Node basik = new Node("Child"); - basik.parent = new Node("Parent"); - basik.elements.add( new Element(basik) ); - basik.elements.add( new Element(basik) ); - basik.elements.add( new Element(basik) ); - - test( context, + Node basik = new Node( "Child" ); + basik.parent = new Node( "Parent" ); + basik.elements.add( new Element( basik ) ); + basik.elements.add( new Element( basik ) ); + basik.elements.add( new Element( basik ) ); + + test( + context, openSession() - .thenCompose(s -> s.persist(basik) - .thenApply(v -> { assertTrue(basik.prePersisted && !basik.postPersisted); return s; } ) - .thenApply(v -> { assertTrue(basik.parent.prePersisted && !basik.parent.postPersisted); return s; } ) - .thenCompose(v -> s.flush()) - .thenApply(v -> { assertTrue(basik.prePersisted && basik.postPersisted); return s; } ) - .thenApply(v -> { assertTrue(basik.parent.prePersisted && basik.parent.postPersisted); return s; } ) + .thenCompose( s -> s + .persist( basik ) + .thenAccept( v -> { + assertThat( basik.prePersisted && !basik.postPersisted ).isTrue(); + assertThat( basik.parent.prePersisted && !basik.parent.postPersisted ).isTrue(); + } ) + .thenCompose( v -> s.flush() ) + .thenAccept( v -> { + assertThat( basik.prePersisted && basik.postPersisted ).isTrue(); + assertThat( basik.parent.prePersisted && basik.parent.postPersisted ).isTrue(); + } ) ) .thenCompose( v -> openSession() ) - .thenCompose(s2 -> s2.find( Node.class, basik.getId() ) + .thenCompose( s2 -> s2 + .find( Node.class, basik.getId() ) .thenCompose( node -> { - assertNotNull( node ); - assertTrue( node.loaded ); - assertEquals( node.string, basik.string); - assertEquals( node.version, 0 ); - assertEquals( node.elements.size(), basik.elements.size() ); + assertThat( node ).isNotNull(); + assertThat( node.loaded ).isTrue(); + assertThat( node.string ).isEqualTo( basik.string ); + assertThat( node.version ).isEqualTo( 0 ); + assertThat( node.elements.size() ).isEqualTo( basik.elements.size() ); node.string = "Adopted"; - node.parent = new Node("New Parent"); + node.parent = new Node( "New Parent" ); return s2.flush() - .thenAccept(v -> { - assertNotNull( node ); - assertTrue( node.postUpdated && node.preUpdated ); - assertFalse( node.postPersisted && node.prePersisted ); - assertTrue( node.parent.postPersisted && node.parent.prePersisted ); - assertEquals( node.version, 1 ); - }); - })) + .thenAccept( v -> { + assertThat( node ).isNotNull(); + assertThat( node.postUpdated && node.preUpdated ).isTrue(); + assertThat( node.postPersisted && node.prePersisted ).isFalse(); + assertThat( node.parent.postPersisted && node.parent.prePersisted ).isTrue(); + assertThat( node.version ).isEqualTo( 1 ); + } ); + } ) ) .thenCompose( v -> openSession() ) - .thenCompose(s2 -> s2.find( Node.class, basik.getId() ) + .thenCompose( s2 -> s2.find( Node.class, basik.getId() ) .thenCompose( node -> { - assertNotNull( node ); - assertEquals( node.version, 1 ); - assertEquals( node.string, "Adopted"); - assertTrue(Hibernate.isInitialized(node.elements)); - assertFalse(Hibernate.isInitialized(node.parent)); + assertThat( node ).isNotNull(); + assertThat( node.version ).isEqualTo( 1 ); + assertThat( node.string ).isEqualTo( "Adopted" ); + assertThat( Hibernate.isInitialized( node.elements ) ).isTrue(); + assertThat( Hibernate.isInitialized( node.parent ) ).isFalse(); return s2.fetch( node.parent ) .thenCompose( parent -> { - assertNotNull( parent ); - return s2.createQuery("update Node set string = upper(string)").executeUpdate() - .thenCompose(v -> s2.refresh(node)) - .thenAccept(v -> { - assertEquals( node.getString(), "ADOPTED" ); - assertEquals( parent.getString(), "NEW PARENT" ); - assertTrue( Hibernate.isInitialized( node.elements ) ); - assertTrue( Hibernate.isInitialized( parent.elements ) ); - }); - }); - })) + assertThat( parent ).isNotNull(); + return s2.createMutationQuery( "update Node set string = upper(string)" ) + .executeUpdate() + .thenCompose( v -> s2.refresh( node ) ) + .thenAccept( v -> { + assertThat( node.getString() ).isEqualTo( "ADOPTED" ); + assertThat( parent.getString() ).isEqualTo( "NEW PARENT" ); + assertThat( Hibernate.isInitialized( node.elements ) ).isTrue(); + assertThat( Hibernate.isInitialized( parent.elements ) ).isTrue(); + } ); + } ); + } ) ) .thenCompose( v -> openSession() ) - .thenCompose(s3 -> s3.find( Node.class, basik.getId() ) + .thenCompose( s3 -> s3.find( Node.class, basik.getId() ) .thenCompose( node -> { - assertFalse( node.postUpdated && node.preUpdated ); - assertFalse( node.postPersisted && node.prePersisted ); - assertEquals( node.version, 1 ); - assertEquals( node.string, "ADOPTED"); + assertThat( node.postUpdated && node.preUpdated ).isFalse(); + assertThat( node.postPersisted && node.prePersisted ).isFalse(); + assertThat( node.version ).isEqualTo( 1 ); + assertThat( node.string ).isEqualTo( "ADOPTED" ); basik.version = node.version; basik.string = "Hello World!"; basik.parent.string = "Goodbye World!"; - return s3.merge(basik) + return s3.merge( basik ) .thenAccept( b -> { - assertEquals( b.string, "Hello World!"); - assertEquals( b.parent.string, "Goodbye World!"); - }) - .thenCompose(v -> s3.remove(node)) - .thenAccept(v -> assertTrue( !node.postRemoved && node.preRemoved ) ) - .thenCompose(v -> s3.flush()) - .thenAccept(v -> assertTrue( node.postRemoved && node.preRemoved ) ); - })) + assertThat( b.string ).isEqualTo( "Hello World!" ); + assertThat( b.parent.string ).isEqualTo( "Goodbye World!" ); + } ) + .thenCompose( v -> s3.remove( node ) ) + .thenAccept( v -> assertThat( !node.postRemoved && node.preRemoved ).isTrue() ) + .thenCompose( v -> s3.flush() ) + .thenAccept( v -> assertThat( node.postRemoved && node.preRemoved ).isTrue() ); + } ) ) .thenCompose( v -> openSession() ) - .thenCompose(s4 -> s4.find( Node.class, basik.getId() ) - .thenAccept( Assertions::assertNull)) + .thenCompose( s4 -> s4.find( Node.class, basik.getId() ) + .thenAccept( result -> assertThat( result ).isNull() ) ) ); } - @Entity(name = "Element") @Table(name="Element") + @Entity(name = "Element") + @Table(name = "Element") public static class Element { - @Id @GeneratedValue Integer id; + @Id + @GeneratedValue + Integer id; @ManyToOne Node node; @@ -176,42 +181,59 @@ public Element(Node node) { this.node = node; } - Element() {} + public Element() { + } } - @Entity(name = "Node") @Table(name="Node") + @Entity(name = "Node") + @Table(name = "Node") public static class Node { - @Id @GeneratedValue Integer id; - @Version Integer version; + @Id + @GeneratedValue + Integer id; + @Version + Integer version; String string; @ManyToOne(fetch = FetchType.LAZY, - cascade = {CascadeType.PERSIST, + cascade = { + CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE, - CascadeType.REMOVE}) + CascadeType.REMOVE + }) Node parent; @OneToMany(fetch = FetchType.EAGER, - cascade = {CascadeType.PERSIST, - CascadeType.REMOVE}, + cascade = { + CascadeType.PERSIST, + CascadeType.REMOVE + }, mappedBy = "node") List elements = new ArrayList<>(); - @Transient boolean prePersisted; - @Transient boolean postPersisted; - @Transient boolean preUpdated; - @Transient boolean postUpdated; - @Transient boolean postRemoved; - @Transient boolean preRemoved; - @Transient boolean loaded; + @Transient + boolean prePersisted; + @Transient + boolean postPersisted; + @Transient + boolean preUpdated; + @Transient + boolean postUpdated; + @Transient + boolean postRemoved; + @Transient + boolean preRemoved; + @Transient + boolean loaded; public Node(String string) { this.string = string; } - Node() {} + public Node() { + } @PrePersist void prePersist() { @@ -278,12 +300,12 @@ public boolean equals(Object o) { return false; } Node node = (Node) o; - return Objects.equals(string, node.string); + return Objects.equals( string, node.string ); } @Override public int hashCode() { - return Objects.hash(string); + return Objects.hash( string ); } } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java index e593574a0..fd05f3942 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java @@ -22,9 +22,7 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; -import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; @@ -40,13 +38,20 @@ import jakarta.persistence.Transient; import jakarta.persistence.Version; +import static jakarta.persistence.CascadeType.MERGE; +import static jakarta.persistence.CascadeType.PERSIST; +import static jakarta.persistence.CascadeType.REFRESH; +import static jakarta.persistence.CascadeType.REMOVE; +import static jakarta.persistence.FetchType.EAGER; +import static jakarta.persistence.FetchType.LAZY; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.annotations.FetchMode.SUBSELECT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @Timeout(value = 10, timeUnit = MINUTES) - public class FetchModeSubselectEagerTest extends BaseReactiveTest { @Override @@ -71,13 +76,11 @@ protected CompletionStage cleanDb() { @Test public void testEagerCollectionFetch(VertxTestContext context) { - Node basik = new Node( "Child" ); basik.parent = new Node( "Parent" ); basik.elements.add( new Element( basik ) ); basik.elements.add( new Element( basik ) ); basik.elements.add( new Element( basik ) ); - test( context, openSession() @@ -85,7 +88,7 @@ public void testEagerCollectionFetch(VertxTestContext context) { .thenCompose( v -> openSession() ) .thenCompose( s -> s.find( Node.class, basik.getId() ) ) .thenAccept( node -> { - assertTrue( Hibernate.isInitialized( node.elements ) ); + assertThat( Hibernate.isInitialized( node.elements ) ).isTrue(); assertEquals( 3, node.elements.size() ); for ( Element element : node.elements ) { assertSame( element.node, node ); @@ -96,7 +99,6 @@ public void testEagerCollectionFetch(VertxTestContext context) { @Test public void testEagerParentFetch(VertxTestContext context) { - Node basik = new Node( "Child" ); basik.parent = new Node( "Parent" ); basik.elements.add( new Element( basik ) ); @@ -110,9 +112,9 @@ public void testEagerParentFetch(VertxTestContext context) { .thenCompose( v -> openSession() ) .thenCompose( s -> s.find( Element.class, basik.elements.get( 0 ).id ) ) .thenAccept( element -> { - assertTrue( Hibernate.isInitialized( element.node ) ); - assertTrue( Hibernate.isInitialized( element.node.elements ) ); - assertEquals( 3, element.node.elements.size() ); + assertThat( Hibernate.isInitialized( element.node ) ).isTrue(); + assertThat( Hibernate.isInitialized( element.node.elements ) ).isTrue(); + assertThat( element.node.elements ).hasSize( 3 ); } ) ); } @@ -120,7 +122,6 @@ public void testEagerParentFetch(VertxTestContext context) { @Test @Disabled("NullPointerException: see https://hibernate.atlassian.net/browse/HHH-19874") public void testEagerFetchQuery(VertxTestContext context) { - Node basik = new Node( "Child" ); basik.parent = new Node( "Parent" ); basik.elements.add( new Element( basik ) ); @@ -132,21 +133,26 @@ public void testEagerFetchQuery(VertxTestContext context) { openSession() .thenCompose( s -> s.persist( basik ).thenCompose( v -> s.flush() ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createSelectionQuery( "from Node order by id", Node.class ).getResultList() ) + .thenCompose( s -> s.createSelectionQuery( "from Node order by id", Node.class ) + .getResultList() ) .thenAccept( list -> { - assertEquals( list.size(), 2 ); - assertTrue( Hibernate.isInitialized( list.get( 0 ).elements ) ); - assertEquals( list.get( 0 ).elements.size(), 3 ); - assertEquals( list.get( 1 ).elements.size(), 0 ); + assertThat( list ).hasSize( 2 ); + assertThat( Hibernate.isInitialized( list.get( 0 ).elements ) ).isTrue(); + assertThat( list.get( 0 ).elements ).hasSize( 3 ); + assertThat( list.get( 1 ).elements ).isEmpty(); } ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createSelectionQuery( - "select distinct n, e from Node n join n.elements e order by n.id", Object[].class ).getResultList() ) + .thenCompose( s -> s + .createSelectionQuery( + "select distinct n, e from Node n join n.elements e order by n.id", + Object[].class + ) + .getResultList() ) .thenAccept( list -> { - assertEquals( list.size(), 3 ); + assertThat( list ).hasSize( 3 ); Object[] tup = list.get( 0 ); assertTrue( Hibernate.isInitialized( ( (Node) tup[0] ).elements ) ); - assertEquals( ( (Node) tup[0] ).elements.size(), 3 ); + assertThat( ( (Node) tup[0] ).elements ).hasSize( 3 ); } ) ); } @@ -181,22 +187,11 @@ public static class Node { Integer version; String string; - @ManyToOne(fetch = FetchType.LAZY, - cascade = { - CascadeType.PERSIST, - CascadeType.REFRESH, - CascadeType.MERGE, - CascadeType.REMOVE - }) + @ManyToOne(fetch = LAZY, cascade = { PERSIST, REFRESH, MERGE, REMOVE }) Node parent; - @OneToMany(fetch = FetchType.EAGER, - cascade = { - CascadeType.PERSIST, - CascadeType.REMOVE - }, - mappedBy = "node") - @Fetch(FetchMode.SUBSELECT) + @OneToMany(fetch = EAGER, cascade = { PERSIST, REMOVE }, mappedBy = "node") + @Fetch(SUBSELECT) List elements = new ArrayList<>(); @Transient diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java index 31bcf2380..6fecfbb85 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinySessionTest.java @@ -10,10 +10,11 @@ import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; +import org.hibernate.HibernateException; import org.hibernate.LockMode; +import org.hibernate.exception.ConstraintViolationException; import org.hibernate.reactive.mutiny.Mutiny; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.smallrye.mutiny.Uni; @@ -22,21 +23,17 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.LockModeType; -import jakarta.persistence.PersistenceException; import jakarta.persistence.Table; +import jakarta.persistence.Version; +import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.EntityType; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; @Timeout(value = 10, timeUnit = MINUTES) - public class MutinySessionTest extends BaseReactiveTest { @Override @@ -52,34 +49,34 @@ private Uni populateDB() { private Uni selectNameFromId(Integer id) { return getMutinySessionFactory().withSession( - session -> session.createQuery("SELECT name FROM GuineaPig WHERE id = " + id ) + session -> session + .createSelectionQuery( "SELECT name FROM GuineaPig WHERE id = " + id, String.class ) .getResultList() .map( MutinySessionTest::nameFromResult ) ); } - private static String nameFromResult(List rowSet) { - switch ( rowSet.size() ) { - case 0: - return null; - case 1: - return (String) rowSet.get( 0 ); - default: - throw new AssertionError( "More than one result returned: " + rowSet.size() ); - } + private static String nameFromResult(List rowSet) { + return switch ( rowSet.size() ) { + case 0 -> null; + case 1 -> rowSet.get( 0 ); + default -> throw new AssertionError( "More than one result returned: " + rowSet.size() ); + }; } @Test public void reactiveFindMultipleIds(VertxTestContext context) { final GuineaPig rump = new GuineaPig( 55, "Rumpelstiltskin" ); final GuineaPig emma = new GuineaPig( 77, "Emma" ); - test( context, populateDB() - .chain( () -> getMutinySessionFactory().withTransaction( s -> s.persistAll( emma, rump ) ) ) - .chain( () -> getMutinySessionFactory().withTransaction( s -> s.find( GuineaPig.class, emma.getId(), rump.getId() ) ) - ) - .invoke( pigs -> { - org.assertj.core.api.Assertions.assertThat( pigs ).containsExactlyInAnyOrder( emma, rump ); - } ) + test( + context, populateDB() + .chain( () -> getMutinySessionFactory().withTransaction( s -> s + .persistAll( emma, rump ) ) + ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s + .find( GuineaPig.class, emma.getId(), rump.getId() ) + ) ) + .invoke( pigs -> assertThat( pigs ).containsExactlyInAnyOrder( emma, rump ) ) ); } @@ -125,42 +122,21 @@ public void reactiveWithTransactionSession(VertxTestContext context) { } @Test - public void reactiveFind1(VertxTestContext context) { + public void reactiveFind(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); test( context, populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, expectedPig.getId() ) - .onItem().invoke( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertTrue( session.contains( actualPig ) ); - assertFalse( session.contains( expectedPig ) ); - assertEquals( LockMode.READ, session.getLockMode( actualPig ) ); - session.detach( actualPig ); - assertFalse( session.contains( actualPig ) ); - } ) - ) ) - - ); - } - - @Test - public void reactiveFind2(VertxTestContext context) { - final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); - test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, expectedPig.getId() ) - .invoke( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertTrue( session.contains( actualPig ) ); - assertFalse( session.contains( expectedPig ) ); - assertEquals( LockMode.READ, session.getLockMode( actualPig ) ); - session.detach( actualPig ); - assertFalse( session.contains( actualPig ) ); - } ) + .call( () -> getMutinySessionFactory().withTransaction( session -> session + .find( GuineaPig.class, expectedPig.getId() ) + .invoke( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertThat( session.contains( actualPig ) ).isTrue(); + assertThat( session.contains( expectedPig ) ).isFalse(); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.READ ); + session.detach( actualPig ); + assertThat( session.contains( actualPig ) ).isFalse(); + } ) ) ) ); } @@ -169,14 +145,13 @@ public void reactiveFind2(VertxTestContext context) { public void reactiveFindWithLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_WRITE ) - .invoke( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_WRITE ); - } ) + context, populateDB() + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_WRITE ) + .invoke( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); + } ) ) ) ); } @@ -185,15 +160,14 @@ public void reactiveFindWithLock(VertxTestContext context) { public void reactiveFindRefreshWithLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, expectedPig.getId() ) - .call( pig -> session.refresh(pig, LockMode.PESSIMISTIC_WRITE) ) - .invoke( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_WRITE ); - } ) + context, populateDB() + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( GuineaPig.class, expectedPig.getId() ) + .call( pig -> session.refresh( pig, LockMode.PESSIMISTIC_WRITE ) ) + .invoke( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); + } ) ) ) ); } @@ -202,31 +176,30 @@ public void reactiveFindRefreshWithLock(VertxTestContext context) { public void reactiveFindReadOnlyRefreshWithLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, expectedPig.getId() ) + context, populateDB() + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( GuineaPig.class, expectedPig.getId() ) .call( pig -> { - session.setReadOnly(pig, true); - pig.setName("XXXX"); + session.setReadOnly( pig, true ); + pig.setName( "XXXX" ); return session.flush() - .call( v -> session.refresh(pig) ) + .call( v -> session.refresh( pig ) ) .invoke( v -> { - assertEquals(expectedPig.name, pig.name); - assertTrue(session.isReadOnly(pig)); + assertThat( pig.name ).isEqualTo( expectedPig.name ); + assertThat( session.isReadOnly( pig ) ).isTrue(); } ); } ) ) ) - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, expectedPig.getId() ) + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( GuineaPig.class, expectedPig.getId() ) .call( pig -> { - session.setReadOnly(pig, false); - pig.setName("XXXX"); + session.setReadOnly( pig, false ); + pig.setName( "XXXX" ); return session.flush() - .call( v -> session.refresh(pig) ) + .call( v -> session.refresh( pig ) ) .invoke( v -> { - assertEquals("XXXX", pig.name); - assertFalse(session.isReadOnly(pig)); + assertThat( pig.name ).isEqualTo( "XXXX" ); + assertThat( session.isReadOnly( pig ) ).isFalse(); } ); } ) ) ) @@ -237,15 +210,14 @@ public void reactiveFindReadOnlyRefreshWithLock(VertxTestContext context) { public void reactiveFindThenUpgradeLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, expectedPig.getId() ) - .call( pig -> session.lock(pig, LockMode.PESSIMISTIC_READ) ) - .invoke( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_READ ); - } ) + context, populateDB() + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( GuineaPig.class, expectedPig.getId() ) + .call( pig -> session.lock( pig, LockMode.PESSIMISTIC_READ ) ) + .invoke( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_READ ); + } ) ) ) ); } @@ -254,169 +226,114 @@ public void reactiveFindThenUpgradeLock(VertxTestContext context) { public void reactiveFindThenWriteLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, expectedPig.getId() ) - .call( pig -> session.lock(pig, LockMode.PESSIMISTIC_WRITE) ) - .invoke( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_WRITE ); - } ) + context, populateDB() + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( GuineaPig.class, expectedPig.getId() ) + .call( pig -> session.lock( pig, LockMode.PESSIMISTIC_WRITE ) ) + .invoke( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); + } ) ) ) ); } @Test - public void reactivePersist1(VertxTestContext context) { - test( - context, - getMutinySessionFactory() - .withSession( s -> s.persist( new GuineaPig( 10, "Tulip" ) ).onItem().call( s::flush ) ) - .onItem().transformToUni( v -> selectNameFromId(10) ) - .onItem().invoke( selectRes -> assertEquals( "Tulip", selectRes ) ) - ); - } - - @Test - public void reactivePersist2(VertxTestContext context) { + public void reactivePersist(VertxTestContext context) { test( - context, - getMutinySessionFactory().withSession( s -> s.persist( new GuineaPig( 10, "Tulip" ) ).chain( s::flush ) ) - .chain( () -> selectNameFromId(10) ) - .invoke( selectRes -> assertEquals( "Tulip", selectRes ) ) + context, getMutinySessionFactory() + .withSession( s -> s.persist( new GuineaPig( 10, "Tulip" ) ).call( s::flush ) ) + .chain( () -> selectNameFromId( 10 ) ) + .invoke( selectRes -> assertThat( selectRes ).isEqualTo( "Tulip" ) ) ); } @Test public void reactivePersistInTx(VertxTestContext context) { test( - context, - getMutinySessionFactory() - .withTransaction( (s,t) -> s.persist( new GuineaPig( 10, "Tulip" ) ) ) - .chain( () -> selectNameFromId(10) ) - .invoke( selectRes -> assertEquals( "Tulip", selectRes ) ) + context, getMutinySessionFactory() + .withTransaction( s -> s.persist( new GuineaPig( 10, "Tulip" ) ) ) + .chain( () -> selectNameFromId( 10 ) ) + .invoke( selectRes -> assertThat( selectRes ).isEqualTo( "Tulip" ) ) ); } @Test public void reactiveRollbackTx(VertxTestContext context) { + final RuntimeException expectedException = new RuntimeException( "For test, After flush" ); test( - context, - getMutinySessionFactory() - .withTransaction( - (s,t) -> s.persist( new ReactiveSessionTest.GuineaPig( 10, "Tulip" ) ) - .call(s::flush) - .invoke( () -> { throw new RuntimeException(); } ) - ) - .onItem().invoke( (Runnable) Assertions::fail ) - .onFailure().recoverWithItem((Void) null) - .chain( () -> selectNameFromId(10) ) - .invoke( Assertions::assertNull ) + context, assertThrown( + RuntimeException.class, getMutinySessionFactory() + .withTransaction( s -> s + .persist( new GuineaPig( 10, "Tulip" ) ) + // Flush the changes but don't commit the transaction + .call( s::flush ) + .invoke( () -> { + // Throw an exception before committing the transaction + throw expectedException; + } ) + ) + ) + .invoke( e -> assertThat( e ).hasMessage( expectedException.getMessage() ) ) + .chain( () -> selectNameFromId( 10 ) ) + .invoke( name -> assertThat( name ).isNull() ) ); } @Test public void reactiveMarkedRollbackTx(VertxTestContext context) { test( - context, - getMutinySessionFactory() - .withTransaction( - (s, t) -> s.persist( new GuineaPig( 10, "Tulip" ) ) - .call(s::flush) - .invoke(t::markForRollback) + context, getMutinySessionFactory() + .withTransaction( (s, t) -> s + .persist( new GuineaPig( 10, "Tulip" ) ) + .call( s::flush ) + .invoke( t::markForRollback ) ) - .chain( () -> selectNameFromId(10) ) - .invoke( Assertions::assertNull ) + .chain( () -> selectNameFromId( 10 ) ) + .invoke( name -> assertThat( name ).isNull() ) ); } @Test - public void reactiveRemoveTransientEntity1(VertxTestContext context) { + public void reactiveRemoveTransientEntity(VertxTestContext context) { test( - context, - populateDB() - .onItem().call( () -> selectNameFromId(5).onItem().invoke( Assertions::assertNotNull ) ) + context, populateDB() + .chain( () -> selectNameFromId( 5 ) ) + .invoke( name -> assertThat( name ).isNotNull() ) .chain( this::openMutinySession ) - .onItem().call( session -> session.remove( new GuineaPig( 5, "Aloi" ) ) ) - .onItem().invoke( (Runnable) Assertions::fail ) - .onFailure().recoverWithItem( () -> null ) -// .onItem().invokeUni( session -> session.flush() ) -// .onTermination().invoke( (session, err, c) -> session.close() ) -// .onItem().invokeUni( v -> selectNameFromId( 5 ).onItem().invoke( context::assertNull ) ) - ); - } - - @Test - public void reactiveRemoveTransientEntity2(VertxTestContext context) { - test( - context, - populateDB() - .chain( () -> selectNameFromId(5) ) - .invoke( Assertions::assertNotNull ) - .chain( this::openMutinySession ) - .call( session -> session.remove( new GuineaPig( 5, "Aloi" ) ) ) - .onItem().invoke( (Runnable) Assertions::fail ) - .onFailure().recoverWithItem( () -> null ) -// .chain( session -> session.flush().eventually(session::close) ) -// .then( () -> selectNameFromId( 5 ) ) -// .invoke( context::assertNull ) - ); - } - - @Test - public void reactiveRemoveManagedEntity1(VertxTestContext context) { - test( - context, - populateDB() - .onItem().call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, 5 ) - .onItem().call(session::remove) - .onItem().call(session::flush) + .chain( session -> assertThrown( + HibernateException.class, + session.remove( new GuineaPig( 5, "Aloi" ) ) ) ) - .onItem().call( () -> selectNameFromId(5).onItem().invoke( Assertions::assertNull ) ) - ); - } - - @Test - public void reactiveRemoveManagedEntity2(VertxTestContext context) { - test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, 5 ) - .chain(session::remove) - .call(session::flush) - ) ) - .chain( () -> selectNameFromId(5) ) - .invoke( Assertions::assertNull ) + .invoke( e -> assertThat( e ) + .hasMessageContaining( "unmanaged instance passed to remove" ) + ) ); } @Test - public void reactiveRemoveManagedEntityWithTx1(VertxTestContext context) { + public void reactiveRemoveManagedEntity(VertxTestContext context) { test( - context, - populateDB() - .onItem().call( () -> getMutinySessionFactory().withTransaction( - (session, transaction) -> session.find( GuineaPig.class, 5 ) - .onItem().call(session::remove) + context, populateDB() + .call( () -> getMutinySessionFactory().withTransaction( session -> session + .find( GuineaPig.class, 5 ) + .call( session::remove ) ) ) - .onItem().call( () -> selectNameFromId(5).onItem().invoke( Assertions::assertNull ) ) + .chain( () -> selectNameFromId( 5 ) ) + .invoke( name -> assertThat( name ).isNull() ) ); } @Test - public void reactiveRemoveManagedEntityWithTx2(VertxTestContext context) { + public void reactiveRemoveManagedEntityWithTx(VertxTestContext context) { test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withTransaction( - (session, transaction) -> session.find( GuineaPig.class, 5 ) - .call(session::remove) + context, populateDB() + .call( () -> getMutinySessionFactory().withTransaction( session -> session + .find( GuineaPig.class, 5 ) + .call( session::remove ) ) ) - .chain( () -> selectNameFromId(5) ) - .invoke( Assertions::assertNull ) + .chain( () -> selectNameFromId( 5 ) ) + .invoke( name -> assertThat( name ).isNull() ) ); } @@ -424,21 +341,19 @@ public void reactiveRemoveManagedEntityWithTx2(VertxTestContext context) { public void reactiveUpdate(VertxTestContext context) { final String NEW_NAME = "Tina"; test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, 5 ) - .map( pig -> { - assertNotNull( pig ); - // Checking we are actually changing the name - assertNotEquals( pig.getName(), NEW_NAME ); - pig.setName( NEW_NAME ); - return null; - } ) - .call(session::flush) + context, populateDB() + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( GuineaPig.class, 5 ) + .invoke( pig -> { + assertThat( pig ).isNotNull(); + // Checking we are actually changing the name + assertThat( pig.getName() ).isNotEqualTo( NEW_NAME ); + pig.setName( NEW_NAME ); + } ) + .call( session::flush ) ) ) - .chain( () -> selectNameFromId(5) ) - .invoke( name -> assertEquals( NEW_NAME, name ) ) + .chain( () -> selectNameFromId( 5 ) ) + .invoke( name -> assertThat( name ).isEqualTo( NEW_NAME ) ) ); } @@ -446,21 +361,22 @@ public void reactiveUpdate(VertxTestContext context) { public void reactiveUpdateVersion(VertxTestContext context) { final String NEW_NAME = "Tina"; test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withSession( - session -> session.find( GuineaPig.class, 5 ) - .map( pig -> { - assertNotNull( pig ); - // Checking we are actually changing the name - assertNotEquals( pig.getName(), NEW_NAME ); - pig.setName( NEW_NAME ); - return null; - } ) - .call(session::flush) - ) ) - .chain( () -> selectNameFromId(5) ) - .invoke( name -> assertEquals( NEW_NAME, name ) ) + context, populateDB() + .call( () -> getMutinySessionFactory().withSession( session -> session + .find( GuineaPig.class, 5 ) + .invoke( pig -> { + assertThat( pig ).isNotNull(); + // Checking we are actually changing the name + assertThat( pig.getName() ).isNotEqualTo( NEW_NAME ); + assertThat( pig.version ).isEqualTo( 0 ); + pig.setName( NEW_NAME ); + pig.version = 10; //ignored by Hibernate + } ) + .call( session::flush ) ) + ) + .chain( () -> getMutinySessionFactory() + .withSession( s -> s.find( GuineaPig.class, 5 ) ) ) + .invoke( pig -> assertThat( pig.version ).isEqualTo( 1 ) ) ); } @@ -468,16 +384,15 @@ public void reactiveUpdateVersion(VertxTestContext context) { public void reactiveQueryWithLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withTransaction( - (session, tx) -> session.createSelectionQuery( "from GuineaPig pig", GuineaPig.class) - .setLockMode( LockModeType.PESSIMISTIC_WRITE ) - .getSingleResult() - .invoke( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_WRITE ); - } ) + context, populateDB().call( () -> getMutinySessionFactory() + .withTransaction( session -> session + .createSelectionQuery( "from GuineaPig pig", GuineaPig.class ) + .setLockMode( LockModeType.PESSIMISTIC_WRITE ) + .getSingleResult() + .invoke( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); + } ) ) ) ); } @@ -486,16 +401,15 @@ public void reactiveQueryWithLock(VertxTestContext context) { public void reactiveQueryWithAliasedLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); test( - context, - populateDB() - .call( () -> getMutinySessionFactory().withTransaction( - (session, tx) -> session.createSelectionQuery( "from GuineaPig pig", GuineaPig.class) - .setLockMode("pig", LockMode.PESSIMISTIC_WRITE ) - .getSingleResult() - .invoke( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_WRITE ); - } ) + context, populateDB().call( () -> getMutinySessionFactory() + .withTransaction( session -> session + .createSelectionQuery( "from GuineaPig pig", GuineaPig.class ) + .setLockMode( "pig", LockMode.PESSIMISTIC_WRITE ) + .getSingleResult() + .invoke( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); + } ) ) ) ); } @@ -507,144 +421,173 @@ public void reactiveMultiQuery(VertxTestContext context) { GuineaPig baz = new GuineaPig( 7, "Baz" ); AtomicInteger i = new AtomicInteger(); - test( context, - getMutinySessionFactory() - .withTransaction( (session, transaction) -> session.persistAll(foo, bar, baz) ) - .call( () -> getMutinySessionFactory().withSession( - session -> session.createSelectionQuery("from GuineaPig", GuineaPig.class) - .getResultList().onItem().disjoint() - .invoke( pig -> { - assertNotNull(pig); - i.getAndIncrement(); - } ) - .collect().asList() - .invoke( list -> { - assertEquals(3, i.get()); - assertEquals(3, list.size()); - } ) + test( + context, getMutinySessionFactory() + .withTransaction( session -> session.persistAll( foo, bar, baz ) ) + .call( () -> getMutinySessionFactory().withSession( session -> session + .createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList().onItem().disjoint() + .invoke( pig -> { + assertThat( pig ).isNotNull(); + i.getAndIncrement(); + } ) + .collect().asList() + .invoke( list -> { + assertThat( i.get() ).isEqualTo( 3 ); + assertThat( list.size() ).isEqualTo( 3 ); + } ) ) ) ); } @Test public void reactiveClose(VertxTestContext context) { - test( context, openMutinySession() - .invoke( session -> assertTrue( session.isOpen() ) ) - .call( Mutiny.Session::close ) - .invoke( session -> assertFalse( session.isOpen() ) ) + test( + context, openMutinySession() + .invoke( session -> assertThat( session.isOpen() ).isTrue() ) + .call( Mutiny.Session::close ) + .invoke( session -> assertThat( session.isOpen() ).isFalse() ) ); } - @Test void testFactory(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> { - session.getFactory().getCache().evictAll(); - session.getFactory().getMetamodel().entity(GuineaPig.class); - session.getFactory().getCriteriaBuilder().createQuery(GuineaPig.class); - session.getFactory().getStatistics().isStatisticsEnabled(); - return Uni.createFrom().voidItem(); - } ) ); + @Test + void testFactory(VertxTestContext context) { + test( + context, getMutinySessionFactory().withSession( session -> { + session.getFactory().getCache().evictAll(); + session.getFactory().getMetamodel().entity( GuineaPig.class ); + session.getFactory().getCriteriaBuilder().createQuery( GuineaPig.class ); + session.getFactory().getStatistics().isStatisticsEnabled(); + return Uni.createFrom().voidItem(); + } ) + ); } @Test public void testMetamodel() { - EntityType pig = getSessionFactory().getMetamodel().entity(GuineaPig.class); - assertNotNull(pig); - assertEquals( 2, pig.getAttributes().size() ); - assertEquals( "GuineaPig", pig.getName() ); + EntityType pig = getSessionFactory().getMetamodel().entity( GuineaPig.class ); + assertThat( pig ).isNotNull(); + assertThat( pig.getAttributes() ) + .map( Attribute::getName ) + .containsExactlyInAnyOrder( "id", "version", "name" ); + assertThat( pig.getName() ).isEqualTo( "GuineaPig" ); } @Test public void testTransactionPropagation(VertxTestContext context) { - test( context, getMutinySessionFactory().withTransaction( - (session, transaction) -> session.createSelectionQuery("from GuineaPig", GuineaPig.class).getResultList() - .chain( list -> { - assertNotNull( session.currentTransaction() ); - assertFalse( session.currentTransaction().isMarkedForRollback() ); - session.currentTransaction().markForRollback(); - assertTrue( session.currentTransaction().isMarkedForRollback() ); - assertTrue( transaction.isMarkedForRollback() ); - return session.withTransaction( t -> { - assertTrue( t.isMarkedForRollback() ); - return session.createSelectionQuery("from GuineaPig", GuineaPig.class).getResultList(); - } ); - } ) - ) ); + test( + context, getMutinySessionFactory() + .withTransaction( (session, transaction) -> session + .createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList() + .chain( list -> { + assertThat( session.currentTransaction() ).isNotNull(); + assertThat( session.currentTransaction().isMarkedForRollback() ).isFalse(); + session.currentTransaction().markForRollback(); + assertThat( session.currentTransaction().isMarkedForRollback() ).isTrue(); + assertThat( session.currentTransaction().isMarkedForRollback() ).isTrue(); + assertThat( transaction.isMarkedForRollback() ).isTrue(); + return session.withTransaction( t -> { + assertThat( t.isMarkedForRollback() ).isTrue(); + return session + .createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList(); + } ); + } ) + ) + ); } @Test public void testSessionPropagation(VertxTestContext context) { - test( context, getMutinySessionFactory().withSession( session -> { - assertFalse( session.isDefaultReadOnly() ); - session.setDefaultReadOnly(true); - return session.createSelectionQuery("from GuineaPig", GuineaPig.class).getResultList() - .chain( list -> getMutinySessionFactory().withSession(s -> { - assertTrue( s.isDefaultReadOnly() ); - return s.createSelectionQuery("from GuineaPig", GuineaPig.class).getResultList(); - } ) ); - } ) ); + test( + context, getMutinySessionFactory() + .withSession( session -> { + assertThat( session.isDefaultReadOnly() ).isFalse(); + session.setDefaultReadOnly( true ); + return session + .createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList() + .chain( list -> getMutinySessionFactory().withSession( s -> { + assertThat( s.isDefaultReadOnly() ).isTrue(); + return s.createSelectionQuery( "from GuineaPig", GuineaPig.class ) + .getResultList(); + } ) ); + } ) + ); } @Test public void testDupeException(VertxTestContext context) { test( - context, - getMutinySessionFactory() - .withTransaction((s, t) -> s.persist( new GuineaPig( 10, "Tulip" ) )) - .chain(() -> getMutinySessionFactory() - .withTransaction((s, t) -> s.persist( new GuineaPig( 10, "Tulip" ) )) - ).onItemOrFailure().invoke((i, t) -> { - assertNotNull(t); - assertTrue(t instanceof PersistenceException); - }) - .onFailure().recoverWithNull() + // It would make sense to check the error message, but it changes based on the database selected. + // I think this is good enough for now. + context, assertThrown( + ConstraintViolationException.class, getMutinySessionFactory() + .withTransaction( s -> s + .persist( new GuineaPig( 10, "Tulip" ) ) + ) + .chain( () -> getMutinySessionFactory().withTransaction( s -> s + .persist( new GuineaPig( 10, "Tulip" ) ) ) + ) + ) ); } @Test public void testExceptionInWithSession(VertxTestContext context) { final Mutiny.Session[] savedSession = new Mutiny.Session[1]; - test( context, getMutinySessionFactory() - .withSession( session -> { - assertTrue( session.isOpen() ); - savedSession[0] = session; - throw new RuntimeException( "No Panic: This is just a test" ); - } ) - .onItem().invoke( () -> fail( "Test should throw an exception" ) ) - .onFailure() - .recoverWithNull() - .invoke( () -> assertFalse( savedSession[0].isOpen(), "Session should be closed" ) ) + test( + context, assertThrown( + RuntimeException.class, getMutinySessionFactory() + .withSession( session -> { + assertThat( session.isOpen() ).isTrue(); + savedSession[0] = session; + throw new RuntimeException( "No Panic: This is just a test" ); + } ) + ) + .invoke( e -> assertThat( e ).hasMessageContaining( "No Panic:" ) ) + .invoke( () -> assertThat( savedSession[0].isOpen() ) + .as( "Session should be closed" ) + .isFalse() ) ); } @Test public void testExceptionInWithTransaction(VertxTestContext context) { final Mutiny.Session[] savedSession = new Mutiny.Session[1]; - test( context, getMutinySessionFactory() - .withTransaction( (session, tx) -> { - assertTrue( session.isOpen() ); - savedSession[0] = session; - throw new RuntimeException( "No Panic: This is just a test" ); - } ) - .onItem().invoke( () -> fail( "Test should throw an exception" ) ) - .onFailure() - .recoverWithNull() - .invoke( () -> assertFalse( savedSession[0].isOpen(), "Session should be closed" ) ) + test( + context, assertThrown( + RuntimeException.class, getMutinySessionFactory() + .withTransaction( (session, tx) -> { + assertThat( session.isOpen() ).isTrue(); + savedSession[0] = session; + throw new RuntimeException( "No Panic: This is just a test" ); + } ) + ) + .invoke( e -> assertThat( e ).hasMessageContaining( "No Panic:" ) ) + .invoke( () -> assertThat( savedSession[0].isOpen() ) + .as( "Session should be closed" ) + .isFalse() ) ); } @Test public void testExceptionInWithStatelessSession(VertxTestContext context) { final Mutiny.StatelessSession[] savedSession = new Mutiny.StatelessSession[1]; - test( context, getMutinySessionFactory() - .withStatelessSession( session -> { - assertTrue( session.isOpen() ); - savedSession[0] = session; - throw new RuntimeException( "No Panic: This is just a test" ); - } ) - .onItem().invoke( () -> fail( "Test should throw an exception" ) ) - .onFailure() - .recoverWithNull() - .invoke( () -> assertFalse( savedSession[0].isOpen(), "Session should be closed" ) ) + test( + context, assertThrown( + RuntimeException.class, getMutinySessionFactory() + .withStatelessSession( session -> { + assertThat( session.isOpen() ).isTrue(); + savedSession[0] = session; + throw new RuntimeException( "No Panic: This is just a test" ); + } ) + ) + .invoke( e -> assertThat( e ).hasMessageContaining( "No Panic:" ) ) + .invoke( () -> assertThat( savedSession[0].isOpen() ) + .as( "Session should be closed" ) + .isFalse() ) ); } @@ -653,58 +596,64 @@ public void testForceFlushWithDelete(VertxTestContext context) { // Pig1 and Pig2 must have the same id final GuineaPig pig1 = new GuineaPig( 111, "Pig 1" ); final GuineaPig pig2 = new GuineaPig( 111, "Pig 2" ); - - test( context, getMutinySessionFactory() - .withTransaction( session -> session - .persist( pig1 ) - .call( () -> session.remove( pig1 ) ) - // pig 2 has the same id as pig1. - // If pig1 has not been removed from the session, - // we will have a duplicated id exception - .call( () -> session.persist( pig2 ) ) - ) - .chain( () -> getMutinySessionFactory() - .withSession( s -> s.find( GuineaPig.class, pig2.getId() ) ) ) - .invoke( result -> assertThatPigsAreEqual( pig2, result ) ) + test( + context, getMutinySessionFactory() + .withTransaction( session -> session + .persist( pig1 ) + .call( () -> session.remove( pig1 ) ) + // pig 2 has the same id as pig1. + // If pig1 has not been removed from the session, + // we will have a duplicated id exception + .call( () -> session.persist( pig2 ) ) + ) + .chain( () -> getMutinySessionFactory() + .withSession( s -> s.find( GuineaPig.class, pig2.getId() ) ) ) + .invoke( result -> assertThatPigsAreEqual( pig2, result ) ) ); } @Test public void testCurrentSession(VertxTestContext context) { - test( context, - getMutinySessionFactory().withSession(session -> - getMutinySessionFactory().withSession(s -> { - assertEquals(session, s); - Mutiny.Session currentSession = getMutinySessionFactory().getCurrentSession(); - assertNotNull(currentSession); - assertTrue(currentSession.isOpen()); - assertEquals(session, currentSession); - return Uni.createFrom().voidItem(); - }).invoke(() -> assertNotNull(getMutinySessionFactory().getCurrentSession())) - ).invoke(() -> assertNull(getMutinySessionFactory().getCurrentSession())) + test( + context, getMutinySessionFactory() + .withSession( session -> getMutinySessionFactory() + .withSession( s -> { + assertThat( s ).isEqualTo( session ); + Mutiny.Session currentSession = getMutinySessionFactory().getCurrentSession(); + assertThat( currentSession ).isNotNull(); + assertThat( currentSession.isOpen() ).isTrue(); + assertThat( currentSession ).isEqualTo( session ); + return Uni.createFrom().voidItem(); + } ) + .invoke( () -> assertThat( getMutinySessionFactory().getCurrentSession() ).isNotNull() ) + ) + .invoke( () -> assertThat( getMutinySessionFactory().getCurrentSession() ).isNull() ) ); } @Test public void testCurrentStatelessSession(VertxTestContext context) { - test( context, - getMutinySessionFactory().withStatelessSession(session -> - getMutinySessionFactory().withStatelessSession(s -> { - assertEquals(session, s); - Mutiny.StatelessSession currentSession = getMutinySessionFactory().getCurrentStatelessSession(); - assertNotNull(currentSession); - assertTrue(currentSession.isOpen()); - assertEquals(session, currentSession); - return Uni.createFrom().voidItem(); - }).invoke(() -> assertNotNull(getMutinySessionFactory().getCurrentStatelessSession())) - ).invoke(() -> assertNull(getMutinySessionFactory().getCurrentStatelessSession())) + test( + context, getMutinySessionFactory() + .withStatelessSession( session -> getMutinySessionFactory() + .withStatelessSession( s -> { + assertEquals( session, s ); + Mutiny.StatelessSession currentSession = getMutinySessionFactory().getCurrentStatelessSession(); + assertThat( currentSession ).isNotNull(); + assertThat( currentSession.isOpen() ).isTrue(); + assertThat( currentSession ).isEqualTo( session ); + return Uni.createFrom().voidItem(); + } ) + .invoke( () -> assertThat( getMutinySessionFactory().getCurrentStatelessSession() ).isNotNull() ) + ) + .invoke( () -> assertThat( getMutinySessionFactory().getCurrentStatelessSession() ).isNull() ) ); } private void assertThatPigsAreEqual(GuineaPig expected, GuineaPig actual) { - assertNotNull( actual ); - assertEquals( expected.getId(), actual.getId() ); - assertEquals( expected.getName(), actual.getName() ); + assertThat( actual ).isNotNull(); + assertThat( actual.getId() ).isEqualTo( expected.getId() ); + assertThat( actual.getName() ).isEqualTo( expected.getName() ); } @Entity(name = "GuineaPig") @@ -714,6 +663,9 @@ public static class GuineaPig { private Integer id; private String name; + @Version + private int version; + public GuineaPig() { } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantNoResolverTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantNoResolverTest.java index 81f3469cb..e3ad22d3d 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantNoResolverTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantNoResolverTest.java @@ -69,27 +69,28 @@ protected Configuration constructConfiguration() { @Test public void reactivePersistFindDelete(VertxTestContext context) { final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" ); - test( - context, - getSessionFactory().openSession( DEFAULT.name() ) - .thenCompose( session -> session - .persist( guineaPig ) - .thenCompose( v -> session.flush() ) - .thenAccept( v -> session.detach( guineaPig ) ) - .thenAccept( v -> assertFalse( session.contains( guineaPig ) ) ) - .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) - .thenAccept( actualPig -> { - assertThatPigsAreEqual( guineaPig, actualPig ); - assertTrue( session.contains( actualPig ) ); - assertFalse( session.contains( guineaPig ) ); - assertEquals( LockMode.READ, session.getLockMode( actualPig ) ); - session.detach( actualPig ); - assertFalse( session.contains( actualPig ) ); - } ) - .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) - .thenCompose( session::remove ) - .thenCompose( v -> session.flush() ) - .thenCompose( v -> session.close() ) ) + test( context, getSessionFactory() + .openSession( DEFAULT.name() ) + .thenCompose( session -> session + .withTransaction( t -> session + .persist( guineaPig ) + .thenCompose( v -> session.flush() ) + .thenAccept( v -> session.detach( guineaPig ) ) + .thenAccept( v -> assertFalse( session.contains( guineaPig ) ) ) + .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) + .thenAccept( actualPig -> { + assertThatPigsAreEqual( guineaPig, actualPig ); + assertTrue( session.contains( actualPig ) ); + assertFalse( session.contains( guineaPig ) ); + assertEquals( LockMode.READ, session.getLockMode( actualPig ) ); + session.detach( actualPig ); + assertFalse( session.contains( actualPig ) ); + } ) + .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) + .thenCompose( session::remove ) + ) + .thenCompose( v -> session.close() ) + ) ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java index 84e7e92c9..4173668be 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java @@ -63,23 +63,25 @@ public void reactivePersistFindDelete(VertxTestContext context) { final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" ); test( context, - getSessionFactory().openSession().thenCompose( session -> session - .persist( guineaPig ) - .thenCompose( v -> session.flush() ) - .thenAccept( v -> session.detach( guineaPig ) ) - .thenAccept( v -> assertFalse( session.contains( guineaPig ) ) ) - .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) - .thenAccept( actualPig -> { - assertThatPigsAreEqual( guineaPig, actualPig ); - assertTrue( session.contains( actualPig ) ); - assertFalse( session.contains( guineaPig ) ); - assertEquals( LockMode.READ, session.getLockMode( actualPig ) ); - session.detach( actualPig ); - assertFalse( session.contains( actualPig ) ); - } ) - .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) - .thenCompose( session::remove ) - .thenCompose( v -> session.flush() ) ) + getSessionFactory().openSession() + .thenCompose( session -> session.withTransaction( t -> session + .persist( guineaPig ) + .thenCompose( v -> session.flush() ) + .thenAccept( v -> session.detach( guineaPig ) ) + .thenAccept( v -> assertFalse( session.contains( guineaPig ) ) ) + .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) + .thenAccept( actualPig -> { + assertThatPigsAreEqual( guineaPig, actualPig ); + assertTrue( session.contains( actualPig ) ); + assertFalse( session.contains( guineaPig ) ); + assertEquals( LockMode.READ, session.getLockMode( actualPig ) ); + session.detach( actualPig ); + assertFalse( session.contains( actualPig ) ); + } ) + .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) + .thenCompose( session::remove ) + .thenCompose( v -> session.flush() ) ) + ) ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java index ed0c724f1..b471ab1b0 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java @@ -14,7 +14,6 @@ import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.stage.Stage; -import org.hibernate.reactive.util.impl.CompletionStages; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; @@ -32,10 +31,10 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.junit.jupiter.api.Assertions.*; @Timeout(value = 10, timeUnit = MINUTES) - public class ReactiveSessionTest extends BaseReactiveTest { @Override @@ -341,28 +340,20 @@ public void reactiveFindWithPessimisticIncrementLock(VertxTestContext context) { @Test public void reactiveFindWithOptimisticIncrementLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); - test( - context, - populateDB() - .thenCompose( v -> getSessionFactory().withTransaction( - (session, transaction) -> session.find( - GuineaPig.class, - expectedPig.getId(), - LockMode.OPTIMISTIC_FORCE_INCREMENT - ) - .thenAccept( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( - LockMode.OPTIMISTIC_FORCE_INCREMENT, - session.getLockMode( actualPig ) - ); - assertEquals( 0, actualPig.version ); - } ) - ) + test( context, populateDB() + .thenCompose( v -> getSessionFactory() + .withTransaction( session -> session + .find( GuineaPig.class, expectedPig.getId(), LockMode.OPTIMISTIC_FORCE_INCREMENT ) + .thenAccept( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertEquals( LockMode.OPTIMISTIC_FORCE_INCREMENT, session.getLockMode( actualPig ) ); + assertEquals( 0, actualPig.version ); + } ) ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 1, actualPig.version ) ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) + .thenAccept( actualPig -> assertEquals( 1, actualPig.version ) ) ); } @@ -800,7 +791,7 @@ context, getSessionFactory().withSession( session -> { session.getFactory().getMetamodel().entity( GuineaPig.class ); session.getFactory().getCriteriaBuilder().createQuery( GuineaPig.class ); session.getFactory().getStatistics().isStatisticsEnabled(); - return CompletionStages.voidFuture(); + return voidFuture(); } ) ); } @@ -809,7 +800,8 @@ context, getSessionFactory().withSession( session -> { public void testTransactionPropagation(VertxTestContext context) { test( context, getSessionFactory().withTransaction( - (session, transaction) -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) + (session, transaction) -> session + .createSelectionQuery( "from GuineaPig", GuineaPig.class ) .getResultList() .thenCompose( list -> { assertNotNull( session.currentTransaction() ); @@ -974,7 +966,7 @@ public void testCurrentSession(VertxTestContext context) { assertNotNull(currentSession); assertTrue(currentSession.isOpen()); assertEquals(session, currentSession); - return CompletionStages.voidFuture(); + return voidFuture(); }) .thenAccept(v -> assertNotNull(getSessionFactory().getCurrentSession())) ) @@ -992,7 +984,7 @@ public void testCurrentStatelessSession(VertxTestContext context) { assertNotNull(currentSession); assertTrue(currentSession.isOpen()); assertEquals(session, currentSession); - return CompletionStages.voidFuture(); + return voidFuture(); }) .thenAccept(v -> assertNotNull(getSessionFactory().getCurrentStatelessSession())) ) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/BasicTypesAndCallbacksForAllDBsTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/BasicTypesAndCallbacksForAllDBsTest.java index 3ff20c2b3..62064c33f 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/BasicTypesAndCallbacksForAllDBsTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/types/BasicTypesAndCallbacksForAllDBsTest.java @@ -31,7 +31,6 @@ import org.hibernate.reactive.BaseReactiveTest; import org.hibernate.reactive.annotations.DisabledFor; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.vertx.junit5.VertxTestContext; @@ -60,17 +59,13 @@ import jakarta.persistence.Transient; import jakarta.persistence.Version; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.testing.ReactiveAssertions.assertWithTruncationThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test all the types and lifecycle callbacks that we expect to work on all supported DBs */ - public class BasicTypesAndCallbacksForAllDBsTest extends BaseReactiveTest { @Override @@ -79,14 +74,15 @@ protected Set> annotatedEntities() { } private void testField(VertxTestContext context, Basic original, Consumer consumer) { - test( context, getSessionFactory() - .withTransaction( (s, t) -> s.persist( original ) ) - .thenCompose( v -> getSessionFactory().withSession( s -> s - .find( Basic.class, original.id ) - .thenAccept( found -> { - assertNotNull( found ); - consumer.accept( found ); - } ) ) ) + test( + context, getSessionFactory() + .withTransaction( (s, t) -> s.persist( original ) ) + .thenCompose( v -> getSessionFactory().withSession( s -> s + .find( Basic.class, original.id ) + .thenAccept( found -> { + assertThat( found ).isNotNull(); + consumer.accept( found ); + } ) ) ) ); } @@ -96,7 +92,7 @@ public void testStringType(VertxTestContext context) { Basic basic = new Basic(); basic.string = string; - testField( context, basic, found -> assertEquals( string, found.string ) ); + testField( context, basic, found -> assertThat( found.string ).isEqualTo( string ) ); } @Test @@ -105,10 +101,12 @@ public void testIntegerType(VertxTestContext context) { basic.primitiveInt = Integer.MIN_VALUE; basic.fieldInteger = Integer.MAX_VALUE; - testField( context, basic, found -> { - assertEquals( Integer.MIN_VALUE, found.primitiveInt ); - assertEquals( Integer.MAX_VALUE, found.fieldInteger ); - } ); + testField( + context, basic, found -> { + assertThat( found.primitiveInt ).isEqualTo( Integer.MIN_VALUE ); + assertThat( found.fieldInteger ).isEqualTo( Integer.MAX_VALUE ); + } + ); } @Test @@ -117,10 +115,12 @@ public void testLongType(VertxTestContext context) { basic.primitiveLong = Long.MIN_VALUE; basic.fieldLong = Long.MAX_VALUE; - testField( context, basic, found -> { - assertEquals( Long.MIN_VALUE, found.primitiveLong ); - assertEquals( Long.MAX_VALUE, found.fieldLong ); - } ); + testField( + context, basic, found -> { + assertThat( found.primitiveLong ).isEqualTo( Long.MIN_VALUE ); + assertThat( found.fieldLong ).isEqualTo( Long.MAX_VALUE ); + } + ); } @Test @@ -132,10 +132,12 @@ public void testFloatType(VertxTestContext context) { basic.primitiveFloat = 10.02f; basic.fieldFloat = 12.562f; - testField( context, basic, found -> { - assertEquals( primitiveFloat, found.primitiveFloat ); - assertEquals( fieldFloat, found.fieldFloat ); - } ); + testField( + context, basic, found -> { + assertThat( found.primitiveFloat ).isEqualTo( primitiveFloat ); + assertThat( found.fieldFloat ).isEqualTo( fieldFloat ); + } + ); } @Test @@ -147,10 +149,12 @@ public void testDoubleType(VertxTestContext context) { basic.primitiveDouble = primitiveDouble; basic.fieldDouble = fieldDouble; - testField( context, basic, found -> { - assertEquals( primitiveDouble, found.primitiveDouble ); - assertEquals( fieldDouble, found.fieldDouble ); - } ); + testField( + context, basic, found -> { + assertThat( found.primitiveDouble ).isEqualTo( primitiveDouble ); + assertThat( found.fieldDouble ).isEqualTo( fieldDouble ); + } + ); } @Test @@ -162,13 +166,15 @@ public void testBooleanType(VertxTestContext context) { basic.booleanYesNo = Boolean.FALSE; basic.booleanNumeric = Boolean.FALSE; - testField( context, basic, found -> { - assertEquals( true, found.primitiveBoolean ); - assertEquals( Boolean.FALSE, found.fieldBoolean ); - assertEquals( Boolean.FALSE, found.booleanTrueFalse ); - assertEquals( Boolean.FALSE, found.booleanYesNo ); - assertEquals( Boolean.FALSE, found.booleanNumeric ); - } ); + testField( + context, basic, found -> { + assertThat( found.primitiveBoolean ).isEqualTo( true ); + assertThat( found.fieldBoolean ).isEqualTo( Boolean.FALSE ); + assertThat( found.booleanTrueFalse ).isEqualTo( Boolean.FALSE ); + assertThat( found.booleanYesNo ).isEqualTo( Boolean.FALSE ); + assertThat( found.booleanNumeric ).isEqualTo( Boolean.FALSE ); + } + ); } @Test @@ -182,11 +188,13 @@ public void testBytesType(VertxTestContext context) { basic.primitiveBytes = primitiveBytes; basic.fieldByte = fieldByte; - testField( context, basic, found -> { - assertEquals( primitiveByte, found.primitiveByte ); - assertTrue( Objects.deepEquals( primitiveBytes, found.primitiveBytes ) ); - assertEquals( fieldByte, found.fieldByte ); - } ); + testField( + context, basic, found -> { + assertThat( found.primitiveByte ).isEqualTo( primitiveByte ); + assertThat( Objects.deepEquals( primitiveBytes, found.primitiveBytes ) ).isTrue(); + assertThat( found.fieldByte ).isEqualTo( fieldByte ); + } + ); } @Test @@ -195,7 +203,7 @@ public void testURL(VertxTestContext context) throws Exception { Basic basic = new Basic(); basic.url = url; - testField( context, basic, found -> assertEquals( url, found.url ) ); + testField( context, basic, found -> assertThat( found.url ).isEqualTo( url ) ); } @Test @@ -204,7 +212,7 @@ public void testDateType(VertxTestContext context) { Basic basic = new Basic(); basic.date = date; - testField( context, basic, found -> assertEquals( date, found.date ) ); + testField( context, basic, found -> assertThat( found.date ).isEqualTo( date ) ); } @Test @@ -213,10 +221,12 @@ public void testDateAsTimestampType(VertxTestContext context) { Basic basic = new Basic(); basic.dateAsTimestamp = date; - testField( context, basic, found -> { - assertTrue( found.dateAsTimestamp instanceof Timestamp ); - assertEquals( date, found.dateAsTimestamp ); - } ); + testField( + context, basic, found -> { + assertThat( found.dateAsTimestamp ).isInstanceOf( Timestamp.class ); + assertThat( found.dateAsTimestamp ).isEqualTo( new Timestamp( date.getTime() ) ); + } + ); } @Test @@ -225,15 +235,15 @@ public void testTimeZoneType(VertxTestContext context) { Basic basic = new Basic(); basic.timeZone = timeZone; - testField( context, basic, found -> assertEquals( basic.timeZone, found.timeZone ) ); + testField( context, basic, found -> assertThat( found.timeZone ).isEqualTo( basic.timeZone ) ); } @Test public void testCalendarAsDateType(VertxTestContext context) { Calendar calendar = GregorianCalendar.getInstance(); - calendar.set( Calendar.DAY_OF_MONTH, 15); - calendar.set( Calendar.MONTH, 7); - calendar.set( Calendar.YEAR, 2002); + calendar.set( Calendar.DAY_OF_MONTH, 15 ); + calendar.set( Calendar.MONTH, 7 ); + calendar.set( Calendar.YEAR, 2002 ); // TemporalType#Date only deals with year/month/day int expectedYear = calendar.get( Calendar.YEAR ); @@ -243,11 +253,13 @@ public void testCalendarAsDateType(VertxTestContext context) { Basic basic = new Basic(); basic.calendarAsDate = calendar; - testField( context, basic, found -> { - assertEquals( expectedDay, found.calendarAsDate.get( Calendar.DAY_OF_MONTH ) ); - assertEquals( expectedMonth, found.calendarAsDate.get( Calendar.MONTH ) ); - assertEquals( expectedYear, found.calendarAsDate.get( Calendar.YEAR ) ); - } ); + testField( + context, basic, found -> { + assertThat( found.calendarAsDate.get( Calendar.DAY_OF_MONTH ) ).isEqualTo( expectedDay ); + assertThat( found.calendarAsDate.get( Calendar.MONTH ) ).isEqualTo( expectedMonth ); + assertThat( found.calendarAsDate.get( Calendar.YEAR ) ).isEqualTo( expectedYear ); + } + ); } @Test @@ -260,10 +272,12 @@ public void testCalendarAsTimestampType(VertxTestContext context) { Basic basic = new Basic(); basic.calendarAsTimestamp = calendar; - testField( context, basic, found -> { - String actual = format( found.calendarAsTimestamp ); - assertEquals( expected, actual ); - } ); + testField( + context, basic, found -> { + String actual = format( found.calendarAsTimestamp ); + assertThat( actual ).isEqualTo( expected ); + } + ); } private static String format(Calendar calendar) { @@ -277,7 +291,7 @@ public void testLocalDateType(VertxTestContext context) { Basic basic = new Basic(); basic.localDate = now; - testField( context, basic, found -> assertEquals( now, found.localDate ) ); + testField( context, basic, found -> assertThat( found.localDate ).isEqualTo( now ) ); } @Test @@ -289,7 +303,7 @@ public void testLocalDateTimeType(VertxTestContext context) { Basic basic = new Basic(); basic.localDateTime = now; - testField( context, basic, found -> assertEquals( now, found.localDateTime ) ); + testField( context, basic, found -> assertThat( found.localDateTime ).isEqualTo( now ) ); } @Test @@ -299,11 +313,13 @@ public void testEnumType(VertxTestContext context) { basic.coverAsOrdinal = Cover.HARD; basic.coverAsString = Cover.SOFT; - testField( context, basic, found -> { - assertEquals( Cover.HARDER, found.cover ); - assertEquals( Cover.HARD, found.coverAsOrdinal ); - assertEquals( Cover.SOFT, found.coverAsString ); - } ); + testField( + context, basic, found -> { + assertThat( found.cover ).isEqualTo( Cover.HARDER ); + assertThat( found.coverAsOrdinal ).isEqualTo( Cover.HARD ); + assertThat( found.coverAsString ).isEqualTo( Cover.SOFT ); + } + ); } @Test @@ -312,9 +328,11 @@ public void testEmbeddableType(VertxTestContext context) { Basic basic = new Basic(); basic.embed = embed; - testField( context, basic, found -> { - assertEquals( embed, found.embed ); - } ); + testField( + context, basic, found -> { + assertThat( found.embed ).isEqualTo( embed ); + } + ); } @Test @@ -322,9 +340,11 @@ public void testBigIntegerWithConverterType(VertxTestContext context) { Basic basic = new Basic(); basic.bigIntegerAsString = BigInteger.TEN; - testField( context, basic, found -> { - assertEquals( BigInteger.TEN.floatValue(), found.bigIntegerAsString.floatValue() ); - } ); + testField( + context, basic, found -> { + assertThat( found.bigIntegerAsString.floatValue() ).isEqualTo( BigInteger.TEN.floatValue() ); + } + ); } @Test @@ -332,9 +352,11 @@ public void testBigDecimalWithUserType(VertxTestContext context) { Basic basic = new Basic(); basic.bigDecimalAsString = BigDecimal.TEN; - testField( context, basic, found -> { - assertEquals( BigInteger.TEN.floatValue(), found.bigDecimalAsString.floatValue() ); - } ); + testField( + context, basic, found -> { + assertThat( found.bigDecimalAsString.floatValue() ).isEqualTo( BigInteger.TEN.floatValue() ); + } + ); } @Test @@ -344,10 +366,12 @@ public void testSerializableType(VertxTestContext context) { Basic basic = new Basic(); basic.thing = thing; - testField( context, basic, found -> { - assertTrue( found.thing instanceof String[] ); - assertTrue( Objects.deepEquals( thing, found.thing ) ); - } ); + testField( + context, basic, found -> { + assertThat( found.thing instanceof String[] ).isTrue(); + assertThat( Objects.deepEquals( thing, found.thing ) ).isTrue(); + } + ); } @Test @@ -355,7 +379,7 @@ public void testUUIDType(VertxTestContext context) { Basic basic = new Basic(); basic.uuid = UUID.fromString( "123e4567-e89b-42d3-a456-556642440000" ); - testField( context, basic, found -> assertEquals( basic.uuid, found.uuid ) ); + testField( context, basic, found -> assertThat( found.uuid ).isEqualTo( basic.uuid ) ); } @Test @@ -363,15 +387,19 @@ public void testDecimalType(VertxTestContext context) { Basic basic = new Basic(); basic.bigDecimal = new BigDecimal( "12.12" ); - testField( context, basic, found -> assertEquals( basic.bigDecimal.floatValue(), found.bigDecimal.floatValue() ) ); + testField( + context, + basic, + found -> assertThat( found.bigDecimal.floatValue() ).isEqualTo( basic.bigDecimal.floatValue() ) + ); } @Test public void testBigIntegerType(VertxTestContext context) { Basic basic = new Basic(); - basic.bigInteger = BigInteger.valueOf( 123L); + basic.bigInteger = BigInteger.valueOf( 123L ); - testField( context, basic, found -> assertEquals( basic.bigInteger, found.bigInteger ) ); + testField( context, basic, found -> assertThat( found.bigInteger ).isEqualTo( basic.bigInteger ) ); } @Test @@ -379,10 +407,9 @@ public void testLocalTimeType(VertxTestContext context) { Basic basic = new Basic(); basic.localTime = LocalTime.now(); - testField( context, basic, found -> assertEquals( - basic.localTime.truncatedTo( ChronoUnit.MINUTES ), - found.localTime.truncatedTo( ChronoUnit.MINUTES ) - ) ); + testField( context, basic, found -> assertThat( found.localTime.truncatedTo( ChronoUnit.MINUTES ) ) + .isEqualTo( basic.localTime.truncatedTo( ChronoUnit.MINUTES ) ) + ); } @Test @@ -392,11 +419,13 @@ public void testDateAsTimeType(VertxTestContext context) { Basic basic = new Basic(); basic.dateAsTime = date; - testField( context, basic, found -> { - SimpleDateFormat timeSdf = new SimpleDateFormat( "HH:mm:ss" ); - assertTrue( found.dateAsTime instanceof Time); - assertEquals( timeSdf.format( date ), timeSdf.format( found.dateAsTime ) ); - } ); + testField( + context, basic, found -> { + SimpleDateFormat timeSdf = new SimpleDateFormat( "HH:mm:ss" ); + assertThat( found.dateAsTime instanceof Time ).isTrue(); + assertThat( timeSdf.format( found.dateAsTime ) ).isEqualTo( timeSdf.format( date ) ); + } + ); } @Test @@ -404,10 +433,12 @@ public void testDuration(VertxTestContext context) { Basic basic = new Basic(); basic.duration = Duration.ofMillis( 1894657L ); - testField( context, basic, found -> { - assertNotNull( found.duration ); - assertEquals( basic.duration, found.duration ); - } ); + testField( + context, basic, found -> { + assertThat( found.duration ).isNotNull(); + assertThat( found.duration ).isEqualTo( basic.duration ); + } + ); } @Test @@ -416,10 +447,12 @@ public void testInstant(VertxTestContext context) { Basic basic = new Basic(); basic.instant = Instant.now(); - testField( context, basic, found -> { - assertNotNull( found.instant ); - assertWithTruncationThat( found.instant ).isEqualTo( basic.instant ); - } ); + testField( + context, basic, found -> { + assertThat( found.instant ).isNotNull(); + assertWithTruncationThat( found.instant ).isEqualTo( basic.instant ); + } + ); } @Test @@ -430,61 +463,61 @@ public void testCallbacksAndVersioning(VertxTestContext context) { basik.parent = parent; test( - context, - openSession() - .thenCompose( s -> s.persist( basik.parent ).thenCompose( v -> s.persist( basik ) ) - .thenAccept( v -> assertTrue( basik.prePersisted && !basik.postPersisted ) ) - .thenAccept( v -> assertTrue( basik.parent.prePersisted && !basik.parent.postPersisted ) ) + context, getSessionFactory() + .withSession( s -> s.persist( basik.parent ).thenCompose( v -> s.persist( basik ) ) + .thenAccept( v -> assertThat( basik.prePersisted && !basik.postPersisted ).isTrue() ) + .thenAccept( v -> assertThat( basik.parent.prePersisted && !basik.parent.postPersisted ).isTrue() ) .thenCompose( v -> s.flush() ) - .thenAccept( v -> assertTrue( basik.prePersisted && basik.postPersisted ) ) - .thenAccept( v -> assertTrue( basik.parent.prePersisted && basik.parent.postPersisted ) ) + .thenAccept( v -> assertThat( basik.prePersisted && basik.postPersisted ).isTrue() ) + .thenAccept( v -> assertThat( basik.parent.prePersisted && basik.parent.postPersisted ).isTrue() ) ) - .thenCompose( v -> openSession() - .thenCompose( s2 -> s2.find( Basic.class, basik.getId() ) - .thenCompose( basic -> { - assertNotNull( basic ); - assertTrue( basic.loaded ); - assertEquals( basic.string, basik.string ); - assertEquals( basic.cover, basik.cover ); - assertEquals( basic.version, 0 ); - - basic.string = "Goodbye"; - basic.cover = Cover.SOFT; - basic.parent = new Basic( "New Parent" ); - return s2.persist( basic.parent ) - .thenCompose( vv -> s2.flush() ) - .thenAccept( vv -> { - assertNotNull( basic ); - assertTrue( basic.postUpdated && basic.preUpdated ); - assertFalse( basic.postPersisted && basic.prePersisted ); - assertTrue( basic.parent.postPersisted && basic.parent.prePersisted ); - assertEquals( basic.version, 1 ); - } ); - } ) - ) ) - .thenCompose( v -> openSession() - .thenCompose( s3 -> s3.find( Basic.class, basik.getId() ) - .thenCompose( basic -> { - assertFalse( basic.postUpdated && basic.preUpdated ); - assertFalse( basic.postPersisted && basic.prePersisted ); - assertEquals( basic.version, 1 ); - assertEquals( basic.string, "Goodbye" ); - return s3.remove( basic ) - .thenAccept( vv -> assertTrue( !basic.postRemoved && basic.preRemoved ) ) - .thenCompose( vv -> s3.flush() ) - .thenAccept( vv -> assertTrue( basic.postRemoved && basic.preRemoved ) ); - } ) - ) ) - .thenCompose( v -> openSession() - .thenCompose( s4 -> s4.find( Basic.class, basik.getId() ) ) - .thenAccept( Assertions::assertNull ) ) + .thenCompose( v -> getSessionFactory().withSession( s2 -> s2 + .find( Basic.class, basik.getId() ) + .thenCompose( basic -> { + assertThat( basic ).isNotNull(); + assertThat( basic.loaded ).isTrue(); + assertThat( basic.string ).isEqualTo( basik.string ); + assertThat( basic.cover ).isEqualTo( basik.cover ); + assertThat( basic.version ).isEqualTo( 0 ); + + basic.string = "Goodbye"; + basic.cover = Cover.SOFT; + basic.parent = new Basic( "New Parent" ); + return s2.persist( basic.parent ) + .thenCompose( vv -> s2.flush() ) + .thenAccept( vv -> { + assertThat( basic ).isNotNull(); + assertThat( basic.postUpdated && basic.preUpdated ).isTrue(); + assertThat( basic.postPersisted && basic.prePersisted ).isFalse(); + assertThat( basic.parent.postPersisted && basic.parent.prePersisted ).isTrue(); + assertThat( basic.version ).isEqualTo( 1 ); + } ); + } ) + ) ) + .thenCompose( v -> getSessionFactory().withSession( s3 -> s3 + .find( Basic.class, basik.getId() ) + .thenCompose( basic -> { + assertThat( basic.postUpdated && basic.preUpdated ).isFalse(); + assertThat( basic.postPersisted && basic.prePersisted ).isFalse(); + assertThat( basic.version ).isEqualTo( 1 ); + assertThat( basic.string ).isEqualTo( "Goodbye" ); + return s3.remove( basic ) + .thenAccept( vv -> assertThat( !basic.postRemoved && basic.preRemoved ).isTrue() ) + .thenCompose( vv -> s3.flush() ) + .thenAccept( vv -> assertThat( basic.postRemoved && basic.preRemoved ).isTrue() ); + } ) + ) ) + .thenCompose( v -> getSessionFactory().withSession( s4 -> s4 + .find( Basic.class, basik.getId() ) + .thenAccept( result -> assertThat( result ).isNull() ) + ) ) ); } enum Cover {HARDER, HARD, SOFT} @Embeddable - static class Embed { + public static class Embed { String one; String two; @@ -608,12 +641,12 @@ private static class Basic { UUID uuid; - @Column(name="dessimal") + @Column(name = "dessimal") BigDecimal bigDecimal; - @Column(name="inteja") + @Column(name = "inteja") BigInteger bigInteger; - @Column(name="localtyme") + @Column(name = "localtyme") private LocalTime localTime; @Temporal(TemporalType.TIME) Date dateAsTime; diff --git a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java index 681455c5c..fe69f3534 100644 --- a/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java +++ b/integration-tests/bytecode-enhancements-it/src/test/java/org/hibernate/reactive/it/LazyBasicFieldTest.java @@ -98,12 +98,14 @@ public void testFetchBasicFieldAlsoInitializesIt(VertxTestContext context) { .find( Crew.class, emily.getId() ) .call( crew -> session.fetch( crew, Crew_.role ) .invoke( role -> assertThat( role ).isEqualTo( emily.getRole() ) ) ) - .invoke( () -> sqlTracker.clear() ) - .call( crew -> session.fetch( crew, Crew_.role ) + .invoke( sqlTracker::clear ) + .call( crew -> session + .fetch( crew, Crew_.role ) .invoke( role -> { // No select query expected, the previous fetch must have initialized the role attribute - assertThat(sqlTracker.getLoggedQueries()).hasSize( 0 ); - assertThat( role ).isEqualTo( emily.getRole() );} ) + assertThat( sqlTracker.getLoggedQueries() ).hasSize( 0 ); + assertThat( role ).isEqualTo( emily.getRole() ); + } ) ) ) ) ); } @@ -116,11 +118,17 @@ public void testTransparentLazyFetching(VertxTestContext context) { emily.setRole( "Passenger" ); emily.setFate( "Unknown" ); - test( context, assertThrown( LazyInitializationException.class, getMutinySessionFactory() - .withTransaction( session -> session.persist( emily ) ) - .call( () -> getMutinySessionFactory().withSession( session -> session.find( Crew.class, emily.getId() ) - .invoke( Crew::getRole ) ) ) - ).invoke( exception -> assertThat( exception.getMessage() ).contains( "Reactive sessions do not support transparent lazy fetching" ) ) + test( context, assertThrown( LazyInitializationException.class, + getMutinySessionFactory().withTransaction( session -> session.persist( emily ) ) + .chain( () -> getMutinySessionFactory().withSession( session -> session + .find( Crew.class, emily.getId() ) + // getRole() must throw a LazyInitializationException because we are not using + // Mutiny.fetch to load a lazy field + .map( Crew::getRole ) ) ) + ).invoke( exception -> assertThat( exception ) + .as( "Expected LazyInitializationException not thrown" ) + .hasMessageContaining( "Reactive sessions do not support transparent lazy fetching" ) + ) ); } @@ -132,14 +140,21 @@ public void testGetReferenceAndTransparentLazyFetching(VertxTestContext context) emily.setRole( "Passenger" ); emily.setFate( "Unknown" ); - test( context, assertThrown( LazyInitializationException.class, getMutinySessionFactory() - .withTransaction( session -> session.persist( emily ) ) - .chain( () -> getMutinySessionFactory().withSession( session -> { - Crew crew = session.getReference( Crew.class, emily.getId() ); - String role = crew.getRole(); - return session.flush(); - } ) ) - ).invoke( exception -> assertThat( exception.getMessage() ).contains( "Reactive sessions do not support transparent lazy fetching" ) ) + test( + context, assertThrown( + LazyInitializationException.class, getMutinySessionFactory() + .withTransaction( session -> session.persist( emily ) ) + .chain( () -> getMutinySessionFactory().withSession( session -> { + Crew crew = session.getReference( Crew.class, emily.getId() ); + // getRole() must throw a LazyInitializationException because we are not using + // Mutiny.fetch to load a lazy field + String role = crew.getRole(); + return Uni.createFrom() + .failure( new AssertionError( "Expected LazyInitializationException not thrown" ) ); + } ) ) + ).invoke( exception -> assertThat( exception ) + .as( "Expected LazyInitializationException not thrown" ) + .hasMessageContaining( "Reactive sessions do not support transparent lazy fetching" ) ) ); } From f6c848f91d68ba8814c5d2a651320f4b5bd9e3a5 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 20 Oct 2025 14:10:34 +0200 Subject: [PATCH 154/185] [#2640] Update CancelSignalTest I don't know if this test actually makes sense: the cancel opertion happens in a different thread that causes some error when calling session.close because the thread doesn't match the one the session was open. I'm going to keep it because it was introduced to solve #1436 but we might need to review it again in the future. --- .../hibernate/reactive/CancelSignalTest.java | 104 +++++++++--------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CancelSignalTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CancelSignalTest.java index d1ae51355..933bc51bb 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CancelSignalTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CancelSignalTest.java @@ -5,16 +5,19 @@ */ package org.hibernate.reactive; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Queue; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.stream.IntStream; + + import org.junit.jupiter.api.Test; @@ -27,73 +30,76 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; -import static java.util.Arrays.stream; import static java.util.concurrent.CompletableFuture.allOf; import static java.util.concurrent.CompletableFuture.runAsync; -import static java.util.stream.Stream.concat; import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; public class CancelSignalTest extends BaseReactiveTest { private static final Logger LOG = Logger.getLogger( CancelSignalTest.class ); + private static final int EXECUTION_SIZE = 10; + @Override protected Collection> annotatedEntities() { return List.of( GuineaPig.class ); } + @Override + public CompletionStage deleteEntities(Class... entities) { + // We don't need to delete anything + return voidFuture(); + } + @Test public void cleanupConnectionWhenCancelSignal(VertxTestContext context) { // larger than 'sql pool size' to check entering the 'pool waiting queue' - int executeSize = 10; CountDownLatch firstSessionWaiter = new CountDownLatch( 1 ); Queue cancellableQueue = new ConcurrentLinkedQueue<>(); - ExecutorService withSessionExecutor = Executors.newFixedThreadPool( executeSize ); - // Create some jobs that are going to be cancelled asynchronously - CompletableFuture[] withSessionFutures = IntStream - .range( 0, executeSize ) - .mapToObj( i -> runAsync( - () -> { - CountDownLatch countDownLatch = new CountDownLatch( 1 ); - Cancellable cancellable = getMutinySessionFactory() - .withSession( s -> { - LOG.debug( "start withSession: " + i ); - sleep( 100 ); - firstSessionWaiter.countDown(); - return s.find( GuineaPig.class, 1 ); - } ) - .onTermination().invoke( () -> { - countDownLatch.countDown(); - LOG.debug( "future " + i + " terminated" ); - } ) - .subscribe().with( item -> LOG.debug( "end withSession: " + i ) ); - cancellableQueue.add( cancellable ); - await( countDownLatch ); - }, - withSessionExecutor - ) ) - .toArray( CompletableFuture[]::new ); - - // Create jobs that are going to cancel the previous ones - ExecutorService cancelExecutor = Executors.newFixedThreadPool( executeSize ); - CompletableFuture[] cancelFutures = IntStream - .range( 0, executeSize ) - .mapToObj( i -> runAsync( - () -> { - await( firstSessionWaiter ); - cancellableQueue.poll().cancel(); - sleep( 500 ); - }, - cancelExecutor - ) ) - .toArray( CompletableFuture[]::new ); - - CompletableFuture allFutures = allOf( concat( stream( withSessionFutures ), stream( cancelFutures ) ) - .toArray( CompletableFuture[]::new ) - ); + final List> allFutures = new ArrayList<>(); + + ExecutorService withSessionExecutor = Executors.newFixedThreadPool( EXECUTION_SIZE ); + for ( int j = 0; j < EXECUTION_SIZE; j++ ) { + final int i = j; + allFutures.add( runAsync( () -> { + CountDownLatch countDownLatch = new CountDownLatch( 1 ); + Cancellable cancellable = getMutinySessionFactory() + .withSession( s -> { + LOG.info( "start withSession: " + i ); + sleep( 100 ); + firstSessionWaiter.countDown(); + return s.find( GuineaPig.class, 1 ); + } ) + .onCancellation().invoke( () -> { + LOG.info( "future " + i + " cancelled" ); + countDownLatch.countDown(); + } ) + .subscribe() + // We cancelled the job, it shouldn't really finish + .with( item -> LOG.info( "end withSession: " + i ) ); + cancellableQueue.add( cancellable ); + await( countDownLatch ); + }, + withSessionExecutor + ) ); + } - // Test that there shouldn't be any pending process - test( context, allFutures.thenAccept( x -> assertThat( sqlPendingMetric() ).isEqualTo( 0.0 ) ) ); + ExecutorService cancelExecutor = Executors.newFixedThreadPool( EXECUTION_SIZE ); + for ( int i = 0; i < EXECUTION_SIZE; i++ ) { + allFutures.add( runAsync( () -> { + await( firstSessionWaiter ); + cancellableQueue.poll().cancel(); + sleep( 500 ); + }, + cancelExecutor + ) ); + } + + test( + context, allOf( allFutures.toArray( new CompletableFuture[0] ) ) + .thenAccept( x -> assertThat( sqlPendingMetric() ).isEqualTo( 0.0 ) ) + ); } private static double sqlPendingMetric() { From f735aa9ff29431aab4cecfa0642491c964aaf002 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 20 Oct 2025 14:10:52 +0200 Subject: [PATCH 155/185] [#2640] Disable CascadeTest#testQuery --- .../src/test/java/org/hibernate/reactive/CascadeTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java index 3c09f4243..f5af9e048 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java @@ -12,6 +12,7 @@ import org.hibernate.Hibernate; import org.hibernate.cfg.Configuration; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -49,6 +50,7 @@ protected Configuration constructConfiguration() { } @Test + @Disabled("It also fails in Hibernate ORM, see https://hibernate.atlassian.net/browse/HHH-19868") public void testQuery(VertxTestContext context) { Node basik = new Node( "Child" ); basik.parent = new Node( "Parent" ); From cefe8d5b5db099b1c28442684539264efb156d36 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:52:08 +0000 Subject: [PATCH 156/185] Bump cockroachdb/cockroach from v25.3.2 to v25.3.3 in /tooling/docker Bumps cockroachdb/cockroach from v25.3.2 to v25.3.3. --- updated-dependencies: - dependency-name: cockroachdb/cockroach dependency-version: v25.3.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- tooling/docker/cockroachdb.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile index 484d65520..86f837a83 100644 --- a/tooling/docker/cockroachdb.Dockerfile +++ b/tooling/docker/cockroachdb.Dockerfile @@ -1,3 +1,3 @@ # CockroachDB # See https://hub.docker.com/r/cockroachdb/cockroach -FROM docker.io/cockroachdb/cockroach:v25.3.2 +FROM docker.io/cockroachdb/cockroach:v25.3.3 From d9df06da93e821f92154a8e06a3a5976dca09bc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Oct 2025 09:52:51 +0000 Subject: [PATCH 157/185] Bump the vertx group with 10 updates Bumps the vertx group with 10 updates: | Package | From | To | | --- | --- | --- | | [io.vertx:vertx-db2-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.21` | `4.5.22` | | [io.vertx:vertx-junit5](https://github.com/eclipse-vertx/vertx-junit5) | `4.5.21` | `4.5.22` | | [io.vertx:vertx-micrometer-metrics](https://github.com/vert-x3/vertx-micrometer-metrics) | `4.5.21` | `4.5.22` | | [io.vertx:vertx-mssql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.21` | `4.5.22` | | [io.vertx:vertx-mysql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.21` | `4.5.22` | | [io.vertx:vertx-oracle-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.21` | `4.5.22` | | [io.vertx:vertx-pg-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.21` | `4.5.22` | | [io.vertx:vertx-sql-client](https://github.com/eclipse-vertx/vertx-sql-client) | `4.5.21` | `4.5.22` | | [io.vertx:vertx-web](https://github.com/vert-x3/vertx-web) | `4.5.21` | `4.5.22` | | [io.vertx:vertx-web-client](https://github.com/vert-x3/vertx-web) | `4.5.21` | `4.5.22` | Updates `io.vertx:vertx-db2-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-junit5` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-junit5/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-micrometer-metrics` from 4.5.21 to 4.5.22 - [Commits](https://github.com/vert-x3/vertx-micrometer-metrics/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-mssql-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-mysql-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-oracle-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-pg-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-sql-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-junit5` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-junit5/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-micrometer-metrics` from 4.5.21 to 4.5.22 - [Commits](https://github.com/vert-x3/vertx-micrometer-metrics/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-mssql-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-mysql-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-oracle-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-pg-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-sql-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/eclipse-vertx/vertx-sql-client/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-web` from 4.5.21 to 4.5.22 - [Commits](https://github.com/vert-x3/vertx-web/compare/4.5.21...4.5.22) Updates `io.vertx:vertx-web-client` from 4.5.21 to 4.5.22 - [Commits](https://github.com/vert-x3/vertx-web/compare/4.5.21...4.5.22) --- updated-dependencies: - dependency-name: io.vertx:vertx-db2-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-junit5 dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-micrometer-metrics dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mssql-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mysql-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-oracle-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-pg-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-sql-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-junit5 dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-micrometer-metrics dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mssql-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-mysql-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-oracle-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-pg-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-sql-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-web dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx - dependency-name: io.vertx:vertx-web-client dependency-version: 4.5.22 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: vertx ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a0d7d0570..a48e25a97 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,9 +9,9 @@ junitVersion = "6.0.0" junitPlatformVersion = "1.13.4" log4jVersion = "2.25.2" testcontainersVersion = "1.21.3" -vertxSqlClientVersion = "4.5.21" -vertxWebVersion= "4.5.21" -vertxWebClientVersion = "4.5.21" +vertxSqlClientVersion = "4.5.22" +vertxWebVersion= "4.5.22" +vertxWebClientVersion = "4.5.22" [libraries] com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } From 0c209f6774de0ee45575fee8818562896b859093 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 23 Oct 2025 13:11:11 +0200 Subject: [PATCH 158/185] [#2649] CascadeTest#testQuery throws NullPointerException --- .../test/java/org/hibernate/reactive/CascadeTest.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java index f5af9e048..9845a6c96 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CascadeTest.java @@ -5,6 +5,7 @@ */ package org.hibernate.reactive; +import jakarta.persistence.Tuple; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -12,7 +13,6 @@ import org.hibernate.Hibernate; import org.hibernate.cfg.Configuration; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -50,7 +50,6 @@ protected Configuration constructConfiguration() { } @Test - @Disabled("It also fails in Hibernate ORM, see https://hibernate.atlassian.net/browse/HHH-19868") public void testQuery(VertxTestContext context) { Node basik = new Node( "Child" ); basik.parent = new Node( "Parent" ); @@ -66,13 +65,13 @@ public void testQuery(VertxTestContext context) { .thenCompose( s -> s.createQuery( "select distinct n from Node n left join fetch n.elements", Node.class ).getResultList() ) .thenAccept( list -> assertThat( list ).hasSize( 2 ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createQuery( "select distinct n, e from Node n join n.elements e", Node.class ).getResultList() ) + .thenCompose( s -> s.createQuery( "select distinct n, e from Node n join n.elements e", Tuple.class ).getResultList() ) .thenAccept( list -> assertThat( list ).hasSize( 3 ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createQuery( "select distinct n.id, e.id from Node n join n.elements e", Node.class ).getResultList() ) + .thenCompose( s -> s.createQuery( "select distinct n.id, e.id from Node n join n.elements e", Tuple.class ).getResultList() ) .thenAccept( list -> assertThat( list ).hasSize( 3 ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.createQuery( "select max(e.id), min(e.id), sum(e.id) from Node n join n.elements e group by n.id order by n.id", Node.class ).getResultList() ) + .thenCompose( s -> s.createQuery( "select max(e.id), min(e.id), sum(e.id) from Node n join n.elements e group by n.id order by n.id", Tuple.class ).getResultList() ) .thenAccept( list -> assertThat( list ).hasSize( 1 ) ) ); } From 324634a7d79c360c2a251bcbe6270ae190159b0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 03:57:28 +0000 Subject: [PATCH 159/185] Bump mysql/community-server from 9.4.0 to 9.5.0 in /tooling/docker Bumps mysql/community-server from 9.4.0 to 9.5.0. --- updated-dependencies: - dependency-name: mysql/community-server dependency-version: 9.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tooling/docker/mysql.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/mysql.Dockerfile b/tooling/docker/mysql.Dockerfile index 897fbd5ee..03918b8e3 100644 --- a/tooling/docker/mysql.Dockerfile +++ b/tooling/docker/mysql.Dockerfile @@ -1,3 +1,3 @@ # MySQL # See https://hub.docker.com/_/mysql -FROM container-registry.oracle.com/mysql/community-server:9.4.0 +FROM container-registry.oracle.com/mysql/community-server:9.5.0 From 1f16b8186b6c9874601fddb604d416cbe4f3889f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 03:18:44 +0000 Subject: [PATCH 160/185] Bump io.smallrye.reactive:mutiny from 2.9.4 to 2.9.5 in the mutiny group Bumps the mutiny group with 1 update: [io.smallrye.reactive:mutiny](https://github.com/smallrye/smallrye-mutiny). Updates `io.smallrye.reactive:mutiny` from 2.9.4 to 2.9.5 - [Release notes](https://github.com/smallrye/smallrye-mutiny/releases) - [Commits](https://github.com/smallrye/smallrye-mutiny/compare/2.9.4...2.9.5) --- updated-dependencies: - dependency-name: io.smallrye.reactive:mutiny dependency-version: 2.9.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: mutiny ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a48e25a97..b3746bc3f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" } com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.2.1.jre11" } com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.4.0" } com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" } -io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.4" } +io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.5" } io-vertx-vertx-db2-client = { group = "io.vertx", name = "vertx-db2-client", version.ref = "vertxSqlClientVersion" } io-vertx-vertx-junit5 = { group = "io.vertx", name = "vertx-junit5", version.ref = "vertxSqlClientVersion" } io-vertx-vertx-micrometer-metrics = { group = "io.vertx", name = "vertx-micrometer-metrics", version.ref = "vertxSqlClientVersion" } From 7f8bc602a0c776e463e3ef53de128379d6226687 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 03:18:55 +0000 Subject: [PATCH 161/185] Bump com.mysql:mysql-connector-j in the testcontainers group Bumps the testcontainers group with 1 update: [com.mysql:mysql-connector-j](https://github.com/mysql/mysql-connector-j). Updates `com.mysql:mysql-connector-j` from 9.4.0 to 9.5.0 - [Changelog](https://github.com/mysql/mysql-connector-j/blob/release/9.x/CHANGES) - [Commits](https://github.com/mysql/mysql-connector-j/compare/9.4.0...9.5.0) --- updated-dependencies: - dependency-name: com.mysql:mysql-connector-j dependency-version: 9.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b3746bc3f..a150ca902 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,7 @@ vertxWebClientVersion = "4.5.22" com-fasterxml-jackson-core-jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jacksonDatabindVersion" } com-ibm-db2-jcc = { group = "com.ibm.db2", name = "jcc", version = "12.1.2.0" } com-microsoft-sqlserver-mssql-jdbc = { group = "com.microsoft.sqlserver", name = "mssql-jdbc", version = "13.2.1.jre11" } -com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.4.0" } +com-mysql-mysql-connector-j = { group = "com.mysql", name = "mysql-connector-j", version = "9.5.0" } com-ongres-scram-client = { group = "com.ongres.scram", name = "client", version = "2.1" } io-smallrye-reactive-mutiny = { group = "io.smallrye.reactive", name = "mutiny", version = "2.9.5" } io-vertx-vertx-db2-client = { group = "io.vertx", name = "vertx-db2-client", version.ref = "vertxSqlClientVersion" } From 1952b308cdd5efded87a564552f9fd3070fc17a6 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 28 Oct 2025 11:49:47 +0100 Subject: [PATCH 162/185] [#2570] No longer publish docs to https://hibernate.org --- release/build.gradle | 94 +++----------------------------------------- 1 file changed, 5 insertions(+), 89 deletions(-) diff --git a/release/build.gradle b/release/build.gradle index 00b43d2f4..25163e6e9 100644 --- a/release/build.gradle +++ b/release/build.gradle @@ -1,28 +1,13 @@ import java.nio.charset.StandardCharsets -ext { - // Select which repository to use for publishing the documentation - // Example: - // ./gradlew uploadDocumentation \ - // -PdocPublishRepoUri="git@github.com:DavideD/hibernate.org.git" \ - // -PdocPublishBranch="staging" - if ( !project.hasProperty('docPublishRepoUri') ) { - docPublishRepoUri = 'git@github.com:hibernate/hibernate.org.git' - } - if ( !project.hasProperty('docPublishBranch') ) { - docPublishBranch = 'staging' - } -} - description = 'Release module' // (Optional) before uploading the documentation, you can check // the generated website under release/build/hibernate.org with: // ./gradlew gitPublishCopy -// To publish the documentation: -// 1. Add the relevant SSH key to your SSH agent. -// 2. Execute this: -// ./gradlew uploadDocumentation -PdocPublishBranch=production +// The generated documentation are copied to the +// rootProject.layout.buildDirectory.dir("staging-deploy/documentation") by the updateDocumentationTask +// while the publishing is delegated to the https://github.com/hibernate/hibernate-release-scripts // To tag a version and trigger a release on CI (which will include publishing to Bintray and publishing documentation): // ./gradlew ciRelease -PreleaseVersion=x.y.z.Final -PdevelopmentVersion=x.y.z-SNAPSHOT -PgitRemote=origin -PgitBranch=main @@ -33,9 +18,6 @@ final Directory documentationDir = project(":documentation").layout.buildDirecto // Relative path on the static website where the documentation is located final String docWebsiteRelativePath = "reactive/documentation/${projectVersion.family}" -// The location of the docs when the website has been cloned -final Directory docWebsiteReactiveFolder = project.layout.buildDirectory.dir( "docs-website/${docWebsiteRelativePath}" ).get() - def releaseChecksTask = tasks.register( "releaseChecks" ) { description 'Checks and preparation for release' group 'Release' @@ -98,30 +80,6 @@ def assembleDocumentationTask = tasks.register( 'assembleDocumentation' ) { } assemble.dependsOn assembleDocumentationTask -/** -* Clone the website -*/ -def removeDocsWebsiteTask = tasks.register( 'removeDocsWebsite', Delete ) { - delete project.layout.buildDirectory.dir( "docs-website" ) -} - -def cloneDocsWebsiteTask = tasks.register( 'cloneDocsWebsite', Exec ) { - dependsOn removeDocsWebsiteTask - // Assure that the buildDir exists. Otherwise this task will fail. - dependsOn compileJava - workingDir project.layout.buildDirectory - commandLine 'git', 'clone', docPublishRepoUri, '-b', docPublishBranch, '--sparse', '--depth', '1', 'docs-website' -} - -def sparseCheckoutDocumentationTask = tasks.register( 'sparseCheckoutDocumentation', Exec ) { - dependsOn cloneDocsWebsiteTask - workingDir project.layout.buildDirectory.dir( "docs-website" ) - commandLine 'git', 'sparse-checkout', 'set', docWebsiteRelativePath -} - -/** -* Update the docs on the cloned website -*/ def changeToReleaseVersionTask = tasks.register( 'changeToReleaseVersion' ) { description 'Updates `gradle/version.properties` file to the specified release-version' group 'Release' @@ -136,7 +94,7 @@ def changeToReleaseVersionTask = tasks.register( 'changeToReleaseVersion' ) { def updateDocumentationTask = tasks.register( 'updateDocumentation' ) { description "Update the documentation on the cloned static website" - dependsOn assembleDocumentationTask, sparseCheckoutDocumentationTask + dependsOn assembleDocumentationTask mustRunAfter changeToReleaseVersion // copy documentation outputs into target/documentation: @@ -144,26 +102,13 @@ def updateDocumentationTask = tasks.register( 'updateDocumentation' ) { // * it is also used as a base to build a staged directory for documentation upload doLast { - // delete the folders in case some files have been removed - delete docWebsiteReactiveFolder.dir("javadocs"), docWebsiteReactiveFolder.dir("reference") - // Aggregated JavaDoc - copy { - from documentationDir.dir("javadocs") - into docWebsiteReactiveFolder.dir("javadocs") - } - copy { from documentationDir.dir("javadocs") into rootProject.layout.buildDirectory.dir("staging-deploy/documentation/javadocs") } // Reference Documentation - copy { - from documentationDir.dir("asciidoc/reference/html_single") - into docWebsiteReactiveFolder.dir("reference/html_single") - } - copy { from documentationDir.dir("asciidoc/reference/html_single") into rootProject.layout.buildDirectory.dir("staging-deploy/documentation/reference/html_single") @@ -171,35 +116,6 @@ def updateDocumentationTask = tasks.register( 'updateDocumentation' ) { } } -def stageDocChangesTask = tasks.register( 'stageDocChanges', Exec ) { - dependsOn updateDocumentationTask - workingDir project.layout.buildDirectory.dir( "docs-website" ) - commandLine 'git', 'add', '-A', '.' -} - -def commitDocChangesTask = tasks.register( 'commitDocChanges', Exec ) { - dependsOn stageDocChangesTask - workingDir project.layout.buildDirectory.dir( "docs-website" ) - commandLine 'git', 'commit', '-m', "[HR] Hibernate Reactive documentation for ${projectVersion}" -} - -def pushDocChangesTask = tasks.register( 'pushDocChanges', Exec ) { - description "Push documentation changes on the remote repository" - dependsOn commitDocChangesTask - workingDir project.layout.buildDirectory.dir( "docs-website" ) - commandLine 'git', 'push', '--atomic', 'origin', docPublishBranch -} - -def uploadDocumentationTask = tasks.register( 'uploadDocumentation' ) { - description "Upload documentation on the website" - group "Release" - dependsOn pushDocChangesTask - - doLast { - logger.lifecycle "Documentation published on '${docPublishRepoUri}' branch '${docPublishBranch}'" - } -} - def gitPreparationForReleaseTask = tasks.register( 'gitPreparationForRelease' ) { dependsOn releaseChecksTask, changeToReleaseVersionTask finalizedBy updateDocumentationTask @@ -245,7 +161,7 @@ void updateVersionFile(var version) { } def publishReleaseArtifactsTask = tasks.register( 'publishReleaseArtifacts' ) { - dependsOn uploadDocumentationTask + dependsOn updateDocumentationTask mustRunAfter gitPreparationForReleaseTask } From 4e71e53764c2f9830d667fb0db1742dbd6944756 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 30 Oct 2025 14:55:11 +0100 Subject: [PATCH 163/185] [#2666] Fix insert with EmbeddedId and stateless session --- .../reactive/session/impl/ReactiveStatelessSessionImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index 40047ee1d..340fcfeb6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -440,7 +440,7 @@ private CompletionStage generatedIdBeforeInsert( private CompletionStage generateIdForInsert(Object entity, Generator generator, ReactiveEntityPersister persister) { if ( generator instanceof ReactiveIdentifierGenerator reactiveGenerator ) { - return reactiveGenerator.generate( this, this ) + return reactiveGenerator.generate( this, entity ) .thenApply( id -> castToIdentifierType( id, persister ) ); } From ad96cc41fc96674105fcbe3ade8fceb8669eac08 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 30 Oct 2025 14:57:15 +0100 Subject: [PATCH 164/185] [#2666] Test insert with StatelessSession and emebedded id --- .../java/org/hibernate/reactive/EmbeddedIdTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdTest.java index de3590fab..14ac0d56f 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EmbeddedIdTest.java @@ -42,6 +42,19 @@ public void populateDb(VertxTestContext context) { test( context, getMutinySessionFactory().withTransaction( s -> s.persistAll( pizza, schnitzel ) ) ); } + @Test + public void testStatelessInsert(VertxTestContext context) { + LocationId nottingham = new LocationId( "UK", "Nottingham" ); + Delivery mushyPeas = new Delivery( nottingham, "Mushy Peas with mint sauce" ); + test( context, getMutinySessionFactory() + .withStatelessTransaction( s -> s.insert( mushyPeas ) ) + .chain( () -> getMutinySessionFactory() + .withTransaction( s -> s.find( Delivery.class, nottingham ) ) + ) + .invoke( result -> assertThat( result ).isEqualTo( mushyPeas ) ) + ); + } + @Test public void testFindSingleId(VertxTestContext context) { test( context, getMutinySessionFactory().withTransaction( s -> s.find( Delivery.class, verbania ) ) From dd8e52033e82f1735a604a0b78021a6abc48e76f Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 31 Oct 2025 14:26:28 +0100 Subject: [PATCH 165/185] [#2699] Make sure to close the connection in case of error I've tested it while working on #2518, but I don't know how to create an isolated test. --- .../hibernate/reactive/logging/impl/Log.java | 5 ++++- .../session/impl/ReactiveSessionImpl.java | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java index 5645e73b9..24246aacf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java @@ -273,6 +273,10 @@ public interface Log extends BasicLogger { @Message(id = 85, value = "Reactive sessions do not support transparent lazy fetching - use Stage.fetch() or Mutiny.fetch() (property '%1$S' of entity '%2$s' was not loaded)") LazyInitializationException lazyFieldInitializationException(String fieldName, String entityName); + @LogMessage(level = ERROR) + @Message(id = 86, value = "Error closing reactive connection") + void errorClosingConnection(@Cause Throwable throwable); + // Same method that exists in CoreMessageLogger @LogMessage(level = WARN) @Message(id = 104, value = "firstResult/maxResults specified with collection fetch; applying in memory!" ) @@ -331,5 +335,4 @@ public interface Log extends BasicLogger { " This is probably due to an operation failing fast due to the transaction being marked for rollback.", id = 520) void jdbcExceptionThrownWithTransactionRolledBack(@Cause JDBCException e); - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index a86a2d68a..a87ef89dd 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -1738,7 +1738,23 @@ public void close() throws HibernateException { @Override public CompletionStage reactiveClose() { - super.close(); + try { + super.close(); + return closeConnection(); + } + catch (RuntimeException e) { + return closeConnection() + .handle( CompletionStages::handle ) + .thenCompose( closeConnectionHandler -> { + if ( closeConnectionHandler.hasFailed() ) { + LOG.errorClosingConnection( closeConnectionHandler.getThrowable() ); + } + return failedFuture( e ); + } ); + } + } + + private CompletionStage closeConnection() { return reactiveConnection != null ? reactiveConnection.close() : voidFuture(); From c299bd405320ecf7f73e4097fbbcdf2c927f8813 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 03:47:08 +0000 Subject: [PATCH 166/185] Bump cockroachdb/cockroach from v25.3.3 to v25.3.4 in /tooling/docker Bumps cockroachdb/cockroach from v25.3.3 to v25.3.4. --- updated-dependencies: - dependency-name: cockroachdb/cockroach dependency-version: v25.3.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- tooling/docker/cockroachdb.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/cockroachdb.Dockerfile b/tooling/docker/cockroachdb.Dockerfile index 86f837a83..0b441f7ca 100644 --- a/tooling/docker/cockroachdb.Dockerfile +++ b/tooling/docker/cockroachdb.Dockerfile @@ -1,3 +1,3 @@ # CockroachDB # See https://hub.docker.com/r/cockroachdb/cockroach -FROM docker.io/cockroachdb/cockroach:v25.3.3 +FROM docker.io/cockroachdb/cockroach:v25.3.4 From 69729e92e6965bad96be3e6093202fad6f497fc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 03:06:57 +0000 Subject: [PATCH 167/185] Bump org.junit.platform:junit-platform-launcher from 6.0.0 to 6.0.1 Bumps [org.junit.platform:junit-platform-launcher](https://github.com/junit-team/junit-framework) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.0...r6.0.1) --- updated-dependencies: - dependency-name: org.junit.platform:junit-platform-launcher dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a150ca902..c2b3603d6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,7 +44,7 @@ org-jboss-logging-jboss-logging-annotations = { group = "org.jboss.logging", nam org-jboss-logging-jboss-logging-processor = { group = "org.jboss.logging", name = "jboss-logging-processor", version.ref = "jbossLoggingAnnotationVersion" } org-junit-jupiter-junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junitVersion" } org-junit-jupiter-junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junitVersion" } -org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "6.0.0" } +org-junit-platform-junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version = "6.0.1" } org-mariadb-jdbc-mariadb-java-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version = "3.5.6" } org-postgresql-postgresql = { group = "org.postgresql", name = "postgresql", version = "42.7.8" } org-testcontainers-cockroachdb = { group = "org.testcontainers", name = "cockroachdb", version.ref = "testcontainersVersion" } From 35d25dd0f59d3f678b8d00ddf4f65a46bb97d05b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 03:06:54 +0000 Subject: [PATCH 168/185] Bump junitVersion from 6.0.0 to 6.0.1 Bumps `junitVersion` from 6.0.0 to 6.0.1. Updates `org.junit.jupiter:junit-jupiter-api` from 6.0.0 to 6.0.1 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.0...r6.0.1) Updates `org.junit.jupiter:junit-jupiter-engine` from 6.0.0 to 6.0.1 - [Release notes](https://github.com/junit-team/junit-framework/releases) - [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.0...r6.0.1) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter-api dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.junit.jupiter:junit-jupiter-engine dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2b3603d6..ab53051f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ hibernateOrmGradlePluginVersion = "7.2.0.CR1" jacksonDatabindVersion = "2.20.0" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" -junitVersion = "6.0.0" +junitVersion = "6.0.1" junitPlatformVersion = "1.13.4" log4jVersion = "2.25.2" testcontainersVersion = "1.21.3" From d4279c8d6799e912fd988adcaa5bfd61c342a55e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 03:06:39 +0000 Subject: [PATCH 169/185] Bump com.fasterxml.jackson.core:jackson-databind Bumps the testcontainers group with 1 update: [com.fasterxml.jackson.core:jackson-databind](https://github.com/FasterXML/jackson). Updates `com.fasterxml.jackson.core:jackson-databind` from 2.20.0 to 2.20.1 - [Commits](https://github.com/FasterXML/jackson/commits) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.core:jackson-databind dependency-version: 2.20.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: testcontainers ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ab53051f5..dfce33ae2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ assertjVersion = "3.27.6" hibernateOrmVersion = "7.2.0.CR1" hibernateOrmGradlePluginVersion = "7.2.0.CR1" -jacksonDatabindVersion = "2.20.0" +jacksonDatabindVersion = "2.20.1" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" junitVersion = "6.0.1" From 1d8c3b8b73a28cbbdbae4d7e9928cf2203e940b3 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 29 Sep 2025 16:44:18 +0200 Subject: [PATCH 170/185] [#2518] Minor clean up --- .../mutiny/impl/MutinySessionFactoryImpl.java | 43 +- .../stage/impl/StageSessionFactoryImpl.java | 15 +- .../reactive/stage/impl/StageSessionImpl.java | 2 +- .../MultithreadedIdentityGenerationTest.java | 4 +- .../reactive/MultithreadedInsertionTest.java | 24 +- .../reactive/ReactiveMultitenantTest.java | 82 ++-- .../reactive/ReactiveSessionTest.java | 422 ++++++++---------- 7 files changed, 280 insertions(+), 312 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java index 93d5d5386..4e82c6a6e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java @@ -92,17 +92,18 @@ public Context getContext() { public Uni openSession() { SessionCreationOptions options = options(); return uni( () -> connection( getTenantIdentifier( options ) ) ) - .chain( reactiveConnection -> create( reactiveConnection, - () -> new ReactiveSessionImpl( delegate, options, reactiveConnection ) ) ) - .map( s -> new MutinySessionImpl(s, this) ); + .chain( reactiveConnection -> create( + reactiveConnection, + () -> new ReactiveSessionImpl( delegate, options, reactiveConnection ) + ) ) + .map( s -> new MutinySessionImpl( s, this ) ); } @Override public Uni openSession(String tenantId) { return uni( () -> connection( tenantId ) ) - .chain( reactiveConnection -> create( reactiveConnection, - () -> new ReactiveSessionImpl( delegate, options( tenantId ), reactiveConnection ) ) ) - .map( s -> new MutinySessionImpl(s, this) ); + .chain( reactiveConnection -> create( reactiveConnection, () -> new ReactiveSessionImpl( delegate, options( tenantId ), reactiveConnection ) ) ) + .map( s -> new MutinySessionImpl( s, this ) ); } /** @@ -122,16 +123,20 @@ private static Uni close(ReactiveConnection connection) { public Uni openStatelessSession() { SessionCreationOptions options = options(); return uni( () -> connection( getTenantIdentifier( options ) ) ) - .chain( reactiveConnection -> create( reactiveConnection, - () -> new ReactiveStatelessSessionImpl( delegate, options, reactiveConnection ) ) ) - .map( s -> new MutinyStatelessSessionImpl(s, this) ); + .chain( reactiveConnection -> create( + reactiveConnection, + () -> new ReactiveStatelessSessionImpl( delegate, options, reactiveConnection ) + ) ) + .map( s -> new MutinyStatelessSessionImpl( s, this ) ); } @Override public Uni openStatelessSession(String tenantId) { return uni( () -> connection( tenantId ) ) - .chain( reactiveConnection -> create( reactiveConnection, - () -> new ReactiveStatelessSessionImpl( delegate, options( tenantId ), reactiveConnection ) ) ) + .chain( reactiveConnection -> create( + reactiveConnection, + () -> new ReactiveStatelessSessionImpl( delegate, options( tenantId ), reactiveConnection ) + ) ) .map( s -> new MutinyStatelessSessionImpl( s, this ) ); } @@ -190,8 +195,8 @@ public Uni withSession(String tenantId, Function> Objects.requireNonNull( tenantId, "parameter 'tenantId' is required" ); Objects.requireNonNull( work, "parameter 'work' is required" ); Context.Key key = new MultitenantKey<>( contextKeyForSession, tenantId ); - Mutiny.Session current = context.get(key); - if ( current!=null && current.isOpen() ) { + Mutiny.Session current = context.get( key ); + if ( current != null && current.isOpen() ) { LOG.debugf( "Reusing existing open Mutiny.Session which was found in the current Vert.x context for current tenant '%s'", tenantId ); return work.apply( current ); } @@ -227,11 +232,11 @@ public Uni withStatelessSession(String tenantId, Function Uni withSession( + private Uni withSession( Uni sessionUni, Function> work, Context.Key contextKey) { @@ -246,25 +251,25 @@ private Uni withSession( @Override public Uni withTransaction(BiFunction> work) { Objects.requireNonNull( work, "parameter 'work' is required" ); - return withSession( s -> s.withTransaction( t -> work.apply(s, t) ) ); + return withSession( s -> s.withTransaction( t -> work.apply( s, t ) ) ); } @Override public Uni withStatelessTransaction(BiFunction> work) { Objects.requireNonNull( work, "parameter 'work' is required" ); - return withStatelessSession( s -> s.withTransaction( t -> work.apply(s, t) ) ); + return withStatelessSession( s -> s.withTransaction( t -> work.apply( s, t ) ) ); } @Override public Uni withTransaction(String tenantId, BiFunction> work) { Objects.requireNonNull( work, "parameter 'work' is required" ); - return withSession( tenantId, s -> s.withTransaction( t -> work.apply(s, t) ) ); + return withSession( tenantId, s -> s.withTransaction( t -> work.apply( s, t ) ) ); } @Override public Uni withStatelessTransaction(String tenantId, BiFunction> work) { Objects.requireNonNull( work, "parameter 'work' is required" ); - return withStatelessSession( tenantId, s -> s.withTransaction( t -> work.apply(s, t) ) ); + return withStatelessSession( tenantId, s -> s.withTransaction( t -> work.apply( s, t ) ) ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java index 2c8a3c473..037103a6a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java @@ -5,7 +5,13 @@ */ package org.hibernate.reactive.stage.impl; -import jakarta.persistence.metamodel.Metamodel; +import java.lang.invoke.MethodHandles; +import java.util.Objects; +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + import org.hibernate.Cache; import org.hibernate.engine.creation.internal.SessionBuilderImpl; import org.hibernate.engine.creation.internal.SessionCreationOptions; @@ -27,12 +33,7 @@ import org.hibernate.service.ServiceRegistry; import org.hibernate.stat.Statistics; -import java.lang.invoke.MethodHandles; -import java.util.Objects; -import java.util.concurrent.CompletionStage; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.Supplier; +import jakarta.persistence.metamodel.Metamodel; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.rethrow; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java index daf467b2e..882393ed9 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java @@ -391,7 +391,7 @@ public Stage.Session setSubselectFetchingEnabled(boolean enabled) { @Override public CompletionStage withTransaction(Function> work) { - return currentTransaction==null ? new Transaction().execute(work) : work.apply(currentTransaction); + return currentTransaction == null ? new Transaction().execute( work ) : work.apply( currentTransaction ); } private Transaction currentTransaction; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedIdentityGenerationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedIdentityGenerationTest.java index a41cefd72..5134a32e5 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedIdentityGenerationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedIdentityGenerationTest.java @@ -212,7 +212,7 @@ public void start(Promise startPromise) { startPromise.fail( "Thread switch detected!" ); } else { - allResults.deliverResulst( generatedIds ); + allResults.deliverResults( generatedIds ); startPromise.complete(); } } @@ -233,7 +233,7 @@ private static class ResultsCollector { private final ConcurrentMap> resultsByThread = new ConcurrentHashMap<>(); - public void deliverResulst(List generatedIds) { + public void deliverResults(List generatedIds) { final String threadName = Thread.currentThread().getName(); resultsByThread.put( threadName, generatedIds ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java index bd2a36d1e..ce5945336 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java @@ -98,15 +98,7 @@ public class MultithreadedInsertionTest { @BeforeAll public static void setupSessionFactory() { - final VertxOptions vertxOptions = new VertxOptions(); - vertxOptions.setEventLoopPoolSize( N_THREADS ); - //We relax the blocked thread checks as we'll actually use latches to block them - //intentionally for the purpose of the test; functionally this isn't required - //but it's useful as self-test in the design of this, to ensure that the way - //things are setup are indeed being run in multiple, separate threads. - vertxOptions.setBlockedThreadCheckInterval( TIMEOUT_MINUTES ); - vertxOptions.setBlockedThreadCheckIntervalUnit( TimeUnit.MINUTES ); - vertx = Vertx.vertx( vertxOptions ); + vertx = Vertx.vertx( getVertxOptions() ); Configuration configuration = new Configuration(); setDefaultProperties( configuration ); configuration.addAnnotatedClass( EntityWithGeneratedId.class ); @@ -121,6 +113,18 @@ public static void setupSessionFactory() { stageSessionFactory = sessionFactory.unwrap( Stage.SessionFactory.class ); } + private static VertxOptions getVertxOptions() { + final VertxOptions vertxOptions = new VertxOptions(); + vertxOptions.setEventLoopPoolSize( N_THREADS ); + //We relax the blocked thread checks as we'll actually use latches to block them + //intentionally for the purpose of the test; functionally this isn't required, + //but it's useful as self-test in the design of this, to ensure that the way + //things are set up are indeed being run in multiple, separate threads. + vertxOptions.setBlockedThreadCheckInterval( TIMEOUT_MINUTES ); + vertxOptions.setBlockedThreadCheckIntervalUnit( TimeUnit.MINUTES ); + return vertxOptions; + } + @AfterAll public static void closeSessionFactory() { stageSessionFactory.close(); @@ -203,7 +207,7 @@ public void stop() { */ @Entity @Table(name="Entity") - private static class EntityWithGeneratedId { + public static class EntityWithGeneratedId { @Id @GeneratedValue Long id; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java index 4173668be..09398a8eb 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java @@ -23,14 +23,11 @@ import jakarta.persistence.Version; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.DEFAULT; import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.TENANT_1; import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.TENANT_2; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * This class creates multiple additional databases so that we can check that queries run @@ -46,10 +43,8 @@ public class ReactiveMultitenantTest extends BaseReactiveTest { protected Configuration constructConfiguration() { Configuration configuration = super.constructConfiguration(); configuration.addAnnotatedClass( GuineaPig.class ); - configuration.setProperty( - AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, - "anything" - );//FIXME this is terrible? + // FIXME this is terrible? + configuration.setProperty( AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, "anything" ); configuration.getProperties().put( Settings.MULTI_TENANT_IDENTIFIER_RESOLVER, TENANT_RESOLVER ); // Contains the SQL scripts for the creation of the additional databases configuration.setProperty( Settings.HBM2DDL_IMPORT_FILES, "/multitenancy-test.sql" ); @@ -61,27 +56,27 @@ protected Configuration constructConfiguration() { public void reactivePersistFindDelete(VertxTestContext context) { TENANT_RESOLVER.setTenantIdentifier( DEFAULT ); final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" ); - test( - context, - getSessionFactory().openSession() - .thenCompose( session -> session.withTransaction( t -> session - .persist( guineaPig ) - .thenCompose( v -> session.flush() ) - .thenAccept( v -> session.detach( guineaPig ) ) - .thenAccept( v -> assertFalse( session.contains( guineaPig ) ) ) - .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) - .thenAccept( actualPig -> { - assertThatPigsAreEqual( guineaPig, actualPig ); - assertTrue( session.contains( actualPig ) ); - assertFalse( session.contains( guineaPig ) ); - assertEquals( LockMode.READ, session.getLockMode( actualPig ) ); - session.detach( actualPig ); - assertFalse( session.contains( actualPig ) ); - } ) - .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) - .thenCompose( session::remove ) - .thenCompose( v -> session.flush() ) ) - ) + test( context, openSession() + .thenCompose( session -> session + .persist( guineaPig ) + .thenCompose( v -> session.flush() ) + .thenAccept( v -> session.detach( guineaPig ) ) + .thenAccept( v -> assertThat( session.contains( guineaPig ) ).isFalse() ) + .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) + .thenAccept( actualPig -> { + assertThat( actualPig ).isNotNull(); + assertThat( actualPig.getId() ).isEqualTo( guineaPig.getId() ); + assertThat( actualPig.getName() ).isEqualTo( guineaPig.getName() ); + assertThat( session.contains( actualPig ) ).isTrue(); + assertThat( session.contains( guineaPig ) ).isFalse(); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.READ ); + session.detach( actualPig ); + assertThat( session.contains( actualPig ) ).isFalse(); + } ) + .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) + .thenCompose( session::remove ) + .thenCompose( v -> session.flush() ) + ) ); } @@ -92,13 +87,13 @@ public void testTenantSelection(VertxTestContext context) { .thenCompose( session -> session .createNativeQuery( "select current_database()" ) .getSingleResult() - .thenAccept( result -> assertEquals( TENANT_1.getDbName(), result ) ) ) + .thenAccept( result -> assertThat( result ).isEqualTo( TENANT_1.getDbName() ) ) ) .thenAccept( unused -> TENANT_RESOLVER.setTenantIdentifier( TENANT_2 ) ) .thenCompose( unused -> openSession() ) .thenCompose( session -> session .createNativeQuery( "select current_database()" ) .getSingleResult() - .thenAccept( result -> assertEquals( TENANT_2.getDbName(), result ) ) ) + .thenAccept( result -> assertThat( result ).isEqualTo( TENANT_2.getDbName() ) ) ) ); } @@ -109,14 +104,14 @@ public void testTenantSelectionStatelessSession(VertxTestContext context) { .thenCompose( t1Session -> t1Session .createNativeQuery( "select current_database()" ) .getSingleResult() - .thenAccept( result -> assertEquals( TENANT_1.getDbName(), result ) ) + .thenAccept( result -> assertThat( result ).isEqualTo( TENANT_1.getDbName() ) ) .thenCompose( unused -> t1Session.close() ) ) .thenAccept( unused -> TENANT_RESOLVER.setTenantIdentifier( TENANT_2 ) ) .thenCompose( v -> getSessionFactory().openStatelessSession() ) .thenCompose( t2Session -> t2Session .createNativeQuery( "select current_database()" ) .getSingleResult() - .thenAccept( result -> assertEquals( TENANT_2.getDbName(), result ) ) + .thenAccept( result -> assertThat( result ).isEqualTo( TENANT_2.getDbName() ) ) .thenCompose( v -> t2Session.close() ) ) ); } @@ -124,26 +119,21 @@ public void testTenantSelectionStatelessSession(VertxTestContext context) { @Test public void testTenantSelectionStatelessSessionMutiny(VertxTestContext context) { TENANT_RESOLVER.setTenantIdentifier( TENANT_1 ); - test( context, getMutinySessionFactory().withStatelessSession( t1Session -> - t1Session - .createNativeQuery( "select current_database()" ) - .getSingleResult() - .invoke( result -> assertEquals( TENANT_1.getDbName(), result ) ) - ) + test( context, getMutinySessionFactory() + .withStatelessSession( t1Session -> t1Session + .createNativeQuery( "select current_database()" ) + .getSingleResult() + .invoke( result -> assertThat( result ).isEqualTo( TENANT_1.getDbName() ) ) + ) .invoke( result -> TENANT_RESOLVER.setTenantIdentifier( TENANT_2 ) ) .chain( () -> getMutinySessionFactory().withStatelessSession( t2Session -> t2Session .createNativeQuery( "select current_database()" ) .getSingleResult() - .invoke( result -> assertEquals( TENANT_2.getDbName(), result ) ) ) ) + .invoke( result -> assertThat( result ).isEqualTo( TENANT_2.getDbName() ) ) + ) ) ); } - private void assertThatPigsAreEqual( GuineaPig expected, GuineaPig actual) { - assertNotNull( actual ); - assertEquals( expected.getId(), actual.getId() ); - assertEquals( expected.getName(), actual.getName() ); - } - @Entity(name = "GuineaPig") @Table(name = "Pig") public static class GuineaPig { diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java index b471ab1b0..b328c4650 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java @@ -14,8 +14,6 @@ import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.stage.Stage; - -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -32,7 +30,6 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; -import static org.junit.jupiter.api.Assertions.*; @Timeout(value = 10, timeUnit = MINUTES) public class ReactiveSessionTest extends BaseReactiveTest { @@ -66,11 +63,11 @@ public void reactiveFind(VertxTestContext context) { .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertTrue( session.contains( actualPig ) ); - assertFalse( session.contains( expectedPig ) ); - assertEquals( LockMode.READ, session.getLockMode( actualPig ) ); + assertThat( session.contains( actualPig ) ).isTrue(); + assertThat( session.contains( expectedPig ) ).isFalse(); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.READ ); session.detach( actualPig ); - assertFalse( session.contains( actualPig ) ); + assertThat( session.contains( actualPig ) ).isFalse(); } ) ) ); @@ -86,9 +83,7 @@ context, populateDB() .thenCompose( v -> getSessionFactory().withTransaction( s -> s .find( GuineaPig.class, emma.getId(), rump.getId() ) ) ) - .thenAccept( pigs -> { - org.assertj.core.api.Assertions.assertThat( pigs ).containsExactlyInAnyOrder( emma, rump ); - } ) + .thenAccept( pigs -> assertThat( pigs ).containsExactlyInAnyOrder( emma, rump ) ) ); } @@ -144,15 +139,15 @@ public void reactivePersistFindDelete(VertxTestContext context) { .persist( guineaPig ) .thenCompose( v -> session.flush() ) .thenAccept( v -> session.detach( guineaPig ) ) - .thenAccept( v -> assertFalse( session.contains( guineaPig ) ) ) + .thenAccept( v -> assertThat( session.contains( guineaPig ) ).isFalse() ) .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) .thenAccept( actualPig -> { assertThatPigsAreEqual( guineaPig, actualPig ); - assertTrue( session.contains( actualPig ) ); - assertFalse( session.contains( guineaPig ) ); - assertEquals( LockMode.READ, session.getLockMode( actualPig ) ); + assertThat( session.contains( actualPig ) ).isTrue(); + assertThat( session.contains( guineaPig ) ).isFalse(); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.READ ); session.detach( actualPig ); - assertFalse( session.contains( actualPig ) ); + assertThat( session.contains( actualPig ) ).isFalse(); } ) .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) .thenCompose( session::remove ) @@ -170,7 +165,7 @@ context, populateDB().thenCompose( v -> getSessionFactory() .find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_WRITE ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_WRITE ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); } ) ) ) ); @@ -188,10 +183,7 @@ context, populateDB() .refresh( pig, LockMode.PESSIMISTIC_WRITE ) .thenAccept( vv -> { assertThatPigsAreEqual( expectedPig, pig ); - assertEquals( - session.getLockMode( pig ), - LockMode.PESSIMISTIC_WRITE - ); + assertThat( session.getLockMode( pig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); } ) ) ) ) @@ -212,8 +204,8 @@ public void reactiveFindReadOnlyRefreshWithLock(VertxTestContext context) { return session.flush() .thenCompose( v -> session.refresh( pig ) ) .thenAccept( v -> { - assertEquals( expectedPig.name, pig.name ); - assertTrue( session.isReadOnly( pig ) ); + assertThat( expectedPig.getName() ).isEqualTo( pig.getName() ); + assertThat( session.isReadOnly( pig ) ).isTrue(); } ); } ) ) @@ -225,8 +217,8 @@ public void reactiveFindReadOnlyRefreshWithLock(VertxTestContext context) { return session.flush() .thenCompose( v -> session.refresh( pig ) ) .thenAccept( v -> { - assertEquals( "XXXX", pig.name ); - assertFalse( session.isReadOnly( pig ) ); + assertThat( "XXXX" ).isEqualTo( pig.getName() ); + assertThat( session.isReadOnly( pig ) ).isFalse(); } ); } ) ) @@ -245,10 +237,7 @@ context, populateDB() .lock( pig, LockMode.PESSIMISTIC_READ ) .thenAccept( v -> { assertThatPigsAreEqual( expectedPig, pig ); - assertEquals( - session.getLockMode( pig ), - LockMode.PESSIMISTIC_READ - ); + assertThat( session.getLockMode( pig ) ).isEqualTo( LockMode.PESSIMISTIC_READ ); } ) ) ) @@ -267,8 +256,8 @@ context, populateDB().thenCompose( v -> getSessionFactory() .lock( pig, LockMode.PESSIMISTIC_WRITE ) .thenAccept( vv -> { assertThatPigsAreEqual( expectedPig, pig ); - assertEquals( session.getLockMode( pig ), LockMode.PESSIMISTIC_WRITE ); - assertEquals( pig.version, 0 ); + assertThat( session.getLockMode( pig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); + assertThat( pig.version ).isEqualTo( 0 ); } ) ) ) ) @@ -287,15 +276,12 @@ public void reactiveFindThenForceLock(VertxTestContext context) { .thenApply( v -> pig ) ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( - session.getLockMode( actualPig ), - LockMode.PESSIMISTIC_FORCE_INCREMENT - ); - assertEquals( actualPig.version, 1 ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_FORCE_INCREMENT ); + assertThat( actualPig.version ).isEqualTo( 1 ); } ) .thenCompose( v -> session.createSelectionQuery( "select version from GuineaPig", Integer.class ) .getSingleResult() ) - .thenAccept( version -> assertEquals( 1, version ) ) + .thenAccept( version -> assertThat( version ).isEqualTo( 1 ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) @@ -303,16 +289,13 @@ public void reactiveFindThenForceLock(VertxTestContext context) { .thenApply( v -> pig ) ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( - session.getLockMode( actualPig ), - LockMode.PESSIMISTIC_FORCE_INCREMENT - ); - assertEquals( actualPig.version, 2 ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_FORCE_INCREMENT ); + assertThat( actualPig.version ).isEqualTo( 2 ); } ) .thenCompose( v -> session .createSelectionQuery( "select version from GuineaPig", Integer.class ) .getSingleResult() ) - .thenAccept( version -> assertEquals( 2, version ) ) + .thenAccept( version -> assertThat( version ).isEqualTo( 2 ) ) ) ); } @@ -327,13 +310,13 @@ public void reactiveFindWithPessimisticIncrementLock(VertxTestContext context) { .withTransaction( session -> session.find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_FORCE_INCREMENT ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( LockMode.PESSIMISTIC_FORCE_INCREMENT, session.getLockMode( actualPig ) ); // grrr, lame - assertEquals( 1, actualPig.version ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_FORCE_INCREMENT ); + assertThat( actualPig.version ).isEqualTo( 1 ); } ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 1, actualPig.version ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 1 ) ) ); } @@ -341,48 +324,41 @@ public void reactiveFindWithPessimisticIncrementLock(VertxTestContext context) { public void reactiveFindWithOptimisticIncrementLock(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); test( context, populateDB() - .thenCompose( v -> getSessionFactory() - .withTransaction( session -> session - .find( GuineaPig.class, expectedPig.getId(), LockMode.OPTIMISTIC_FORCE_INCREMENT ) - .thenAccept( actualPig -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( LockMode.OPTIMISTIC_FORCE_INCREMENT, session.getLockMode( actualPig ) ); - assertEquals( 0, actualPig.version ); - } ) - ) + .thenCompose( v -> getSessionFactory().withTransaction( session -> session + .find( GuineaPig.class, expectedPig.getId(), LockMode.OPTIMISTIC_FORCE_INCREMENT ) + .thenAccept( actualPig -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.OPTIMISTIC_FORCE_INCREMENT ); + assertThat( actualPig.version ).isEqualTo( 0 ); + } ) + ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 1, actualPig.version ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 1 ) ) ); } @Test public void reactiveLockWithOptimisticIncrement(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); - test( - context, - populateDB() - .thenCompose( v -> getSessionFactory().withTransaction( - (session, transaction) -> session.find( GuineaPig.class, expectedPig.getId() ) - .thenCompose( actualPig -> session.lock( - actualPig, - LockMode.OPTIMISTIC_FORCE_INCREMENT - ) - .thenAccept( vv -> { - assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( - session.getLockMode( actualPig ), - LockMode.OPTIMISTIC_FORCE_INCREMENT - ); - assertEquals( 0, actualPig.version ); - } ) - ) - ) + test( context, populateDB() + .thenCompose( v -> getSessionFactory() + .withTransaction( session -> session + .find( GuineaPig.class, expectedPig.getId() ) + .thenCompose( actualPig -> session + .lock( actualPig, LockMode.OPTIMISTIC_FORCE_INCREMENT ) + .thenAccept( vv -> { + assertThatPigsAreEqual( expectedPig, actualPig ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.OPTIMISTIC_FORCE_INCREMENT ); + assertThat( actualPig.version ).isEqualTo( 0 ); + } ) + ) ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 1, actualPig.version ) ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 1 ) ) ); } @@ -400,18 +376,15 @@ public void reactiveLockWithIncrement(VertxTestContext context) { ) .thenAccept( vv -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( - session.getLockMode( actualPig ), - LockMode.PESSIMISTIC_FORCE_INCREMENT - ); - assertEquals( 1, actualPig.version ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_FORCE_INCREMENT ); + assertThat( actualPig.version ).isEqualTo( 1 ); } ) ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 1, actualPig.version ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 1 ) ) ); } @@ -426,12 +399,12 @@ public void reactiveFindWithOptimisticVerifyLock(VertxTestContext context) { .find( GuineaPig.class, expectedPig.getId(), LockMode.OPTIMISTIC ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( LockMode.OPTIMISTIC, session.getLockMode( actualPig ) ); - assertEquals( 0, actualPig.version ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.OPTIMISTIC ); + assertThat( actualPig.version ).isEqualTo( 0 ); } ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 0, actualPig.version ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 0 ) ) ); } @@ -446,12 +419,12 @@ public void reactiveLockWithOptimisticVerify(VertxTestContext context) { .thenCompose( actualPig -> session.lock( actualPig, LockMode.OPTIMISTIC ) .thenAccept( vv -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( LockMode.OPTIMISTIC, session.getLockMode( actualPig ) ); - assertEquals( 0, actualPig.version ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.OPTIMISTIC ); + assertThat( actualPig.version ).isEqualTo( 0 ); } ) ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 0, actualPig.version ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 0 ) ) ); } @@ -467,12 +440,12 @@ public void reactiveFindWithPessimisticRead(VertxTestContext context) { .find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_READ ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( session.getLockMode( actualPig ), LockMode.PESSIMISTIC_READ ); - assertEquals( 0, actualPig.version ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_READ ); + assertThat( actualPig.version ).isEqualTo( 0 ); } ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 0, actualPig.version ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 0 ) ) ); } @@ -489,12 +462,12 @@ public void reactiveLockWithPessimisticRead(VertxTestContext context) { .thenCompose( actualPig -> session.lock( actualPig, LockMode.PESSIMISTIC_READ ) .thenAccept( vv -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( LockMode.PESSIMISTIC_READ, session.getLockMode( actualPig ) ); - assertEquals( 0, actualPig.version ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_READ ); + assertThat( actualPig.version ).isEqualTo( 0 ); } ) ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 0, actualPig.version ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 0 ) ) ); } @@ -510,12 +483,12 @@ public void reactiveFindWithPessimisticWrite(VertxTestContext context) { .find( GuineaPig.class, expectedPig.getId(), LockMode.PESSIMISTIC_WRITE ) .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( LockMode.PESSIMISTIC_WRITE, session.getLockMode( actualPig ) ); - assertEquals( 0, actualPig.version ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); + assertThat( actualPig.version ).isEqualTo( 0 ); } ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 0, actualPig.version ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 0 ) ) ); } @@ -532,12 +505,12 @@ public void reactiveLockWithPessimisticWrite(VertxTestContext context) { .thenCompose( actualPig -> session.lock( actualPig, LockMode.PESSIMISTIC_WRITE ) .thenAccept( vv -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( LockMode.PESSIMISTIC_WRITE, session.getLockMode( actualPig ) ); - assertEquals( 0, actualPig.version ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); + assertThat( actualPig.version ).isEqualTo( 0 ); } ) ) ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, expectedPig.getId() ) ) - .thenAccept( actualPig -> assertEquals( 0, actualPig.version ) ) + .thenAccept( actualPig -> assertThat( actualPig.version ).isEqualTo( 0 ) ) ); } @@ -554,7 +527,7 @@ public void reactiveQueryWithLock(VertxTestContext context) { .getSingleResult() .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( LockMode.PESSIMISTIC_WRITE, session.getLockMode( actualPig ) ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); } ) ) ) ); } @@ -571,10 +544,7 @@ context, populateDB() .getSingleResult() .thenAccept( actualPig -> { assertThatPigsAreEqual( expectedPig, actualPig ); - assertEquals( - LockMode.PESSIMISTIC_WRITE, - session.getLockMode( actualPig ) - ); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.PESSIMISTIC_WRITE ); } ) ) ) @@ -591,7 +561,7 @@ public void reactivePersist(VertxTestContext context) { .thenCompose( v -> s.close() ) ) .thenCompose( v -> selectNameFromId( 10 ) ) - .thenAccept( selectRes -> assertEquals( "Tulip", selectRes ) ) + .thenAccept( selectRes -> assertThat( selectRes ).isEqualTo( "Tulip" ) ) ); } @@ -604,80 +574,72 @@ public void reactivePersistInTx(VertxTestContext context) { .withTransaction( t -> s.persist( new GuineaPig( 10, "Tulip" ) ) ) .thenCompose( v -> s.close() ) ) .thenCompose( vv -> selectNameFromId( 10 ) ) - .thenAccept( selectRes -> assertEquals( "Tulip", selectRes ) ) + .thenAccept( selectRes -> assertThat( selectRes ).isEqualTo( "Tulip" ) ) ); } @Test public void reactiveRollbackTx(VertxTestContext context) { - test( - context, - openSession() - .thenCompose( s -> s - .withTransaction( t -> s - .persist( new GuineaPig( 10, "Tulip" ) ) - .thenCompose( v -> s.flush() ) - .thenAccept( v -> { - throw new RuntimeException( "No Panic: This is just a test" ); - } ) - ) - .thenCompose( v -> s.close() ) + test( context, openSession() + .thenCompose( s -> s + .withTransaction( t -> s + .persist( new GuineaPig( 10, "Tulip" ) ) + .thenCompose( v -> s.flush() ) + .thenAccept( v -> { + throw new RuntimeException( "No Panic: This is just a test" ); + } ) ) - .handle( (v, e) -> null ) - .thenCompose( vv -> selectNameFromId( 10 ) ) - .thenAccept( Assertions::assertNull ) + .thenCompose( v -> s.close() ) + ) + .handle( (v, e) -> null ) + .thenCompose( vv -> selectNameFromId( 10 ) ) + .thenAccept( result -> assertThat( result ).isNull() ) ); } @Test public void reactiveMarkedRollbackTx(VertxTestContext context) { - test( - context, openSession() - .thenCompose( s -> s - .withTransaction( t -> s - .persist( new GuineaPig( 10, "Tulip" ) ) - .thenCompose( vv -> s.flush() ) - .thenAccept( vv -> t.markForRollback() ) - ) - .thenCompose( v -> s.close() ) + test( context, openSession() + .thenCompose( s -> s + .withTransaction( t -> s + .persist( new GuineaPig( 10, "Tulip" ) ) + .thenCompose( vv -> s.flush() ) + .thenAccept( vv -> t.markForRollback() ) ) - .thenCompose( vv -> selectNameFromId( 10 ) ) - .thenAccept( Assertions::assertNull ) + .thenCompose( v -> s.close() ) + ) + .thenCompose( vv -> selectNameFromId( 10 ) ) + .thenAccept( result -> assertThat( result ).isNull() ) ); } @Test public void reactiveRemoveTransientEntity(VertxTestContext context) { - test( - context, - populateDB() - .thenCompose( v -> selectNameFromId( 5 ) ) - .thenAccept( Assertions::assertNotNull ) - .thenCompose( v -> openSession() ) - .thenCompose( session -> session.remove( new GuineaPig( 5, "Aloi" ) ) - .thenCompose( v -> session.flush() ) - .thenCompose( v -> session.close() ) - ) - .handle( (r, e) -> { - assertNotNull( e ); - return r; - } ) - + test( context, populateDB() + .thenCompose( v -> selectNameFromId( 5 ) ) + .thenAccept( result -> assertThat( result ).isNotNull() ) + .thenCompose( v -> openSession() ) + .thenCompose( session -> session.remove( new GuineaPig( 5, "Aloi" ) ) + .thenCompose( v -> session.flush() ) + .thenCompose( v -> session.close() ) + ) + .handle( (r, e) -> { + assertNotNull( e ); + return r; + } ) ); } @Test public void reactiveRemoveManagedEntity(VertxTestContext context) { - test( - context, - populateDB() + test( context, populateDB() .thenCompose( v -> openSession() ) - .thenCompose( session -> - session.find( GuineaPig.class, 5 ) - .thenCompose( session::remove ) - .thenCompose( v -> session.flush() ) - .thenCompose( v -> selectNameFromId( 5 ) ) - .thenAccept( Assertions::assertNull ) ) + .thenCompose( session -> session + .find( GuineaPig.class, 5 ) + .thenCompose( session::remove ) + .thenCompose( v -> session.flush() ) + .thenCompose( v -> selectNameFromId( 5 ) ) + .thenAccept( result -> assertThat( result ).isNull() ) ) ); } @@ -690,16 +652,16 @@ public void reactiveUpdate(VertxTestContext context) { .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, 5 ) .thenAccept( pig -> { - assertNotNull( pig ); + assertThat( pig ).isNotNull(); // Checking we are actually changing the name - assertNotEquals( NEW_NAME, pig.getName() ); + assertThat( pig.getName() ).isNotEqualTo( NEW_NAME ); pig.setName( NEW_NAME ); } ) .thenCompose( v -> session.flush() ) .thenCompose( v -> session.close() ) ) .thenCompose( v -> selectNameFromId( 5 ) ) - .thenAccept( name -> assertEquals( NEW_NAME, name ) ) + .thenAccept( name -> assertThat( name ).isEqualTo( NEW_NAME ) ) ); } @@ -712,10 +674,10 @@ public void reactiveUpdateVersion(VertxTestContext context) { .thenCompose( v -> openSession() ) .thenCompose( session -> session.find( GuineaPig.class, 5 ) .thenAccept( pig -> { - assertNotNull( pig ); + assertThat( pig ).isNotNull(); // Checking we are actually changing the name - assertNotEquals( NEW_NAME, pig.getName() ); - assertEquals( 0, pig.version ); + assertThat( pig.getName() ).isNotEqualTo( NEW_NAME ); + assertThat( pig.version ).isEqualTo( 0 ); pig.setName( NEW_NAME ); pig.version = 10; //ignored by Hibernate } ) @@ -724,7 +686,7 @@ public void reactiveUpdateVersion(VertxTestContext context) { ) .thenCompose( v -> openSession() ) .thenCompose( s -> s.find( GuineaPig.class, 5 ) - .thenAccept( pig -> assertEquals( 1, pig.version ) ) ) + .thenAccept( pig -> assertThat( pig.version ).isEqualTo( 1 ) ) ) ); } @@ -733,9 +695,9 @@ public void reactiveClose(VertxTestContext context) { test( context, openSession() .thenCompose( session -> { - assertTrue( session.isOpen() ); + assertThat( session.isOpen() ).isTrue(); return session.close() - .thenAccept( v -> assertFalse( session.isOpen() ) ); + .thenAccept( v -> assertThat( session.isOpen() ).isFalse() ); } ) ); } @@ -754,8 +716,8 @@ public void testSessionWithNativeAffectedEntities(VertxTestContext context) { .setParameter( "n", pig.name ) .getResultList() ) .thenAccept( list -> { - assertFalse( list.isEmpty() ); - assertEquals( 1, list.size() ); + assertThat( list ).isNotEmpty(); + assertThat( list.size() ).isEqualTo( 1 ); assertThatPigsAreEqual( pig, list.get( 0 ) ); } ) .thenCompose( v -> s.find( GuineaPig.class, pig.id ) ) @@ -764,23 +726,23 @@ public void testSessionWithNativeAffectedEntities(VertxTestContext context) { p.name = "X"; } ) .thenCompose( v -> s.createNativeQuery( "update pig set name='Y' where name='X'", affectsPigs ).executeUpdate() ) - .thenAccept( rows -> assertEquals( 1, rows ) ) + .thenAccept( rows -> assertThat( rows ).isEqualTo( 1 ) ) .thenCompose( v -> s.refresh( pig ) ) - .thenAccept( v -> assertEquals( "Y", pig.name ) ) + .thenAccept( v -> assertThat( pig.name ).isEqualTo( "Y" ) ) .thenAccept( v -> pig.name = "Z" ) .thenCompose( v -> s.createNativeQuery( "delete from pig where name='Z'", affectsPigs ).executeUpdate() ) - .thenAccept( rows -> assertEquals( 1, rows ) ) + .thenAccept( rows -> assertThat( rows ).isEqualTo( 1 ) ) .thenCompose( v -> s.createNativeQuery( "select id from pig", affectsPigs ).getResultList() ) - .thenAccept( list -> assertTrue( list.isEmpty() ) ) ) + .thenAccept( list -> assertThat( list ).isEmpty() ) ) ); } @Test public void testMetamodel() { EntityType pig = getSessionFactory().getMetamodel().entity( GuineaPig.class ); - assertNotNull( pig ); - assertEquals( 3, pig.getAttributes().size() ); - assertEquals( "GuineaPig", pig.getName() ); + assertThat( pig ).isNotNull(); + assertThat( pig.getAttributes().size() ).isEqualTo( 3 ); + assertThat( pig.getName() ).isEqualTo( "GuineaPig" ); } @Test @@ -804,14 +766,14 @@ context, getSessionFactory().withTransaction( .createSelectionQuery( "from GuineaPig", GuineaPig.class ) .getResultList() .thenCompose( list -> { - assertNotNull( session.currentTransaction() ); - assertFalse( session.currentTransaction().isMarkedForRollback() ); + assertThat( session.currentTransaction() ).isNotNull(); + assertThat( session.currentTransaction().isMarkedForRollback() ).isFalse(); session.currentTransaction().markForRollback(); - assertTrue( session.currentTransaction().isMarkedForRollback() ); - assertTrue( transaction.isMarkedForRollback() ); + assertThat( session.currentTransaction().isMarkedForRollback() ).isTrue(); + assertThat( transaction.isMarkedForRollback() ).isTrue(); return session.withTransaction( t -> { - assertEquals( t, transaction ); - assertTrue( t.isMarkedForRollback() ); + assertThat( t ).isEqualTo( transaction ); + assertThat( t.isMarkedForRollback() ).isTrue(); return session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); } ); } ) @@ -823,11 +785,11 @@ context, getSessionFactory().withTransaction( public void testSessionPropagation(VertxTestContext context) { test( context, getSessionFactory().withSession( session -> { - assertFalse( session.isDefaultReadOnly() ); + assertThat( session.isDefaultReadOnly() ).isFalse(); session.setDefaultReadOnly( true ); return session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList() .thenCompose( list -> getSessionFactory().withSession( s -> { - assertTrue( s.isDefaultReadOnly() ); + assertThat( s.isDefaultReadOnly() ).isTrue(); return s.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList(); } ) ); } ) @@ -843,9 +805,9 @@ public void testDupeException(VertxTestContext context) { .thenCompose( v -> getSessionFactory() .withTransaction( (s, t) -> s.persist( new GuineaPig( 10, "Tulip" ) ) ) ).handle( (i, t) -> { - assertNotNull( t ); - assertInstanceOf( CompletionException.class, t ); - assertInstanceOf( PersistenceException.class, t.getCause() ); + assertThat( t ).isNotNull(); + assertThat( t ).isInstanceOf( CompletionException.class ); + assertThat( t.getCause() ).isInstanceOf( PersistenceException.class ); return null; } ) ); @@ -856,12 +818,12 @@ public void testExceptionInWithSession(VertxTestContext context) { final Stage.Session[] savedSession = new Stage.Session[1]; test( context, getSessionFactory().withSession( session -> { - assertTrue( session.isOpen() ); + assertThat( session.isOpen() ).isTrue(); savedSession[0] = session; throw new RuntimeException( "No Panic: This is just a test" ); } ).handle( (o, t) -> { - assertNotNull( t ); - assertFalse( savedSession[0].isOpen(), "Session should be closed" ); + assertThat( t ).isNotNull(); + assertThat( savedSession[0].isOpen() ).withFailMessage( "Session should be closed" ).isFalse(); return null; } ) ); @@ -872,12 +834,12 @@ public void testExceptionInWithTransaction(VertxTestContext context) { final Stage.Session[] savedSession = new Stage.Session[1]; test( context, getSessionFactory().withTransaction( (session, tx) -> { - assertTrue( session.isOpen() ); + assertThat( session.isOpen() ).isTrue(); savedSession[0] = session; throw new RuntimeException( "No Panic: This is just a test" ); } ).handle( (o, t) -> { - assertNotNull( t ); - assertFalse( savedSession[0].isOpen(), "Session should be closed" ); + assertThat( t ).isNotNull(); + assertThat( savedSession[0].isOpen() ).withFailMessage( "Session should be closed" ).isFalse(); return null; } ) ); @@ -888,12 +850,12 @@ public void testExceptionInWithStatelessSession(VertxTestContext context) { final Stage.StatelessSession[] savedSession = new Stage.StatelessSession[1]; test( context, getSessionFactory().withStatelessSession( session -> { - assertTrue( session.isOpen() ); + assertThat( session.isOpen() ).isTrue(); savedSession[0] = session; throw new RuntimeException( "No Panic: This is just a test" ); } ).handle( (o, t) -> { - assertNotNull( t ); - assertFalse( savedSession[0].isOpen(), "Session should be closed" ); + assertThat( t ).isNotNull(); + assertThat( savedSession[0].isOpen() ).withFailMessage( "Session should be closed" ).isFalse(); return null; } ) ); @@ -948,54 +910,60 @@ public void testCreateSelectionQueryNull(VertxTestContext context) { context, openSession() .thenCompose( session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) .getSingleResultOrNull() - .thenAccept( Assertions::assertNull ) ) + .thenAccept( result -> assertThat( result ).isNull() ) ) .thenCompose( v -> openSession() ) .thenCompose( session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ) .getSingleResultOrNull() - .thenAccept( Assertions::assertNull ) ) + .thenAccept( result -> assertThat( result ).isNull() ) ) ); } @Test public void testCurrentSession(VertxTestContext context) { - test( context, - getSessionFactory().withSession(session -> - getSessionFactory().withSession(s -> { - assertEquals(session, s); - Stage.Session currentSession = getSessionFactory().getCurrentSession(); - assertNotNull(currentSession); - assertTrue(currentSession.isOpen()); - assertEquals(session, currentSession); - return voidFuture(); - }) - .thenAccept(v -> assertNotNull(getSessionFactory().getCurrentSession())) - ) - .thenAccept(v -> assertNull(getSessionFactory().getCurrentSession())) + test( + context, getSessionFactory() + .withSession( s1 -> getSessionFactory() + .withSession( s2 -> { + assertThat( s2 ).isEqualTo( s1 ); + Stage.Session currentSession = getSessionFactory().getCurrentSession(); + assertThat( currentSession ).isNotNull(); + assertThat( currentSession.isOpen() ).isTrue(); + assertThat( currentSession ).isEqualTo( s1 ); + return voidFuture(); + } ) + // We closed s2, not s1 + .thenAccept( v -> assertThat( getSessionFactory().getCurrentSession() ).isNotNull() ) + ) + // Both sessions are closed now + .thenAccept( v -> assertThat( getSessionFactory().getCurrentSession() ).isNull() ) ); } @Test public void testCurrentStatelessSession(VertxTestContext context) { - test( context, - getSessionFactory().withStatelessSession(session -> - getSessionFactory().withStatelessSession(s -> { - assertEquals(session, s); - Stage.StatelessSession currentSession = getSessionFactory().getCurrentStatelessSession(); - assertNotNull(currentSession); - assertTrue(currentSession.isOpen()); - assertEquals(session, currentSession); - return voidFuture(); - }) - .thenAccept(v -> assertNotNull(getSessionFactory().getCurrentStatelessSession())) - ) - .thenAccept(v -> assertNull(getSessionFactory().getCurrentStatelessSession())) + test( + context, getSessionFactory() + .withStatelessSession( session -> getSessionFactory() + .withStatelessSession( s -> { + assertThat( s ).isEqualTo( session ); + Stage.StatelessSession currentSession = getSessionFactory().getCurrentStatelessSession(); + assertThat( currentSession ).isNotNull(); + assertThat( currentSession.isOpen() ).isTrue(); + assertThat( currentSession ).isEqualTo( session ); + return voidFuture(); + } ) + // We closed s2, not s1 + .thenAccept( v -> assertThat( getSessionFactory().getCurrentStatelessSession() ).isNotNull() ) + ) + // Both sessions are closed now + .thenAccept( v -> assertThat( getSessionFactory().getCurrentStatelessSession() ).isNull() ) ); } private void assertThatPigsAreEqual(GuineaPig expected, GuineaPig actual) { - assertNotNull( actual ); - assertEquals( expected.getId(), actual.getId() ); - assertEquals( expected.getName(), actual.getName() ); + assertThat( actual ).isNotNull(); + assertThat( actual.getId() ).isEqualTo( expected.getId() ); + assertThat( actual.getName() ).isEqualTo( expected.getName() ); } @Entity(name = "GuineaPig") From 7492e4b43d4e00440ed2c7c5662d532a90fd3d4f Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 2 Oct 2025 15:46:29 +0200 Subject: [PATCH 171/185] [#2518] Remove CompletionException as cause of exception --- .../session/impl/ReactiveSessionImpl.java | 99 ++++++++----------- .../reactive/ReactiveSessionTest.java | 14 +-- 2 files changed, 49 insertions(+), 64 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index a87ef89dd..93fc8c076 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -151,7 +151,6 @@ import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; import static org.hibernate.reactive.util.impl.CompletionStages.rethrow; import static org.hibernate.reactive.util.impl.CompletionStages.returnNullorRethrow; -import static org.hibernate.reactive.util.impl.CompletionStages.returnOrRethrow; import static org.hibernate.reactive.util.impl.CompletionStages.supplyStage; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -171,10 +170,7 @@ public class ReactiveSessionImpl extends SessionImpl implements ReactiveSession, //Lazily initialized private transient ExceptionConverter exceptionConverter; - public ReactiveSessionImpl( - SessionFactoryImpl delegate, - SessionCreationOptions options, - ReactiveConnection connection) { + public ReactiveSessionImpl(SessionFactoryImpl delegate, SessionCreationOptions options, ReactiveConnection connection) { super( delegate, options ); InternalStateAssertions.assertUseOnEventLoop(); this.associatedWorkThread = Thread.currentThread(); @@ -977,20 +973,13 @@ private CompletionStage fireRemove(DeleteEvent event) { return getFactory().getEventListenerGroups().eventListenerGroup_DELETE .fireEventOnEachListener( event, (ReactiveDeleteEventListener l) -> l::reactiveOnDelete ) - .handle( (v, e) -> { + .handle( CompletionStages::handle ) + .thenCompose( handler -> { delayedAfterCompletion(); - - if ( e instanceof ObjectDeletedException ) { - throw getExceptionConverter().convert( new IllegalArgumentException( e ) ); - } - else if ( e instanceof MappingException ) { - throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); - } - else if ( e instanceof RuntimeException ) { - //including HibernateException - throw getExceptionConverter().convert( (RuntimeException) e ); - } - return returnNullorRethrow( e ); + final Throwable e = handler.getThrowable(); + return e != null + ? failedFuture( convertException( e ) ) + : voidFuture(); } ); } @@ -999,20 +988,13 @@ private CompletionStage fireRemove(DeleteEvent event, DeleteContext transi return getFactory().getEventListenerGroups().eventListenerGroup_DELETE .fireEventOnEachListener( event, transientEntities, (ReactiveDeleteEventListener l) -> l::reactiveOnDelete ) - .handle( (v, e) -> { + .handle( CompletionStages::handle ) + .thenCompose( handler -> { delayedAfterCompletion(); - - if ( e instanceof ObjectDeletedException ) { - throw getExceptionConverter().convert( new IllegalArgumentException( e ) ); - } - else if ( e instanceof MappingException ) { - throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); - } - else if ( e instanceof RuntimeException ) { - //including HibernateException - throw getExceptionConverter().convert( (RuntimeException) e ); - } - return returnNullorRethrow( e ); + final Throwable e = handler.getThrowable(); + return e != null + ? failedFuture( convertException( e ) ) + : voidFuture(); } ); } @@ -1036,42 +1018,45 @@ private CompletionStage fireMerge(MergeEvent event) { return getFactory().getEventListenerGroups().eventListenerGroup_MERGE .fireEventOnEachListener( event, (ReactiveMergeEventListener l) -> l::reactiveOnMerge ) - .handle( (v, e) -> { + .handle( CompletionStages::handle ) + .thenCompose( handler -> { checkNoUnresolvedActionsAfterOperation(); - - if ( e instanceof ObjectDeletedException ) { - throw getExceptionConverter().convert( new IllegalArgumentException( e ) ); - } - else if ( e instanceof MappingException ) { - throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); - } - else if ( e instanceof RuntimeException ) { - //including HibernateException - throw getExceptionConverter().convert( (RuntimeException) e ); - } - return returnOrRethrow( e, (T) event.getResult() ); + final Throwable e = handler.getThrowable(); + return e != null + ? failedFuture( convertException( e ) ) + : completedFuture( (T) event.getResult() ); } ); } + private Throwable convertException(Throwable e) { + if ( e instanceof CompletionException) { + return convertException( e.getCause() ); + } + if ( e instanceof ObjectDeletedException ) { + return getExceptionConverter().convert( new IllegalArgumentException( e ) ); + } + if ( e instanceof MappingException ) { + return getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); + } + if ( e instanceof RuntimeException ) { + //including HibernateException + return getExceptionConverter().convert( (RuntimeException) e ); + } + return e; + } + private CompletionStage fireMerge(MergeContext copiedAlready, MergeEvent event) { pulseTransactionCoordinator(); return getFactory().getEventListenerGroups().eventListenerGroup_MERGE .fireEventOnEachListener( event, copiedAlready,(ReactiveMergeEventListener l) -> l::reactiveOnMerge ) - .handle( (v, e) -> { + .handle( CompletionStages::handle ) + .thenCompose( handler -> { delayedAfterCompletion(); - - if ( e instanceof ObjectDeletedException ) { - throw getExceptionConverter().convert( new IllegalArgumentException( e ) ); - } - else if ( e instanceof MappingException ) { - throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage(), e ) ); - } - else if ( e instanceof RuntimeException ) { - //including HibernateException - throw getExceptionConverter().convert( (RuntimeException) e ); - } - return returnNullorRethrow( e ); + final Throwable e = handler.getThrowable(); + return e != null + ? failedFuture( convertException( e ) ) + : voidFuture(); } ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java index b328c4650..369dc983c 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java @@ -10,6 +10,7 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; +import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.stage.Stage; @@ -29,6 +30,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @Timeout(value = 10, timeUnit = MINUTES) @@ -619,14 +621,12 @@ public void reactiveRemoveTransientEntity(VertxTestContext context) { .thenCompose( v -> selectNameFromId( 5 ) ) .thenAccept( result -> assertThat( result ).isNotNull() ) .thenCompose( v -> openSession() ) - .thenCompose( session -> session.remove( new GuineaPig( 5, "Aloi" ) ) - .thenCompose( v -> session.flush() ) - .thenCompose( v -> session.close() ) + .thenCompose( session -> assertThrown( HibernateException.class, session.remove( new GuineaPig( 5, "Aloi" ) ) ) + ) + .thenAccept( t -> assertThat( t ) + .hasCauseInstanceOf( IllegalArgumentException.class ) + .hasMessageContaining( "unmanaged instance" ) ) - .handle( (r, e) -> { - assertNotNull( e ); - return r; - } ) ); } From fa7d4264aedff05ba605fdb2b3a9f32bd95a3a88 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 28 Oct 2025 15:08:34 +0100 Subject: [PATCH 172/185] [#2518] Add createSession and createStatelessSession In Mutiny and Stage classes --- .../org/hibernate/reactive/mutiny/Mutiny.java | 62 +++++- .../mutiny/impl/MutinySessionFactoryImpl.java | 28 +++ .../reactive/pool/ReactiveConnectionPool.java | 23 ++- .../reactive/pool/impl/SqlClientPool.java | 183 +++++++++++++++++- .../org/hibernate/reactive/stage/Stage.java | 49 +++++ .../stage/impl/StageSessionFactoryImpl.java | 32 ++- 6 files changed, 366 insertions(+), 11 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java index bee3a5c1f..c8d1dfd5e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java @@ -5,6 +5,11 @@ */ package org.hibernate.reactive.mutiny; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + import org.hibernate.Cache; import org.hibernate.CacheMode; import org.hibernate.Filter; @@ -44,10 +49,6 @@ import jakarta.persistence.criteria.CriteriaUpdate; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.Metamodel; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Function; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; @@ -1971,6 +1972,59 @@ interface Transaction { */ interface SessionFactory extends AutoCloseable { + /** + * Obtain a new {@link Session reactive session}, the main + * interaction point between the user's program and Hibernate + * Reactive. + *

+ * The underlying database connection is obtained lazily + * when the returned {@link Session} needs to access the + * database. + *

+ * The client must close the session using {@link Session#close()}. + */ + @Incubating + Session createSession(); + + /** + * Obtain a new {@link Session reactive session}. + *

+ * The underlying database connection is obtained lazily + * when the returned {@link Session} needs to access the + * database. + *

+ * The client must close the session using {@link Session#close()}. + * @param tenantId the id of the tenant + */ + @Incubating + Session createSession(String tenantId); + + /** + * Obtain a new {@link StatelessSession reactive stateless session}. + *

+ * The underlying database connection is obtained lazily + * when the returned {@link StatelessSession} needs to access the + * database. + *

+ * The client must close the session using {@link Session#close()}. + */ + @Incubating + StatelessSession createStatelessSession(); + + /** + * Obtain a new {@link StatelessSession reactive stateless session}. + *

+ * The underlying database connection is obtained lazily + * when the returned {@link StatelessSession} needs to access the + * database. + *

+ * The client must close the session using {@link Session#close()}. + * + * @param tenantId the id of the tenant + */ + @Incubating + StatelessSession createStatelessSession(String tenantId); + /** * Obtain a new {@link Session reactive session} {@link Uni}, the main * interaction point between the user's program and Hibernate diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java index 4e82c6a6e..1959872ad 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionFactoryImpl.java @@ -28,6 +28,8 @@ import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.pool.ReactiveConnectionPool; +import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.session.ReactiveStatelessSession; import org.hibernate.reactive.session.impl.ReactiveSessionImpl; import org.hibernate.reactive.session.impl.ReactiveStatelessSessionImpl; import org.hibernate.service.ServiceRegistry; @@ -88,6 +90,32 @@ public Context getContext() { return context; } + @Override + public Mutiny.Session createSession() { + return createSession( getTenantIdentifier( options() ) ); + } + + @Override + public Mutiny.Session createSession(String tenantId) { + final SessionCreationOptions options = options(); + ReactiveConnectionPool pool = delegate.getServiceRegistry().getService( ReactiveConnectionPool.class ); + ReactiveSession sessionImpl = new ReactiveSessionImpl( delegate, options, pool.getProxyConnection( tenantId ) ); + return new MutinySessionImpl( sessionImpl, this ); + } + + @Override + public Mutiny.StatelessSession createStatelessSession() { + return createStatelessSession( getTenantIdentifier( options() ) ); + } + + @Override + public Mutiny.StatelessSession createStatelessSession(String tenantId) { + final SessionCreationOptions options = options(); + ReactiveConnectionPool pool = delegate.getServiceRegistry().getService( ReactiveConnectionPool.class ); + ReactiveStatelessSession sessionImpl = new ReactiveStatelessSessionImpl( delegate, options, pool.getProxyConnection( tenantId ) ); + return new MutinyStatelessSessionImpl( sessionImpl, this ); + } + @Override public Uni openSession() { SessionCreationOptions options = options(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnectionPool.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnectionPool.java index ec1bdbd52..ad259652b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnectionPool.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/ReactiveConnectionPool.java @@ -37,12 +37,25 @@ */ @Incubating public interface ReactiveConnectionPool extends Service { + /** + * Obtain a lazily-initializing reactive connection. The + * actual connection might be made when the returned + * instance if {@link ReactiveConnection} is first used. + */ + ReactiveConnection getProxyConnection(); /** * Obtain a reactive connection, returning the connection - * via a {@link CompletionStage}. + * via a {@link CompletionStage} and overriding the default + * {@link SqlExceptionHelper} for the pool. */ - CompletionStage getConnection(); + ReactiveConnection getProxyConnection(SqlExceptionHelper sqlExceptionHelper); + + /** + * Obtain a reactive connection for the given tenant id, + * returning the connection via a {@link CompletionStage}. + */ + ReactiveConnection getProxyConnection(String tenantId); /** * Obtain a reactive connection, returning the connection @@ -57,6 +70,12 @@ public interface ReactiveConnectionPool extends Service { */ CompletionStage getConnection(String tenantId); + /** + * Obtain a reactive connection, returning the connection + * via a {@link CompletionStage}. + */ + CompletionStage getConnection(); + /** * Obtain a reactive connection for the given tenant id, * returning the connection via a {@link CompletionStage} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPool.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPool.java index 5e2a59517..89ba43e7e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPool.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/pool/impl/SqlClientPool.java @@ -7,10 +7,12 @@ import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; +import java.util.function.Supplier; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; @@ -26,8 +28,11 @@ import io.vertx.sqlclient.RowSet; import io.vertx.sqlclient.SqlConnection; import io.vertx.sqlclient.Tuple; +import io.vertx.sqlclient.spi.DatabaseMetadata; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.rethrow; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * A pool of reactive connections backed by a supplier of @@ -72,6 +77,7 @@ public abstract class SqlClientPool implements ReactiveConnectionPool { * subclasses which support multitenancy. * * @param tenantId the id of the tenant + * * @throws UnsupportedOperationException if multitenancy is not supported * @see ReactiveConnectionPool#getConnection(String) */ @@ -79,6 +85,23 @@ protected Pool getTenantPool(String tenantId) { throw new UnsupportedOperationException( "multitenancy not supported by built-in SqlClientPool" ); } + @Override + public ReactiveConnection getProxyConnection() { + return new ProxyConnection( this::getConnection ); + } + + @Override + public ReactiveConnection getProxyConnection(String tenantId) { + return tenantId == null + ? new ProxyConnection( this::getConnection ) + : new ProxyConnection( () -> getConnection( tenantId ) ); + } + + @Override + public ReactiveConnection getProxyConnection(SqlExceptionHelper sqlExceptionHelper) { + return new ProxyConnection( () -> getConnection( sqlExceptionHelper ) ); + } + @Override public CompletionStage getConnection() { return getConnectionFromPool( getPool() ); @@ -143,10 +166,13 @@ private T convertException(T rows, String sql, Throwable sqlException) { if ( sqlException == null ) { return rows; } - if ( sqlException instanceof DatabaseException ) { - DatabaseException de = (DatabaseException) sqlException; + if ( sqlException instanceof DatabaseException de ) { sqlException = getSqlExceptionHelper() - .convert( new SQLException( de.getMessage(), de.getSqlState(), de.getErrorCode() ), "error executing SQL statement", sql ); + .convert( + new SQLException( de.getMessage(), de.getSqlState(), de.getErrorCode() ), + "error executing SQL statement", + sql + ); } return rethrow( sqlException ); } @@ -186,4 +212,155 @@ private SqlClientConnection newConnection(SqlConnection connection) { private SqlClientConnection newConnection(SqlConnection connection, SqlExceptionHelper sqlExceptionHelper) { return new SqlClientConnection( connection, getPool(), getSqlStatementLogger(), sqlExceptionHelper ); } + + private static class ProxyConnection implements ReactiveConnection { + private final Supplier> connectionSupplier; + private Integer batchSize; + private ReactiveConnection connection; + + public ProxyConnection(Supplier> connectionSupplier) { + this.connectionSupplier = connectionSupplier; + } + + /** + * @return the existing {@link ReactiveConnection}, or open a new one + */ + CompletionStage connection() { + if ( connection == null ) { + return connectionSupplier.get() + .thenApply( conn -> { + if ( batchSize != null ) { + conn.withBatchSize( batchSize ); + } + connection = conn; + return connection; + } ); + } + return completedFuture( connection ); + } + + @Override + public boolean isTransactionInProgress() { + return connection != null && connection.isTransactionInProgress(); + } + + @Override + public DatabaseMetadata getDatabaseMetadata() { + Objects.requireNonNull( connection, "Database metadata not available until the connection is opened" ); + return connection.getDatabaseMetadata(); + } + + @Override + public CompletionStage execute(String sql) { + return connection().thenCompose( conn -> conn.execute( sql ) ); + } + + @Override + public CompletionStage executeOutsideTransaction(String sql) { + return connection().thenCompose( conn -> conn.executeOutsideTransaction( sql ) ); + } + + @Override + public CompletionStage executeUnprepared(String sql) { + return connection().thenCompose( conn -> conn.executeUnprepared( sql ) ); + } + + @Override + public CompletionStage update(String sql) { + return connection().thenCompose( conn -> conn.update( sql ) ); + } + + @Override + public CompletionStage update(String sql, Object[] paramValues) { + return connection().thenCompose( conn -> conn.update( sql, paramValues ) ); + } + + @Override + public CompletionStage update(String sql, Object[] paramValues, boolean allowBatching, Expectation expectation) { + return connection().thenCompose( conn -> conn.update( sql, paramValues, allowBatching, expectation ) ); + } + + @Override + public CompletionStage update(String sql, List paramValues) { + return connection().thenCompose( conn -> conn.update( sql, paramValues ) ); + } + + @Override + public CompletionStage select(String sql) { + return connection().thenCompose( conn -> conn.select( sql ) ); + } + + @Override + public CompletionStage select(String sql, Object[] paramValues) { + return connection().thenCompose( conn -> conn.select( sql ) ); + } + + @Override + public CompletionStage selectJdbc(String sql, Object[] paramValues) { + return connection().thenCompose( conn -> conn.selectJdbc( sql, paramValues ) ); + } + + @Override + public CompletionStage insertAndSelectIdentifier( + String sql, + Object[] paramValues, + Class idClass, + String idColumnName) { + return connection().thenCompose( conn -> conn + .insertAndSelectIdentifier( sql, paramValues, idClass, idColumnName ) ); + } + + @Override + public CompletionStage insertAndSelectIdentifierAsResultSet( + String sql, + Object[] paramValues, + Class idClass, + String idColumnName) { + return connection().thenCompose( conn -> conn + .insertAndSelectIdentifierAsResultSet( sql, paramValues, idClass, idColumnName ) ); + } + + @Override + public CompletionStage selectIdentifier(String sql, Object[] paramValues, Class idClass) { + return connection().thenCompose( conn -> conn.selectIdentifier( sql, paramValues, idClass ) ); + } + + @Override + public CompletionStage beginTransaction() { + return connection().thenCompose( ReactiveConnection::beginTransaction ); + } + + @Override + public CompletionStage commitTransaction() { + return connection().thenCompose( ReactiveConnection::commitTransaction ); + } + + @Override + public CompletionStage rollbackTransaction() { + return connection().thenCompose( ReactiveConnection::rollbackTransaction ); + } + + @Override + public ReactiveConnection withBatchSize(int batchSize) { + if ( connection == null ) { + this.batchSize = batchSize; + } + else { + connection = connection.withBatchSize( batchSize ); + } + return this; + } + + @Override + public CompletionStage executeBatch() { + return connection().thenCompose( ReactiveConnection::executeBatch ); + } + + @Override + public CompletionStage close() { + return connection != null + ? connection.close().thenAccept( v -> connection = null ) + : voidFuture(); + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java index dccb3a072..9941bd257 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java @@ -1979,6 +1979,55 @@ interface Transaction { */ interface SessionFactory extends AutoCloseable { + /** + * Obtain a new {@link Session reactive session}. + *

+ * The underlying database connection is obtained lazily + * when the returned {@link Session} needs to access the + * database. + *

+ * The client must close the session using {@link Session#close()}. + */ + @Incubating + Session createSession(); + + /** + * Obtain a new {@link Session reactive session}. + *

+ * The underlying database connection is obtained lazily + * when the returned {@link Session} needs to access the + * database. + *

+ * The client must close the session using {@link Session#close()}. + */ + @Incubating + Session createSession(String tenantId); + + /** + * Obtain a new {@link Session reactive session}. + *

+ * The underlying database connection is obtained lazily + * when the returned {@link Session} needs to access the + * database. + *

+ * The client must close the session using {@link Session#close()}. + */ + @Incubating + StatelessSession createStatelessSession(); + + /** + * Obtain a new {@link StatelessSession reactive stateless session}. + *

+ * The underlying database connection is obtained lazily + * when the returned {@link StatelessSession} needs to access the + * database. + *

+ * The client must close the session using {@link Session#close()}. + * @param tenantId the id of the tenant + */ + @Incubating + StatelessSession createStatelessSession(String tenantId); + /** * Obtain a new {@linkplain Session reactive session} {@link CompletionStage}, the main * interaction point between the user's program and Hibernate diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java index 037103a6a..6a04e25ae 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionFactoryImpl.java @@ -27,6 +27,7 @@ import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.pool.ReactiveConnectionPool; +import org.hibernate.reactive.session.ReactiveStatelessSession; import org.hibernate.reactive.session.impl.ReactiveSessionImpl; import org.hibernate.reactive.session.impl.ReactiveStatelessSessionImpl; import org.hibernate.reactive.stage.Stage; @@ -77,6 +78,32 @@ public Context getContext() { return context; } + @Override + public Stage.Session createSession() { + return createSession( getTenantIdentifier( options() ) ); + } + + @Override + public Stage.Session createSession(String tenantId) { + final SessionCreationOptions options = options(); + ReactiveConnectionPool pool = delegate.getServiceRegistry().getService( ReactiveConnectionPool.class ); + ReactiveSessionImpl sessionImpl = new ReactiveSessionImpl( delegate, options, pool.getProxyConnection( tenantId ) ); + return new StageSessionImpl( sessionImpl ); + } + + @Override + public Stage.StatelessSession createStatelessSession() { + return createStatelessSession( getTenantIdentifier( options() ) ); + } + + @Override + public Stage.StatelessSession createStatelessSession(String tenantId) { + final SessionCreationOptions options = options(); + ReactiveConnectionPool pool = delegate.getServiceRegistry().getService( ReactiveConnectionPool.class ); + ReactiveStatelessSession sessionImpl = new ReactiveStatelessSessionImpl( delegate, options, pool.getProxyConnection( tenantId ) ); + return new StageStatelessSessionImpl( sessionImpl ); + } + @Override public CompletionStage openSession() { SessionCreationOptions options = options(); @@ -307,7 +334,8 @@ public HibernateCriteriaBuilder getCriteriaBuilder() { } private String getTenantIdentifier(SessionCreationOptions options) { - return options.getTenantIdentifierValue() == null ? null : delegate.getTenantIdentifierJavaType().toString( - options.getTenantIdentifierValue() ); + return options.getTenantIdentifierValue() == null + ? null + : delegate.getTenantIdentifierJavaType().toString( options.getTenantIdentifierValue() ); } } From d11ffb6789e6f3dfc992f8001c3eb19f8257aa5f Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 28 Oct 2025 15:10:22 +0100 Subject: [PATCH 173/185] [#2518] Test sessions with lazy connection creation --- .../reactive/BatchingConnectionTest.java | 24 +++- .../reactive/ReactiveMultitenantTest.java | 24 +++- .../reactive/ReactiveSessionTest.java | 104 ++++++++++++++++++ 3 files changed, 149 insertions(+), 3 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java index 75d90375e..869d2ab9b 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BatchingConnectionTest.java @@ -5,13 +5,13 @@ */ package org.hibernate.reactive; - import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.reactive.mutiny.impl.MutinySessionImpl; import org.hibernate.reactive.mutiny.impl.MutinyStatelessSessionImpl; import org.hibernate.reactive.pool.BatchingConnection; +import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.pool.impl.SqlClientConnection; import org.hibernate.reactive.stage.impl.StageSessionImpl; import org.hibernate.reactive.stage.impl.StageStatelessSessionImpl; @@ -30,7 +30,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; @Timeout(value = 10, timeUnit = MINUTES) - public class BatchingConnectionTest extends ReactiveSessionTest { private static SqlStatementTracker sqlTracker; @@ -65,6 +64,27 @@ private static boolean filter(String s) { return false; } + @Override + protected void assertConnectionIsLazy(ReactiveConnection connection) { + assertConnectionIsLazy( connection, false ); + } + + @Override + protected void assertConnectionIsLazy(ReactiveConnection connection, boolean stateless) { + final ReactiveConnection actualConnection; + if ( !stateless ) { + // Only the stateful session creates a batching connection + assertThat( connection ).isInstanceOf( BatchingConnection.class ); + // A little hack, withBatchSize returns the underlying connection when the parameter is less than 1 + actualConnection = connection.withBatchSize( -1 ); + } + else { + actualConnection = connection; + } + assertThat( actualConnection.getClass().getName() ) + .isEqualTo( org.hibernate.reactive.pool.impl.SqlClientPool.class.getName() + "$ProxyConnection" ); + } + @Test public void testBatchingWithPersistAll(VertxTestContext context) { test( context, openSession().thenCompose( s -> s diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java index 09398a8eb..15e41716a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveMultitenantTest.java @@ -10,8 +10,9 @@ import org.hibernate.LockMode; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; -import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.annotations.EnabledFor; +import org.hibernate.reactive.provider.Settings; +import org.hibernate.reactive.stage.Stage; import org.junit.jupiter.api.Test; @@ -97,6 +98,27 @@ public void testTenantSelection(VertxTestContext context) { ); } + @Test + public void testTenantSelectionWithProxy(VertxTestContext context) { + TENANT_RESOLVER.setTenantIdentifier( TENANT_1 ); + Stage.Session t1Session = getSessionFactory().createSession(); + test( + context, t1Session + .createNativeQuery( "select current_database()" ) + .getSingleResult() + .thenAccept( result -> assertThat( result ).isEqualTo( TENANT_1.getDbName() ) ) + .thenCompose( v -> t1Session.close() ) + .thenAccept( v -> TENANT_RESOLVER.setTenantIdentifier( TENANT_2 ) ) + .thenApply( v -> getSessionFactory().createSession() ) + .thenCompose( t2Session -> t2Session + .createNativeQuery( "select current_database()" ) + .getSingleResult() + .thenAccept( result -> assertThat( result ).isEqualTo( TENANT_2.getDbName() ) ) + .thenCompose( v -> t2Session.close() ) + ) + ); + } + @Test public void testTenantSelectionStatelessSession(VertxTestContext context) { TENANT_RESOLVER.setTenantIdentifier( TENANT_1 ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java index 369dc983c..d83cb64cd 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveSessionTest.java @@ -13,7 +13,13 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.reactive.common.AffectedEntities; +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.mutiny.impl.MutinySessionImpl; +import org.hibernate.reactive.mutiny.impl.MutinyStatelessSessionImpl; +import org.hibernate.reactive.pool.ReactiveConnection; import org.hibernate.reactive.stage.Stage; +import org.hibernate.reactive.stage.impl.StageSessionImpl; +import org.hibernate.reactive.stage.impl.StageStatelessSessionImpl; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -55,6 +61,104 @@ private CompletionStage selectNameFromId(Integer id) { ); } + @Test + public void reactivePersistFindRemoveWithSessionProxy(VertxTestContext context) { + final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" ); + Stage.Session session = getSessionFactory().createSession(); + assertConnectionIsLazy( ( (StageSessionImpl) session ).getReactiveConnection() ); + session.setBatchSize( 55 ); + + test( context, session + .persist( guineaPig ) + .thenCompose( v -> session.flush() ) + .thenAccept( v -> session.detach( guineaPig ) ) + .thenAccept( v -> assertThat( session.contains( guineaPig ) ).isFalse() ) + .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) + .thenAccept( actualPig -> { + assertThatPigsAreEqual( guineaPig, actualPig ); + assertThat( session.contains( actualPig ) ).isTrue(); + assertThat( session.contains( guineaPig ) ).isFalse(); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.READ ); + assertThat( session.getBatchSize() ).isEqualTo( 55 ); + session.detach( actualPig ); + assertThat( session.contains( actualPig ) ).isFalse(); + } ) + .thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) ) + .thenCompose( session::remove ) + .thenCompose( v -> session.flush() ) + .thenCompose( v -> session.close() ) + ); + } + + @Test + public void reactiveInsertGetDeleteWithStatelessSessionProxy(VertxTestContext context) { + final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" ); + Stage.StatelessSession session = getSessionFactory().createStatelessSession(); + assertConnectionIsLazy( ( (StageStatelessSessionImpl) session ).getReactiveConnection(), true ); + test( context, session + .insert( guineaPig ) + .thenCompose( v -> session.get( GuineaPig.class, guineaPig.getId() ) ) + .thenAccept( actualPig -> assertThatPigsAreEqual( guineaPig, actualPig ) ) + .thenCompose( v -> session.get( GuineaPig.class, guineaPig.getId() ) ) + .thenCompose( session::delete ) + .thenCompose( v -> session.close() ) + ); + } + + @Test + public void reactivePersistFindRemoveWithSessionProxyAndMutiny(VertxTestContext context) { + final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" ); + Mutiny.Session session = getMutinySessionFactory().createSession(); + assertConnectionIsLazy( ( (MutinySessionImpl) session ).getReactiveConnection() ); + session.setBatchSize( 55 ); + test( context, session + .persist( guineaPig ) + .call( session::flush ) + .chain( () -> { + session.detach( guineaPig ); + assertThat( session.contains( guineaPig ) ).isFalse(); + return session.find( GuineaPig.class, guineaPig.getId() ); + } ) + .chain( actualPig -> { + assertThatPigsAreEqual( guineaPig, actualPig ); + assertThat( session.contains( actualPig ) ).isTrue(); + assertThat( session.contains( guineaPig ) ).isFalse(); + assertThat( session.getLockMode( actualPig ) ).isEqualTo( LockMode.READ ); + assertThat( session.getBatchSize() ).isEqualTo( 55 ); + session.detach( actualPig ); + assertThat( session.contains( actualPig ) ).isFalse(); + return session.find( GuineaPig.class, guineaPig.getId() ); + } ) + .chain( session::remove ) + .call( session::flush ) + .eventually( session::close ) + ); + } + + protected void assertConnectionIsLazy(ReactiveConnection connection, boolean stateless) { + assertConnectionIsLazy( connection ); + } + + protected void assertConnectionIsLazy(ReactiveConnection connection) { + assertThat( connection.getClass().getName() ) + .isEqualTo( org.hibernate.reactive.pool.impl.SqlClientPool.class.getName() + "$ProxyConnection" ); + } + + @Test + public void reactiveInsertGetDeleteWithStatelessSessionProxyAndMutiny(VertxTestContext context) { + final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" ); + Mutiny.StatelessSession session = getMutinySessionFactory().createStatelessSession(); + assertConnectionIsLazy( ( (MutinyStatelessSessionImpl) session ).getReactiveConnection(), true ); + test( context, session + .insert( guineaPig ) + .chain( () -> session.get( GuineaPig.class, guineaPig.getId() ) ) + .invoke( actualPig -> assertThatPigsAreEqual( guineaPig, actualPig ) ) + .chain( () -> session.get( GuineaPig.class, guineaPig.getId() ) ) + .call( session::delete ) + .eventually( session::close ) + ); + } + @Test public void reactiveFind(VertxTestContext context) { final GuineaPig expectedPig = new GuineaPig( 5, "Aloi" ); From ed98a36ba218a341fe6a4afb34bb1629b1681a4a Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 28 Oct 2025 15:07:58 +0100 Subject: [PATCH 174/185] [#2518] Test id generation with connection opened lazily --- ...readedInsertionWithLazyConnectionTest.java | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionWithLazyConnectionTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionWithLazyConnectionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionWithLazyConnectionTest.java new file mode 100644 index 000000000..2948cce27 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionWithLazyConnectionTest.java @@ -0,0 +1,278 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.hibernate.SessionFactory; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; +import org.hibernate.reactive.stage.Stage; +import org.hibernate.reactive.util.impl.CompletionStages; +import org.hibernate.reactive.vertx.VertxInstance; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import io.vertx.core.AbstractVerticle; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Promise; +import io.vertx.core.Vertx; +import io.vertx.core.VertxOptions; +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxExtension; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.fail; +import static org.hibernate.cfg.AvailableSettings.SHOW_SQL; +import static org.hibernate.reactive.BaseReactiveTest.setDefaultProperties; +import static org.hibernate.reactive.provider.Settings.POOL_CONNECT_TIMEOUT; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +/** + * This is a multi-threaded stress test, intentionally consuming some time + * that also opens the connection lazily. + * The purpose is to verify that the sequence optimizer used by Hibernate Reactive + * is indeed able to generate unique IDs backed by the database sequences, while + * running multiple operations in different threads and on multiple Vert.x eventloops. + * This is very similar to MultithreadedIdentityGenerationTest except it models + * the full operations including the insert statements, while the latter focuses + * on the generated IDs to be unique; it's useful to maintain both tests as: + * - ID generation needs to be unique so it's good to stress that aspect + * in isolation + * - insert operations are downstream events, so this allows us to test that + * such downstream events are not being unintentionally duplicated/dropped, + * which could actually happen when the id generator triggers unintended + * threading behaviours. + * + * N.B. We actually had a case in which the IDs were uniquely generated but the + * downstream event was being processed twice (or more) concurrently, so it's + * useful to have both integration tests. + * + * A typical reactive application will not require multiple threads, but we + * specifically want to test for the case in which the single ID source is being + * shared across multiple threads and also multiple eventloops. + * @see MultithreadedInsertionTest + */ +@ExtendWith(VertxExtension.class) +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +@Timeout(value = MultithreadedInsertionWithLazyConnectionTest.TIMEOUT_MINUTES, timeUnit = MINUTES) +public class MultithreadedInsertionWithLazyConnectionTest { + + /** + * The number of threads should be higher than the default size of the connection pool so that + * this test is also effective in detecting problems with resource starvation. + */ + private static final int N_THREADS = 12; + private static final int ENTITIES_STORED_PER_THREAD = 2000; + + //Should finish much sooner, but generating this amount of IDs could be slow on some CIs + public static final int TIMEOUT_MINUTES = 10; + + // Keeping this disabled because it generates a lot of queries + private static final boolean LOG_SQL = false; + + /** + * If true, it will print info about the threads + */ + private static final boolean THREAD_PRETTY_MSG = true; + + private static final Latch startLatch = new Latch( "start", N_THREADS ); + private static final Latch endLatch = new Latch( "end", N_THREADS ); + + private static Stage.SessionFactory stageSessionFactory; + private static Vertx vertx; + private static SessionFactory sessionFactory; + + @BeforeAll + public static void setupSessionFactory() { + vertx = Vertx.vertx( getVertxOptions() ); + Configuration configuration = new Configuration(); + setDefaultProperties( configuration ); + configuration.addAnnotatedClass( EntityWithGeneratedId.class ); + configuration.setProperty( SHOW_SQL, String.valueOf( LOG_SQL ) ); + configuration.setProperty( POOL_CONNECT_TIMEOUT, String.valueOf( TIMEOUT_MINUTES * 60 * 1000 ) ); + StandardServiceRegistryBuilder builder = new ReactiveServiceRegistryBuilder() + .applySettings( configuration.getProperties() ) + //Inject our custom vert.x instance: + .addService( VertxInstance.class, () -> vertx ); + StandardServiceRegistry registry = builder.build(); + sessionFactory = configuration.buildSessionFactory( registry ); + stageSessionFactory = sessionFactory.unwrap( Stage.SessionFactory.class ); + } + + private static VertxOptions getVertxOptions() { + final VertxOptions vertxOptions = new VertxOptions(); + vertxOptions.setEventLoopPoolSize( N_THREADS ); + //We relax the blocked thread checks as we'll actually use latches to block them + //intentionally for the purpose of the test; functionally this isn't required + //but it's useful as self-test in the design of this, to ensure that the way + //things are setup are indeed being run in multiple, separate threads. + vertxOptions.setBlockedThreadCheckInterval( TIMEOUT_MINUTES ); + vertxOptions.setBlockedThreadCheckIntervalUnit( TimeUnit.MINUTES ); + return vertxOptions; + } + + @AfterAll + public static void closeSessionFactory() { + stageSessionFactory.close(); + } + + @Test + public void testIdentityGenerator(VertxTestContext context) { + final DeploymentOptions deploymentOptions = new DeploymentOptions(); + deploymentOptions.setInstances( N_THREADS ); + + vertx + .deployVerticle( InsertEntitiesVerticle::new, deploymentOptions ) + .onSuccess( res -> { + endLatch.waitForEveryone(); + context.completeNow(); + } ) + .onFailure( context::failNow ) + .eventually( () -> vertx.close() ); + } + + private static class InsertEntitiesVerticle extends AbstractVerticle { + + int sequentialOperation = 0; + + public InsertEntitiesVerticle() { + } + + @Override + public void start(Promise startPromise) { + startLatch.reached(); + startLatch.waitForEveryone();//Not essential, but to ensure a good level of parallelism + final String initialThreadName = Thread.currentThread().getName(); + final Stage.Session session = stageSessionFactory.createSession(); + storeMultipleEntities( session ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> session + .close() + .thenCompose( handler::getResultAsCompletionStage ) + ) + .whenComplete( (o, throwable) -> { + endLatch.reached(); + if ( throwable != null ) { + prettyOut( throwable.getMessage() ); + startPromise.fail( throwable ); + } + else { + if ( !initialThreadName.equals( Thread.currentThread().getName() ) ) { + prettyOut( "Thread switch detected. Expecting " + initialThreadName + ", actual " + Thread.currentThread().getName() ); + startPromise.fail( "Thread switch detected!" ); + } + else { + startPromise.complete(); + } + } + } ); + } + + private CompletionStage storeMultipleEntities(Stage.Session s) { + return loop( 0, ENTITIES_STORED_PER_THREAD, index -> storeEntity( s ) ); + } + + private CompletionStage storeEntity(Stage.Session s) { + final Thread beforeOperationThread = Thread.currentThread(); + final int localVerticleOperationSequence = sequentialOperation++; + final EntityWithGeneratedId entity = new EntityWithGeneratedId(); + entity.name = beforeOperationThread + "__" + localVerticleOperationSequence; + + return s + .withTransaction( t -> s.persist( entity ) ) + .thenCompose( v -> beforeOperationThread != Thread.currentThread() + ? failedFuture( new IllegalStateException( "Detected an unexpected switch of carrier threads!" ) ) + : voidFuture() ); + } + + @Override + public void stop() { + prettyOut( "Verticle stopped " + super.toString() ); + } + } + + /** + * Trivial entity using default id generation + */ + @Entity + @Table(name = "Entity") + private static class EntityWithGeneratedId { + @Id + @GeneratedValue + Long id; + + String name; + + public EntityWithGeneratedId() { + } + } + + /** + * Custom latch which is rather verbose about threads reaching the milestones, to help verifying the design + */ + private static final class Latch { + private final String label; + private final CountDownLatch countDownLatch; + + public Latch(String label, int membersCount) { + this.label = label; + this.countDownLatch = new CountDownLatch( membersCount ); + } + + public void reached() { + final long count = countDownLatch.getCount(); + countDownLatch.countDown(); + prettyOut( "Reached latch '" + label + "', current countdown is " + ( count - 1 ) ); + } + + public void waitForEveryone() { + try { + boolean reachedZero = countDownLatch.await( TIMEOUT_MINUTES, MINUTES ); + if ( reachedZero ) { + prettyOut( "Everyone has now breached '" + label + "'" ); + } + else { + fail( "Time out reached" ); + } + } + catch ( InterruptedException e ) { + fail( e ); + } + } + } + + private static void prettyOut(final String message) { + if ( THREAD_PRETTY_MSG ) { + final String threadName = Thread.currentThread().getName(); + final long l = System.currentTimeMillis(); + final long seconds = ( l / 1000 ) - initialSecond; + //We prefix log messages by seconds since bootstrap; I'm preferring this over millisecond precision + //as it's not very relevant to see exactly how long each stage took (it's actually distracting) + //but it's more useful to group things coarsely when some lock or timeout introduces a significant + //divide between some operations (when a starvation or timeout happens it takes some seconds). + System.out.println( seconds + " - " + threadName + ": " + message ); + } + } + + private static final long initialSecond = ( System.currentTimeMillis() / 1000 ); + +} From a25ae647e9e3a5953f0a8c6a969d696eb8b3939e Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 31 Oct 2025 14:29:11 +0100 Subject: [PATCH 175/185] [#2518] Add error log when existing test fails Print the exception when there's an error in MultithreadedInsertionTest --- .../java/org/hibernate/reactive/MultithreadedInsertionTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java index ce5945336..7fcf1c8c4 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MultithreadedInsertionTest.java @@ -162,10 +162,12 @@ public void start(Promise startPromise) { .whenComplete( (o, throwable) -> { endLatch.reached(); if ( throwable != null ) { + prettyOut( throwable.getMessage() ); startPromise.fail( throwable ); } else { if ( !initialThreadName.equals( Thread.currentThread().getName() ) ) { + prettyOut( "Thread switch detected. Expecting " + initialThreadName + ", actual " + Thread.currentThread().getName() ); startPromise.fail( "Thread switch detected!" ); } else { From 186a1bad72bbbb8f93f032f5814b4d426aa62524 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 7 Nov 2025 09:44:29 +0100 Subject: [PATCH 176/185] [#2738] Return the correct column data type for Oracle The Oracle schema extractor is only partially implemented, returning data type 0 for most of the SQL column types. This causes the schema validation to fail even if the columns on the table are valid. --- ...leSqlReactiveInformationExtractorImpl.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java index a6cd34af0..bf3d7a1ea 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/OracleSqlReactiveInformationExtractorImpl.java @@ -187,13 +187,23 @@ protected String getResultSetIsNullableLabel() { @Override protected int dataTypeCode(String typeName) { - // ORACLE only supports "float" sql type for double precision - // so return code for double for both double and float column types - if ( typeName.equalsIgnoreCase( "float" ) || - typeName.toLowerCase().startsWith( "double" ) ) { - return Types.DOUBLE; - } - return super.dataTypeCode( typeName ); + return switch ( typeName.toLowerCase() ) { + // ORACLE only supports "float" sql type for double precision + // so return code for double for both double and float column types + case "float", "double", "binary_double" -> Types.DOUBLE; + case "timestamp" -> Types.TIMESTAMP; + case "timestamp with time zone", "timestamp with local time zone" -> Types.TIMESTAMP_WITH_TIMEZONE; + case "clob" -> Types.CLOB; + case "blob" -> Types.BLOB; + case "raw" -> Types.VARBINARY; + case "long raw" -> Types.LONGVARBINARY; + case "ref cursor" -> Types.REF_CURSOR; + case "number" -> Types.NUMERIC; + case "date" -> Types.DATE; + case "nvarchar2" -> Types.NVARCHAR; + case "varchar2" -> Types.VARCHAR; + default -> 0; + }; } @Override From 6165f23f2acb4d3bfa973a2fba83d425924de80b Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 7 Nov 2025 11:41:37 +0100 Subject: [PATCH 177/185] [#2738] Test column type validation in Oracle --- .../reactive/schema/SchemaValidationTest.java | 73 ++++++++++++++++++- .../resources/oracle-SchemaValidationTest.sql | 12 +++ 2 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 hibernate-reactive-core/src/test/resources/oracle-SchemaValidationTest.sql diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java index 6e27e2eaf..1a5942e25 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaValidationTest.java @@ -6,13 +6,16 @@ package org.hibernate.reactive.schema; +import java.net.URL; import java.util.concurrent.CompletionStage; import java.util.stream.Stream; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.reactive.BaseReactiveTest; import org.hibernate.reactive.annotations.DisabledFor; +import org.hibernate.reactive.annotations.EnabledFor; import org.hibernate.reactive.provider.Settings; import org.hibernate.tool.schema.spi.SchemaManagementException; @@ -23,6 +26,7 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -34,6 +38,7 @@ import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MARIA; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.MYSQL; +import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.ORACLE; import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.GROUPED; import static org.hibernate.tool.schema.JdbcMetadataAccessStrategy.INDIVIDUALLY; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -46,7 +51,7 @@ * - TODO: Test that validation fails when a column is the wrong type */ @DisabledFor(value = DB2, reason = "We don't have an information extractor. See https://github.com/hibernate/hibernate-reactive/issues/911") -@DisabledFor(value = {MARIA, MYSQL}, reason = "HHH-18869: Schema creation creates an invalid schema") +@DisabledFor(value = { MARIA, MYSQL }, reason = "HHH-18869: Schema creation creates an invalid schema") public class SchemaValidationTest extends BaseReactiveTest { static Stream settings() { @@ -76,9 +81,18 @@ public void before(VertxTestContext context) { } public CompletionStage setupFactory(String strategy, String type) { + return setupFactory( strategy, type, null ); + } + + public CompletionStage setupFactory(String strategy, String type, String importFile) { Configuration createConf = constructConfiguration( "create", strategy, type ); createConf.addAnnotatedClass( BasicTypesTestEntity.class ); - + if ( importFile != null ) { + final URL importFileURL = Thread.currentThread() + .getContextClassLoader() + .getResource( importFile ); + createConf.setProperty( AvailableSettings.JAKARTA_HBM2DDL_LOAD_SCRIPT_SOURCE, importFileURL.getFile() ); + } // Make sure that the extra table is not in the db Configuration dropConf = constructConfiguration( "drop", strategy, type ); dropConf.addAnnotatedClass( Extra.class ); @@ -96,6 +110,21 @@ public void after(VertxTestContext context) { closeFactory( context ); } + @ParameterizedTest + @MethodSource("settings") + @EnabledFor( ORACLE ) + public void testOracleColumnTypeValidation(final String strategy, final String type, VertxTestContext context) { + test( + context, setupFactory( strategy, type, "oracle-SchemaValidationTest.sql" ) + .thenCompose( v -> { + Configuration validateConf = constructConfiguration( "validate", strategy, type ); + validateConf.addAnnotatedClass( Fruit.class ); + new StandardServiceRegistryBuilder().applySettings( validateConf.getProperties() ); + return setupSessionFactory( validateConf ); + } ) + ); + } + // When we have created the table, the validation should pass @ParameterizedTest @MethodSource("settings") @@ -112,7 +141,6 @@ context, setupFactory( strategy, type ) ); } - // Validation should fail if a table is missing @ParameterizedTest @MethodSource("settings") @@ -151,4 +179,43 @@ public static class Extra { private String description; } + + @Entity(name = "Fruit") + public static class Fruit { + + @Id + @GeneratedValue + private Integer id; + + @Column(name = "something_name", nullable = false, updatable = false) + private String name; + + public Fruit() { + } + + public Fruit(String name) { + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "Fruit{" + id + "," + name + '}'; + } + } } diff --git a/hibernate-reactive-core/src/test/resources/oracle-SchemaValidationTest.sql b/hibernate-reactive-core/src/test/resources/oracle-SchemaValidationTest.sql new file mode 100644 index 000000000..69a01b357 --- /dev/null +++ b/hibernate-reactive-core/src/test/resources/oracle-SchemaValidationTest.sql @@ -0,0 +1,12 @@ +-- Import file for testing schema validation in SchemaValidationTest +drop table if exists Fruit cascade constraints +drop sequence if exists Fruit_SEQ + +-- Create the table manually, so that we can check if the validation succeeds +create sequence fruit_seq start with 1 increment by 50; +create table Fruit (id number(10,0) not null, something_name nvarchar2(20) not null, primary key (id)) + +INSERT INTO fruit(id, something_name) VALUES (1, 'Cherry'); +INSERT INTO fruit(id, something_name) VALUES (2, 'Apple'); +INSERT INTO fruit(id, something_name) VALUES (3, 'Banana'); +ALTER SEQUENCE fruit_seq RESTART start with 4; \ No newline at end of file From fb259700d9aaffe301c42a4322c0da2e68109233 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 03:33:19 +0000 Subject: [PATCH 178/185] Bump db2_community/db2 from 12.1.2.0 to 12.1.3.0 in /tooling/docker Bumps db2_community/db2 from 12.1.2.0 to 12.1.3.0. --- updated-dependencies: - dependency-name: db2_community/db2 dependency-version: 12.1.3.0 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tooling/docker/db2.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling/docker/db2.Dockerfile b/tooling/docker/db2.Dockerfile index b70ff8a80..fbd438115 100644 --- a/tooling/docker/db2.Dockerfile +++ b/tooling/docker/db2.Dockerfile @@ -1,3 +1,3 @@ # IBM DB2 # See https://hub.docker.com/r/ibmcom/db2 -FROM icr.io/db2_community/db2:12.1.2.0 +FROM icr.io/db2_community/db2:12.1.3.0 From 328f82632df4b0fb6497db9f94a287a3efde0258 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 6 Nov 2025 16:31:34 +0100 Subject: [PATCH 179/185] [#2737] MariaDB, mutation delete query does not delete an entity with joined inheritance --- .../temptable/ReactiveTableBasedDeleteHandler.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java index c27fbf502..66a6ee910 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/mutation/internal/temptable/ReactiveTableBasedDeleteHandler.java @@ -98,14 +98,12 @@ public CompletionStage reactiveExecute(JdbcParameterBindings jdbcParame private CompletionStage deleteRows(JdbcParameterBindings jdbcParameterBindings, StandardReactiveJdbcMutationExecutor jdbcMutationExecutor, SqmJdbcExecutionContextAdapter executionContext, int[] rows) { if ( getEntityDescriptor() instanceof UnionSubclassEntityPersister ) { - return CompletionStages - .loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + return loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) .thenApply( tot -> rows[0] += tot ) ); } else { - return CompletionStages - .loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + return loop( getDeletes(), delete -> reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) .thenApply( tot -> rows[0] = tot ) ); } @@ -128,8 +126,10 @@ private CompletionStage executeDelete( ); } return loop( getCollectionTableDeletes(), delete -> - reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) - ).thenApply( v -> rows ); + reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + ).thenCompose( v -> loop( getDeletes(), delete -> + reactiveExecute( jdbcParameterBindings, delete, jdbcMutationExecutor, executionContext ) + ) ).thenApply( v -> rows ); } private static CompletionStage reactiveExecute( From d86bd66ff65595c6e215f65c2b794d6037b4273e Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 6 Nov 2025 16:33:39 +0100 Subject: [PATCH 180/185] [#2737] Add test for MariaDB, mutation delete query does not delete an entity with joined inheritance --- .../JoinedSubclassInheritanceTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java index 432535b51..d12e4aded 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java @@ -212,6 +212,29 @@ public void testHqlInsertWithTransaction(VertxTestContext context) { ); } + @Test + public void testHqlDelete(VertxTestContext context) { + final Integer id = 1; + final String title = "Spell Book: A Comprehensive Guide to Magic Spells and Incantations"; + SpellBook spellBook = new SpellBook( id, title, true, new Date() ); + test( context, getMutinySessionFactory().withTransaction( session -> session.persist( spellBook ) ) + .call( () -> getMutinySessionFactory().withTransaction( session -> session + .createMutationQuery( + "delete from SpellBook where id = :id and forbidden = :forbidden and title = :title" ) + .setParameter( "id", id ) + .setParameter( "title", title ) + .setParameter( "forbidden", true ) + .executeUpdate() ) + ) + .call( () -> getMutinySessionFactory().withTransaction( session -> session + .createSelectionQuery( "from SpellBook g where g.id = :id ", SpellBook.class ) + .setParameter( "id", id ) + .getSingleResultOrNull() + .invoke( Assertions::assertNull ) ) + ) + ); + } + @Entity(name="SpellBook") @Table(name = "SpellBookJS") @DiscriminatorValue("S") From e2c33db45168f8e23fcf47b96ce8ddb0e202b2bc Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 11 Nov 2025 09:39:48 +0100 Subject: [PATCH 181/185] [#2763] Ugrade Hibernate ORM to 7.2.0.CR2 Update the catalog and fix compilation errors. --- gradle/libs.versions.toml | 4 ++-- .../provider/service/NoJdbcEnvironmentInitiator.java | 2 ++ .../hibernate/reactive/testing/TestingRegistryExtension.java | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dfce33ae2..d5b618e89 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] assertjVersion = "3.27.6" -hibernateOrmVersion = "7.2.0.CR1" -hibernateOrmGradlePluginVersion = "7.2.0.CR1" +hibernateOrmVersion = "7.2.0.CR2" +hibernateOrmGradlePluginVersion = "7.2.0.CR2" jacksonDatabindVersion = "2.20.1" jbossLoggingAnnotationVersion = "3.0.4.Final" jbossLoggingVersion = "3.6.1.Final" diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java index 0cf59c413..cf3de29fb 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/NoJdbcEnvironmentInitiator.java @@ -10,6 +10,7 @@ import org.hibernate.engine.jdbc.connections.spi.DatabaseConnectionInfo; import org.hibernate.engine.jdbc.dialect.spi.DialectFactory; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.jdbc.env.JdbcMetadataOnBoot; import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl; import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; @@ -77,6 +78,7 @@ protected JdbcEnvironmentImpl getJdbcEnvironmentWithDefaults( @Override protected JdbcEnvironmentImpl getJdbcEnvironmentUsingJdbcMetadata( + JdbcMetadataOnBoot jdbcMetadataAccess, Map configurationValues, ServiceRegistryImplementor registry, DialectFactory dialectFactory, diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/testing/TestingRegistryExtension.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/testing/TestingRegistryExtension.java index d3a0f3528..0a4e82468 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/testing/TestingRegistryExtension.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/testing/TestingRegistryExtension.java @@ -135,6 +135,11 @@ public ServiceBinding locateServiceBinding(Class servi public void destroy() { } + @Override + public boolean isActive() { + return true; + } + @Override public void registerChild(ServiceRegistryImplementor child) { } From bfc5ef7ed2a85a493b3a0c7e80d24b7e075254fb Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 29 Oct 2025 15:52:34 +0100 Subject: [PATCH 182/185] [#2650] FetchModeSubselectEagerTest#testEagerFetchQuery throws NullPointerException The issue is solved by the Hibernate ORM upgrade to 7.2.0.CR2. See issue HHH-19874: https://hibernate.atlassian.net/browse/HHH-19874 --- .../org/hibernate/reactive/FetchModeSubselectEagerTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java index fd05f3942..490e06d0c 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/FetchModeSubselectEagerTest.java @@ -17,7 +17,6 @@ import org.hibernate.cfg.Configuration; import org.hibernate.reactive.util.impl.CompletionStages; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -120,7 +119,6 @@ public void testEagerParentFetch(VertxTestContext context) { } @Test - @Disabled("NullPointerException: see https://hibernate.atlassian.net/browse/HHH-19874") public void testEagerFetchQuery(VertxTestContext context) { Node basik = new Node( "Child" ); basik.parent = new Node( "Parent" ); From ca222225aedac16dcecb9846c6922ba07fa692c4 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Fri, 7 Nov 2025 15:53:24 +0100 Subject: [PATCH 183/185] Simplify the release process --- ci/release/Jenkinsfile | 6 +- publish.gradle | 8 ++ release/build.gradle | 252 +---------------------------------------- 3 files changed, 17 insertions(+), 249 deletions(-) diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 73948feab..44cb63d83 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -178,7 +178,7 @@ pipeline { configFile(fileId: 'release.config.ssh', targetLocation: "${env.HOME}/.ssh/config"), configFile(fileId: 'release.config.ssh.knownhosts', targetLocation: "${env.HOME}/.ssh/known_hosts") ]) { - sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net']) { + sshagent(['ed25519.Hibernate-CI.github.com']) { // set release version // update changelog from JIRA // tags the version @@ -211,7 +211,7 @@ pipeline { string(credentialsId: 'release.gpg.passphrase', variable: 'JRELEASER_GPG_PASSPHRASE'), string(credentialsId: 'Hibernate-CI.github.com', variable: 'JRELEASER_GITHUB_TOKEN') ]) { - sshagent(['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'jenkins.in.relation.to', 'hibernate-ci.frs.sourceforge.net']) { + sshagent(['ed25519.Hibernate-CI.github.com', 'jenkins.in.relation.to']) { // performs documentation upload and Sonatype release // push to github withEnv([ @@ -237,7 +237,7 @@ pipeline { withCredentials([ gitUsernamePassword(credentialsId: 'username-and-token.Hibernate-CI.github.com', gitToolName: 'Default') ]) { - sshagent( ['ed25519.Hibernate-CI.github.com', 'hibernate.filemgmt.jboss.org', 'hibernate-ci.frs.sourceforge.net'] ) { + sshagent( ['ed25519.Hibernate-CI.github.com'] ) { dir( '.release/hibernate.org' ) { checkout scmGit( branches: [[name: '*/production']], diff --git a/publish.gradle b/publish.gradle index 6d05724fa..747415921 100644 --- a/publish.gradle +++ b/publish.gradle @@ -88,3 +88,11 @@ publishing { } } } + +def releasePrepareTask = tasks.register("releasePrepare") { + description "Prepares all the artifacts and documentation for a JReleaser release." + group "Release" + + // Create all the published artifacts (i.e. jars) and move them to the staging directory (for JReleaser): + dependsOn publishAllPublicationsToStagingRepository +} diff --git a/release/build.gradle b/release/build.gradle index 25163e6e9..c50d1c658 100644 --- a/release/build.gradle +++ b/release/build.gradle @@ -18,58 +18,6 @@ final Directory documentationDir = project(":documentation").layout.buildDirecto // Relative path on the static website where the documentation is located final String docWebsiteRelativePath = "reactive/documentation/${projectVersion.family}" -def releaseChecksTask = tasks.register( "releaseChecks" ) { - description 'Checks and preparation for release' - group 'Release' - - doFirst { - logger.lifecycle("Checking that the working tree is clean...") - String uncommittedFiles = executeGitCommand('status', '--porcelain') - if (!uncommittedFiles.isEmpty()) { - throw new GradleException( - "Cannot release because there are uncommitted or untracked files in the working tree.\n" + - "Commit or stash your changes first.\n" + - "Uncommitted files:\n " + - uncommittedFiles - ) - } - - String gitBranchLocal = project.hasProperty( 'gitBranch' ) && !project.property( 'gitBranch' ).isEmpty() - ? project.property( 'gitBranch' ) - : executeGitCommand( 'branch', '--show-current' ).trim() - - String gitRemoteLocal - if ( project.hasProperty( 'gitRemote' ) && !project.property( 'gitRemote' ).isEmpty() ) { - gitRemoteLocal = project.property( 'gitRemote' ) - } - else { - final String remotes = executeGitCommand( 'remote', 'show' ).trim() - final List tokens = remotes.tokenize() - if ( tokens.size() != 1 ) { - throw new GradleException( "Could not determine `gitRemote` property for `releaseChecks` tasks." ) - } - gitRemoteLocal = tokens.get( 0 ) - } - - project.ext { - gitBranch = gitBranchLocal - gitRemote = gitRemoteLocal - } - - logger.lifecycle( "Switching to branch '${project.gitBranch}'..." ) - executeGitCommand( 'checkout', project.gitBranch ) - - logger.lifecycle( "Checking that all commits are pushed..." ) - String diffWithUpstream = executeGitCommand( 'diff', '@{u}' ) - if ( !diffWithUpstream.isEmpty() ) { - throw new GradleException( - "Cannot perform `ciRelease` tasks because there are un-pushed local commits .\n" + - "Push your commits first." - ) - } - } -} - /** * Assembles all documentation into the {buildDir}/documentation directory. */ @@ -80,22 +28,9 @@ def assembleDocumentationTask = tasks.register( 'assembleDocumentation' ) { } assemble.dependsOn assembleDocumentationTask -def changeToReleaseVersionTask = tasks.register( 'changeToReleaseVersion' ) { - description 'Updates `gradle/version.properties` file to the specified release-version' - group 'Release' - - dependsOn releaseChecksTask - - doFirst { - logger.lifecycle( "Updating version-file to release-version : `${project.releaseVersion}`" ) - updateVersionFile( "${project.releaseVersion}" ) - } -} - def updateDocumentationTask = tasks.register( 'updateDocumentation' ) { description "Update the documentation on the cloned static website" dependsOn assembleDocumentationTask - mustRunAfter changeToReleaseVersion // copy documentation outputs into target/documentation: // * this is used in building the dist bundles @@ -116,188 +51,13 @@ def updateDocumentationTask = tasks.register( 'updateDocumentation' ) { } } -def gitPreparationForReleaseTask = tasks.register( 'gitPreparationForRelease' ) { - dependsOn releaseChecksTask, changeToReleaseVersionTask - finalizedBy updateDocumentationTask - - doLast { - logger.lifecycle( "Performing pre-steps Git commit : `${project.releaseVersion}`" ) - executeGitCommand( 'add', '.' ) - executeGitCommand( 'commit', '-m', "Update project version to : `${project.releaseVersion}`" ) - } -} - -def changeToDevelopmentVersionTask = tasks.register( 'changeToDevelopmentVersion' ) { - description 'Updates `gradle/version.properties` file to the specified development-version' - group 'Release' - - dependsOn releaseChecksTask - - doFirst { - logger.lifecycle( "Updating version-file to development-version : `${project.developmentVersion}`" ) - updateVersionFile( "${project.developmentVersion}" ) - } -} - -def releasePreparePostGitTask = tasks.register( 'gitTasksAfterRelease' ) { - dependsOn changeToDevelopmentVersionTask - - doLast { - if ( project.createTag ) { - logger.lifecycle( "Tagging release : `${project.releaseTag}`..." ) - executeGitCommand( 'tag', '-a', project.releaseTag, '-m', "Release $project.projectVersion" ) - } - - logger.lifecycle( "Performing post-steps Git commit : `${project.releaseVersion}`" ) - executeGitCommand( 'add', '.' ) - executeGitCommand( 'commit', '-m', "Update project version to : `${project.developmentVersion}`" ) - } -} - -void updateVersionFile(var version) { - logger.lifecycle( "Updating `gradle/version.properties` version to `${version}`" ) - project.versionFile.text = "projectVersion=${version}" - project.version = version -} - -def publishReleaseArtifactsTask = tasks.register( 'publishReleaseArtifacts' ) { - dependsOn updateDocumentationTask - - mustRunAfter gitPreparationForReleaseTask -} - -def releasePerformPostGitTask = tasks.register( 'gitTasksAfterReleasePerform' ) { - - doLast { - if ( project.createTag ) { - logger.lifecycle( "Pushing branch and tag to remote `${project.gitRemote}`..." ) - executeGitCommand( 'push', '--atomic', project.gitRemote, project.gitBranch, project.releaseTag ) - } - else { - logger.lifecycle( "Pushing branch to remote `${project.gitRemote}`..." ) - executeGitCommand( 'push', project.gitRemote, project.gitBranch ) - } - } -} - def releasePrepareTask = tasks.register( "releasePrepare" ) { - description "On a local checkout, performs all the changes required for the release, website updates included" - group "Release" - - dependsOn gitPreparationForReleaseTask - - finalizedBy releasePreparePostGitTask -} - -def releasePerformTask = tasks.register( 'releasePerform' ) { - group 'Release' - description 'Performs a release on local check-out, including updating changelog and ' - - dependsOn publishReleaseArtifactsTask - - finalizedBy releasePerformPostGitTask -} - -/* -* Release everything -*/ -def releaseTask = tasks.register( 'release' ) { - description 'Performs a release on local check-out' - group 'Release' - - dependsOn releasePrepareTask - dependsOn releasePerformTask -} - -def ciReleaseTask = tasks.register( 'ciRelease' ) { - description "Triggers the release on CI: creates commits to change the version (release, then development), creates a tag, pushes everything. Then CI will take over and perform the release." + description "Prepares all the artifacts and documentation for a JReleaser release." group "Release" - dependsOn releaseTask -} - -static String executeGitCommand(Object ... subcommand){ - List command = ['git'] - Collections.addAll( command, subcommand ) - def proc = command.execute() - def code = proc.waitFor() - def stdout = inputStreamToString( proc.getInputStream() ) - def stderr = inputStreamToString( proc.getErrorStream() ) - if ( code != 0 ) { - throw new GradleException( "An error occurred while executing " + command + "\n\nstdout:\n" + stdout + "\n\nstderr:\n" + stderr ) - } - return stdout -} - -static String inputStreamToString(InputStream inputStream) { - inputStream.withCloseable { ins -> - new BufferedInputStream(ins).withCloseable { bis -> - new ByteArrayOutputStream().withCloseable { buf -> - int result = bis.read() - while (result != -1) { - buf.write((byte) result) - result = bis.read() - } - return buf.toString(StandardCharsets.UTF_8.name()) - } - } - } -} - -gradle.getTaskGraph().whenReady { tg-> - if ( ( tg.hasTask( project.tasks.releasePrepare ) || tg.hasTask( project.tasks.releasePerform ) ) - && ! project.getGradle().getStartParameter().isDryRun() ) { - String releaseVersionLocal - String developmentVersionLocal - - def console = tg.hasTask( project.tasks.release ) && !tg.hasTask( project.tasks.ciRelease ) - ? System.console() - : null - - if (project.hasProperty('releaseVersion')) { - releaseVersionLocal = project.property('releaseVersion') - } - else { - if (console) { - // prompt for `releaseVersion` - releaseVersionLocal = console.readLine('> Enter the release version: ') - } - else { - throw new GradleException( - "`release`-related tasks require the following properties: 'releaseVersion', 'developmentVersion'" - ) - } - } - - if (project.hasProperty('developmentVersion')) { - developmentVersionLocal = project.property('developmentVersion') - } - else { - if (console) { - // prompt for `developmentVersion` - developmentVersionLocal = console.readLine('> Enter the next development version: ') - } - else { - throw new GradleException( - "`release`-related tasks require the following properties: 'releaseVersion', 'developmentVersion'" - ) - } - } - - assert releaseVersionLocal != null && developmentVersionLocal != null - - // set up information for the release-related tasks - project.ext { - releaseVersion = releaseVersionLocal - developmentVersion = developmentVersionLocal - createTag = !project.hasProperty('noTag') - releaseTag = project.createTag ? determineReleaseTag(releaseVersionLocal) : '' - } - } -} - -static String determineReleaseTag(String releaseVersion) { - return releaseVersion.endsWith( '.Final' ) - ? releaseVersion.replace( ".Final", "" ) - : releaseVersion + // Render the Documentation/Javadocs and move them to the staging directory (for JReleaser): + dependsOn updateDocumentationTask + // Create all the published artifacts (i.e. jars) and move them to the staging directory (for JReleaser): + // this one is defined in the publish.gradle + // dependsOn project.getTasksByName(publishAllPublicationsToStagingRepository, false) } From 066ca5581fe163cb24ef7dd47bd00ca36fc56e97 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 13 Nov 2025 15:47:49 +0100 Subject: [PATCH 184/185] Simplify the release process, removed anymore used docWebsiteRelativePath --- release/build.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/release/build.gradle b/release/build.gradle index c50d1c658..ff80aff49 100644 --- a/release/build.gradle +++ b/release/build.gradle @@ -15,9 +15,6 @@ description = 'Release module' // The folder containing the rendered documentation final Directory documentationDir = project(":documentation").layout.buildDirectory.get() -// Relative path on the static website where the documentation is located -final String docWebsiteRelativePath = "reactive/documentation/${projectVersion.family}" - /** * Assembles all documentation into the {buildDir}/documentation directory. */ From adb9dd324a68808c9333617da228054169a27d22 Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Thu, 13 Nov 2025 12:22:02 +0100 Subject: [PATCH 185/185] Add a GitHub release "extra notes" template --- ci/release/Jenkinsfile | 3 ++- github_release_notes.md | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 github_release_notes.md diff --git a/ci/release/Jenkinsfile b/ci/release/Jenkinsfile index 44cb63d83..9a631cf31 100644 --- a/ci/release/Jenkinsfile +++ b/ci/release/Jenkinsfile @@ -217,7 +217,8 @@ pipeline { withEnv([ "DISABLE_REMOTE_GRADLE_CACHE=true" ]) { - sh ".release/scripts/publish.sh -j ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" + def ghReleaseNote = sh('realpath -e github_release_notes.md 2>/dev/null', returnStdout: true).trim() + sh ".release/scripts/publish.sh -j ${ghReleaseNote != '' ? '--notes=' + ghReleaseNote : ''} ${env.SCRIPT_OPTIONS} ${env.PROJECT} ${env.RELEASE_VERSION} ${env.DEVELOPMENT_VERSION} ${env.GIT_BRANCH}" } } } diff --git a/github_release_notes.md b/github_release_notes.md new file mode 100644 index 000000000..db3469c5d --- /dev/null +++ b/github_release_notes.md @@ -0,0 +1,3 @@ + +* See the [website](https://hibernate.org/reactive/releases/{{releaseVersionFamily}}) for requirements and compatibilities. +* See the [What's New](https://hibernate.org/reactive/releases/{{releaseVersionFamily}}/#whats-new) guide for details about new features and capabilities.