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 49b69a0d3..36e1f25f2 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 @@ -24,6 +24,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; import org.hibernate.proxy.HibernateProxy; +import org.hibernate.query.Order; import org.hibernate.query.Page; import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.Identifier; @@ -353,6 +354,30 @@ default SelectionQuery setLockMode(String alias, LockModeType lockModeType) { // */ // Query setLockOptions(LockOptions lockOptions); + /** + * If the result type of this query is an entity class, add one or more + * {@linkplain Order rules} for ordering the query results. + * + * @param orderList one or more instances of {@link Order} + * + * @see Order + * + * @see org.hibernate.query.Query#setOrder(List) + */ + SelectionQuery setOrder(List> orderList); + + /** + * If the result type of this query is an entity class, add a + * {@linkplain Order rule} for ordering the query results. + * + * @param order an instance of {@link Order} + * + * @see Order + * + * @see org.hibernate.query.Query#setOrder(Order) + */ + SelectionQuery setOrder(Order order); + /** * Set the {@link EntityGraph} that will be used as a fetch plan for * the root entity returned by this query. diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java index b09f76085..9cb3a0287 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyQueryImpl.java @@ -14,6 +14,7 @@ import org.hibernate.LockMode; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.query.Order; import org.hibernate.query.Page; import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.Mutiny.Query; @@ -68,6 +69,18 @@ public Query setLockMode(LockMode lockMode) { return this; } + @Override + public Mutiny.SelectionQuery setOrder(List> orders) { + delegate.setOrder( orders ); + return this; + } + + @Override + public Mutiny.SelectionQuery setOrder(Order order) { + delegate.setOrder( (List>) order ); + return this; + } + @Override public Query setPlan(EntityGraph entityGraph) { delegate.applyGraph( (RootGraphImplementor) entityGraph, GraphSemantic.FETCH ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java index f9211f9de..0d037e0ed 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySelectionQueryImpl.java @@ -14,8 +14,8 @@ import org.hibernate.LockMode; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.query.Order; import org.hibernate.query.Page; -import org.hibernate.reactive.mutiny.Mutiny; import org.hibernate.reactive.mutiny.Mutiny.SelectionQuery; import org.hibernate.reactive.query.ReactiveSelectionQuery; @@ -118,7 +118,7 @@ public SelectionQuery setFirstResult(int startPosition) { } @Override - public Mutiny.SelectionQuery setPage(Page page) { + public SelectionQuery setPage(Page page) { setMaxResults( page.getMaxResults() ); setFirstResult( page.getFirstResult() ); return this; @@ -191,6 +191,19 @@ public SelectionQuery setLockMode(String alias, LockMode lockMode) { return this; } + @Override + public SelectionQuery setOrder(List> orders) { + delegate.setOrder( orders ); + return this; + } + + @Override + public SelectionQuery setOrder(Order order) { + delegate.setOrder( order ); + return this; + } + + @Override public SelectionQuery setParameter(String name, Object value) { delegate.setParameter( name, value ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java index 581731b49..81508c13b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveSelectionQuery.java @@ -22,6 +22,7 @@ import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.query.BindableType; import org.hibernate.query.CommonQueryContract; +import org.hibernate.query.Order; import org.hibernate.query.QueryParameter; import jakarta.persistence.CacheRetrieveMode; @@ -123,6 +124,10 @@ default CompletionStage> getReactiveResultList() { void applyGraph(RootGraphImplementor graph, GraphSemantic semantic); + ReactiveSelectionQuery setOrder(List> orderList); + + ReactiveSelectionQuery setOrder(Order order); + ReactiveSelectionQuery enableFetchProfile(String profileName); @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java index 7c076ae27..a441b4b55 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 @@ -29,6 +29,7 @@ import org.hibernate.internal.AbstractSharedSessionContract; import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.query.BindableType; +import org.hibernate.query.Order; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; @@ -473,6 +474,18 @@ public ReactiveNativeQueryImpl setLockMode(String alias, LockMode lockMode) { return this; } + @Override + public ReactiveNativeQueryImpl setOrder(List> orders) { + super.setOrder( orders ); + return this; + } + + @Override + public ReactiveNativeQueryImpl setOrder(Order order) { + super.setOrder( order ); + return this; + } + @Override public ReactiveNativeQueryImpl setComment(String comment) { super.setComment( comment ); 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 92d00b305..e0c71f7e3 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 @@ -9,11 +9,13 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; +import java.util.List; import java.util.Map; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.query.BindableType; +import org.hibernate.query.Order; import org.hibernate.query.QueryParameter; import org.hibernate.query.spi.SqmQuery; import org.hibernate.reactive.query.ReactiveSelectionQuery; @@ -167,6 +169,12 @@

ReactiveSqmSelectionQuery setParameterList( @Override ReactiveSqmSelectionQuery setTimeout(int timeout); + @Override + ReactiveSqmSelectionQuery setOrder(List> orders); + + @Override + ReactiveSqmSelectionQuery setOrder(Order order); + @Override ReactiveSqmSelectionQuery setFetchSize(int fetchSize); 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 4dcd2891c..950fd917a 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 @@ -35,6 +35,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.BindableType; import org.hibernate.query.IllegalQueryOperationException; +import org.hibernate.query.Order; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; @@ -459,6 +460,18 @@ public ReactiveQuerySqmImpl setLockMode(String alias, LockMode lockMode) { return this; } + @Override + public ReactiveQuerySqmImpl setOrder(List> orders) { + super.setOrder( orders ); + return this; + } + + @Override + public ReactiveQuerySqmImpl setOrder(Order order) { + super.setOrder( order ); + return this; + } + @Override public ReactiveQuerySqmImpl setTupleTransformer(TupleTransformer transformer) { throw new UnsupportedOperationException(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java index 88cc98728..138f996a4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSqmSelectionQueryImpl.java @@ -26,6 +26,7 @@ import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.internal.util.collections.IdentitySet; import org.hibernate.query.BindableType; +import org.hibernate.query.Order; import org.hibernate.query.QueryLogging; import org.hibernate.query.QueryParameter; import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext; @@ -251,6 +252,18 @@ public ReactiveSqmSelectionQueryImpl setFollowOnLocking(boolean enable) { return this; } + @Override + public ReactiveSqmSelectionQueryImpl setOrder(List> orders) { + super.setOrder( orders ); + return this; + } + + @Override + public ReactiveSqmSelectionQueryImpl setOrder(Order order) { + super.setOrder( order ); + return this; + } + @Override public ReactiveSqmSelectionQueryImpl setFetchSize(int fetchSize) { super.setFetchSize( fetchSize ); 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 319c1c8f5..5016bcfc3 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 @@ -27,6 +27,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; import org.hibernate.proxy.HibernateProxy; +import org.hibernate.query.Order; import org.hibernate.query.Page; import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.Identifier; @@ -347,6 +348,30 @@ default SelectionQuery setLockMode(String alias, LockModeType lockModeType) { // */ // Query setLockOptions(LockOptions lockOptions); + /** + * If the result type of this query is an entity class, add one or more + * {@linkplain Order rules} for ordering the query results. + * + * @param orderList one or more instances of {@link Order} + * + * @see Order + * + * @see org.hibernate.query.Query#setOrder(List) + */ + SelectionQuery setOrder(List> orderList); + + /** + * If the result type of this query is an entity class, add a + * {@linkplain Order rule} for ordering the query results. + * + * @param order an instance of {@link Order} + * + * @see Order + * + * @see org.hibernate.query.Query#setOrder(Order) + */ + SelectionQuery setOrder(Order order); + /** * Set the {@link EntityGraph} that will be used as a fetch plan for * the root entity returned by this query. diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java index ee0a73b71..3a4948275 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageQueryImpl.java @@ -13,6 +13,7 @@ import org.hibernate.LockMode; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.query.Order; import org.hibernate.query.Page; import org.hibernate.reactive.query.ReactiveQuery; import org.hibernate.reactive.stage.Stage; @@ -59,6 +60,18 @@ public Query setLockMode(LockMode lockMode) { return this; } + @Override + public Stage.SelectionQuery setOrder(List> orders) { + delegate.setOrder( orders ); + return this; + } + + @Override + public Stage.SelectionQuery setOrder(Order order) { + delegate.setOrder( (List>) order ); + return this; + } + @Override public Query setPlan(EntityGraph entityGraph) { delegate.applyGraph( (RootGraphImplementor) entityGraph, GraphSemantic.FETCH ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java index 40e1eda9f..f28bfd9d0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSelectionQueryImpl.java @@ -5,6 +5,7 @@ */ package org.hibernate.reactive.stage.impl; +import java.lang.invoke.MethodHandles; import java.util.List; import java.util.concurrent.CompletionStage; @@ -13,7 +14,10 @@ import org.hibernate.LockMode; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.query.Order; import org.hibernate.query.Page; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.query.ReactiveSelectionQuery; import org.hibernate.reactive.stage.Stage.SelectionQuery; @@ -25,6 +29,7 @@ import jakarta.persistence.Parameter; public class StageSelectionQueryImpl implements SelectionQuery { + private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final ReactiveSelectionQuery delegate; public StageSelectionQueryImpl(ReactiveSelectionQuery delegate) { @@ -188,6 +193,18 @@ public SelectionQuery setLockMode(String alias, LockMode lockMode) { return this; } + @Override + public SelectionQuery setOrder(List> orders) { + delegate.setOrder( orders ); + return this; + } + + @Override + public SelectionQuery setOrder(Order order) { + delegate.setOrder( order ); + return this; + } + @Override public SelectionQuery setParameter(String name, Object value) { delegate.setParameter( name, value ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java new file mode 100644 index 000000000..1808c60a8 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java @@ -0,0 +1,327 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl; + +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.metamodel.SingularAttribute; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.query.Order.asc; +import static org.hibernate.query.Order.desc; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@Timeout(value = 10, timeUnit = MINUTES) +public class OrderTest extends BaseReactiveTest { + final Book book1 = new Book("9781932394153", "Hibernate in Action"); + final Book book2 = new Book("9781617290459", "Java Persistence with Hibernate"); + + @Override + protected Collection> annotatedEntities() { + return List.of( Book.class ); + } + + @BeforeEach + public void populateDB(VertxTestContext context) { + test( context, getSessionFactory() + .withTransaction( session -> session.persist( book1, book2 ) ) + ); + } + + @Test + public void testOrder(VertxTestContext context) { + final SingularAttribute isbn = getIsbnAttribute(); + final SingularAttribute title = getTitleAttribute(); + + test( + context, + getSessionFactory().withSession( session -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( asc( title ) ).getResultList() + .thenAccept( books -> assertThat( books ).contains( book1, book2 ) ) + .thenCompose( v -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( desc( title ) ).getResultList() + ).thenAccept( books -> assertThat( books ).containsExactly( book2, book1 ) ) + .thenCompose( v -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( asc( isbn ) ) + .getResultList() + ).thenAccept( books -> assertThat( books ).containsExactly( book2, book1 ) ) + .thenCompose( v -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( desc( isbn ) ) + .getResultList() + ).thenAccept( books -> assertThat( books ).containsExactly( book1, book2 ) ) + ) + ); + } + + @Test + public void testOrderMutiny(VertxTestContext context) { + final SingularAttribute isbn = getIsbnAttribute(); + final SingularAttribute title = getTitleAttribute(); + + test( + context, + getMutinySessionFactory().withSession( session -> session + .createSelectionQuery( "select title from Book", String.class ) + .getResultList() + .invoke( list -> assertEquals( 2, list.size() ) ) + .chain( v -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( asc( title ) ) + .getResultList() ).invoke( books -> assertThat( books ).contains( book1, book2 ) ) + .chain( v -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( desc( title ) ) + .getResultList() ).invoke( books -> assertThat( books ).containsExactly( book2, book1 ) ) + .chain( v -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( asc( isbn ) ) + .getResultList() ).invoke( books -> assertThat( books ).containsExactly( book2, book1 ) ) + .chain( v -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( desc( isbn ) ) + .getResultList() ).invoke( books -> assertThat( books ).contains( book1, book2 ) ) + ) + ); + } + + @Test + public void testAscDescBySelectElement(VertxTestContext context) { + test( + context, + getSessionFactory().withSession( session -> session + .createSelectionQuery( "select isbn, title from Book", Object[].class ) + .setOrder( asc( 2 ) ).getResultList() + .thenAccept( list -> assertOrderByBookArray( list, book1, book2 ) ) + .thenCompose( v -> session + .createSelectionQuery( "select isbn, title from Book", Object[].class ) + .setOrder( desc( 2 ) ).getResultList() + .thenAccept( list -> assertOrderByBookArray( list, book2, book1 ) ) + ) + ) + ); + } + + @Test + public void testAscDescBySelectElementMutiny(VertxTestContext context) { + test( + context, + getMutinySessionFactory().withSession( session -> session + .createSelectionQuery( "select isbn, title from Book", Object[].class ) + .setOrder( asc( 2 ) ).getResultList() + .invoke( list -> assertOrderByBookArray( list, book1, book2 ) ) + .chain( v -> session + .createSelectionQuery( "select isbn, title from Book", Object[].class ) + .setOrder( desc( 2 ) ) + .getResultList() ).invoke( list -> assertOrderByBookArray( list, book2, book1 ) ) + ) + ); + } + + @Test + public void testOrderWithList(VertxTestContext context) { + final SingularAttribute isbn = getIsbnAttribute(); + final SingularAttribute title = getTitleAttribute(); + + test( + context, + getSessionFactory().withSession( session -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( List.of( asc( isbn ), desc( title ) ) ).getResultList() + .thenAccept( isbnAsc -> assertThat( isbnAsc ).containsExactly( book2, book1 ) ) + .thenCompose( v -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( List.of( desc( isbn ), desc( title ) ) ).getResultList() + .thenAccept( isbnDesc -> assertThat( isbnDesc ).containsExactly( book1, book2 ) ) + ) + ) + ); + } + + @Test + public void testOrderWithListMutiny(VertxTestContext context) { + final SingularAttribute isbn = getIsbnAttribute(); + final SingularAttribute title = getTitleAttribute(); + + test( + context, + getMutinySessionFactory().withSession( session -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( List.of( asc( isbn ), desc( title ) ) ).getResultList() + .invoke( isbnAsc -> assertThat( isbnAsc ).containsExactly( book2, book1 ) ) + .chain( v -> session + .createSelectionQuery( "from Book", Book.class ) + .setOrder( List.of( desc( isbn ), desc( title ) ) ).getResultList() + .invoke( isbnDesc -> assertThat( isbnDesc ).containsExactly( book1, book2 ) ) + ) + ) + ); + } + + @Test + public void testAscDescWithNamedParam(VertxTestContext context) { + final SingularAttribute title = getTitleAttribute(); + + test( + context, + getSessionFactory().withSession( session -> session + .createSelectionQuery( "from Book where title like :title", Book.class ) + .setParameter( "title", "%Hibernate%" ) + .setOrder( asc( title ) ) + .getResultList() + .thenAccept( list -> assertOrderByBook( list, book1, book2 ) ) + .thenCompose( v -> session + .createSelectionQuery( "from Book where title like :title", Book.class ) + .setParameter( "title", "%Hibernate%" ) + .setOrder( desc( title ) ).getResultList() + .thenAccept( isbnDesc -> assertThat( isbnDesc ).containsExactly( book2, book1 ) ) + ) + ) + ); + } + + @Test + public void testAscDescWithNamedParamMutiny(VertxTestContext context) { + final SingularAttribute title = getTitleAttribute(); + + test( + context, + getMutinySessionFactory().withSession( session -> session + .createSelectionQuery( "from Book where title like :title", Book.class ) + .setParameter( "title", "%Hibernate%" ) + .setOrder( asc( title ) ) + .getResultList() + .invoke( list -> assertOrderByBook( list, book1, book2 ) ) + .chain( v -> session + .createSelectionQuery( "from Book where title like :title", Book.class ) + .setParameter( "title", "%Hibernate%" ) + .setOrder( desc( title ) ).getResultList() + .invoke( isbnDesc -> assertThat( isbnDesc ).containsExactly( book2, book1 ) ) + ) + ) + ); + } + + @Test + public void testAscDescWithPositionalParam(VertxTestContext context) { + final SingularAttribute title = getTitleAttribute(); + + test( + context, + getSessionFactory().withSession( session -> session + .createSelectionQuery( "from Book where title like :title", Book.class ) + .setParameter( "title", "%Hibernate%" ) + .setOrder( asc( title ) ) + .getResultList() + .thenAccept( list -> assertOrderByBook( list, book1, book2 ) ) + .thenCompose( v -> session + .createSelectionQuery( "from Book where title like :title", Book.class ) + .setParameter( "title", "%Hibernate%" ) + .setOrder( desc( title ) ).getResultList() + .thenAccept( isbnDesc -> assertThat( isbnDesc ).containsExactly( book2, book1 ) ) + ) + ) + ); + } + + @Test + public void testAscDescWithPositionalParamMutiny(VertxTestContext context) { + final SingularAttribute title = getTitleAttribute(); + + test( + context, + getMutinySessionFactory().withSession( session -> session + .createSelectionQuery( "from Book where title like :title", Book.class ) + .setParameter( "title", "%Hibernate%" ) + .setOrder( asc( title ) ) + .getResultList() + .invoke( list -> assertOrderByBook( list, book1, book2 ) ) + .chain( v -> session + .createSelectionQuery( "from Book where title like :title", Book.class ) + .setParameter( "title", "%Hibernate%" ) + .setOrder( desc( title ) ).getResultList() + .invoke( isbnDesc -> assertThat( isbnDesc ).containsExactly( book2, book1 ) ) + ) + ) + ); + } + + private void assertOrderByBookArray(List resultList, Book first, Book second ) { + List titles = resultList.stream().map( book -> (book[1]) ).collect( toList() ); + assertEquals( first.title, titles.get( 0 ) ); + assertEquals( second.title, titles.get( 1 ) ); + } + + private void assertOrderByBook(List resultList, Book first, Book second ) { + List titles = resultList.stream().map( book -> book.title ).collect( toList() ); + assertEquals( first.title, titles.get( 0 ) ); + assertEquals( second.title, titles.get( 1 ) ); + } + + private SingularAttribute getIsbnAttribute() { + MappingMetamodelImpl metamodel = (MappingMetamodelImpl) getSessionFactory().getMetamodel(); + EntityDomainType bookType = metamodel.getJpaMetamodel().findEntityType( Book.class ); + return bookType.findSingularAttribute( "isbn" ); + } + + private SingularAttribute getTitleAttribute() { + MappingMetamodelImpl metamodel = (MappingMetamodelImpl) getSessionFactory().getMetamodel(); + EntityDomainType bookType = metamodel.getJpaMetamodel().findEntityType( Book.class ); + return bookType.findSingularAttribute( "title" ); + } + + @Entity(name="Book") + static class Book { + @Id + String isbn; + String title; + + Book(String isbn, String title) { + this.isbn = isbn; + this.title = title; + } + + Book() { + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Book book = (Book)o; + return Objects.equals( isbn, book.isbn ) && Objects.equals( + title, + book.title + ); + } + + @Override + public int hashCode() { + return Objects.hash( isbn, title ); + } + } +}