diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index b3cd485307..6827950b0e 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -1074,6 +1074,16 @@ public class ProcedureCallImpl throw new UnsupportedOperationException( "Not supported for procedure calls" ); } + @Override + public Query ascending(int element) { + throw new UnsupportedOperationException( "Not supported for procedure calls" ); + } + + @Override + public Query descending(int element) { + throw new UnsupportedOperationException( "Not supported for procedure calls" ); + } + @Override public Query unordered() { return this; diff --git a/hibernate-core/src/main/java/org/hibernate/query/Query.java b/hibernate-core/src/main/java/org/hibernate/query/Query.java index 6cb3e78e55..cf18c995d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Query.java @@ -905,6 +905,12 @@ public interface Query extends SelectionQuery, MutationQuery, TypedQuery descending(SingularAttribute attribute); + @Override + Query ascending(int element); + + @Override + Query descending(int element); + @Override Query unordered(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java index 8c425e46d9..fe29909370 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java @@ -493,6 +493,28 @@ public interface SelectionQuery extends CommonQueryContract { @Incubating SelectionQuery descending(SingularAttribute attribute); + /** + * Add an element of the select list to be used to order the query results + * in ascending order. + * + * @param element an integer identifying an element of the select list + * + * @since 6.3 + */ + @Incubating + SelectionQuery ascending(int element); + + /** + * Add an element of the select list to be used to order the query results + * in descending order. + * + * @param element an integer identifying an element of the select list + * + * @since 6.3 + */ + @Incubating + SelectionQuery descending(int element); + /** * Clear the ordering conditions for this query. * diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmQueryImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmQueryImplementor.java index 7c89ac46f6..038972830a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmQueryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/spi/SqmQueryImplementor.java @@ -136,8 +136,22 @@ public interface SqmQueryImplementor extends QueryImplementor, SqmQuery, N return this; } + @Override + default SqmQueryImplementor ascending(int element) { + addOrdering( element, SortOrder.ASCENDING ); + return this; + } + + @Override + default SqmQueryImplementor descending(int element) { + addOrdering( element, SortOrder.DESCENDING ); + return this; + } + SqmQueryImplementor addOrdering(SingularAttribute attribute, SortOrder order); + SqmQueryImplementor addOrdering(int element, SortOrder order); + @Override SqmQueryImplementor unordered(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java index 4205159cec..e82d96d660 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java @@ -39,7 +39,6 @@ import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.Query; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; -import org.hibernate.query.SelectionQuery; import org.hibernate.query.TupleTransformer; import org.hibernate.query.named.NamedQueryMemento; @@ -288,6 +287,16 @@ public abstract class AbstractQuery throw new UnsupportedOperationException( "Should be implemented by " + this.getClass().getName() ); } + @Override + public Query ascending(int element) { + throw new UnsupportedOperationException( "Should be implemented by " + this.getClass().getName() ); + } + + @Override + public Query descending(int element) { + throw new UnsupportedOperationException( "Should be implemented by " + this.getClass().getName() ); + } + @Override public Query unordered() { throw new UnsupportedOperationException( "Should be implemented by " + this.getClass().getName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index ebc896c88d..b6bb45e1c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -1510,6 +1510,16 @@ public class NativeQueryImpl throw new UnsupportedOperationException("Not yet supported for native queries"); } + @Override + public Query ascending(int element) { + throw new UnsupportedOperationException("Not yet supported for native queries"); + } + + @Override + public Query descending(int element) { + throw new UnsupportedOperationException("Not yet supported for native queries"); + } + @Override public Query unordered() { return this; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index bd67d61c94..aaa44d1722 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -88,6 +88,7 @@ import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; +import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmParameter; @@ -99,6 +100,7 @@ import org.hibernate.query.sqm.tree.insert.SqmValues; import org.hibernate.query.sqm.tree.select.SqmQueryPart; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelectableNode; +import org.hibernate.query.sqm.tree.select.SqmSortSpecification; import org.hibernate.query.sqm.tree.update.SqmAssignment; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.sql.results.internal.TupleMetadata; @@ -982,6 +984,36 @@ public class QuerySqmImpl } } + @Override + public SqmQueryImplementor addOrdering(int element, SortOrder order) { + if ( sqm instanceof SqmSelectStatement ) { + sqm = sqm.copy( SqmCopyContext.noParamCopyContext() ); + SqmSelectStatement select = (SqmSelectStatement) sqm; + int size = select.getSelection().getSelectionItems().size(); + if ( element < 1) { + throw new IllegalArgumentException("Cannot order by element " + element + " (the first select item is element 1)"); + } + if ( element > size) { + throw new IllegalArgumentException("Cannot order by element " + element + " (there are " + size + " select items)"); + } + NodeBuilder nodeBuilder = sqm.nodeBuilder(); + List orders = new ArrayList<>( select.getOrderList() ); + orders.add( new SqmSortSpecification( + new SqmAliasedNodeRef( + element, + nodeBuilder.getTypeConfiguration().standardBasicTypeForJavaType( Integer.class ), + nodeBuilder + ), + order + ) ); + select.orderBy( orders ); + return this; + } + else { + throw new IllegalStateException( "Not a select query" ); + } + } + @Override public SqmQueryImplementor unordered() { if ( sqm instanceof SqmSelectStatement ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java index 809e9356cb..44e6b77afa 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmSelectionQueryImpl.java @@ -61,11 +61,13 @@ import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKey import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; +import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef; import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.from.SqmRoot; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.query.sqm.tree.select.SqmSelection; +import org.hibernate.query.sqm.tree.select.SqmSortSpecification; import org.hibernate.sql.results.internal.TupleMetadata; import org.hibernate.type.descriptor.java.JavaType; @@ -567,6 +569,18 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery return this; } + @Override + public SqmSelectionQuery ascending(int element) { + addOrdering( element, SortOrder.ASCENDING ); + return this; + } + + @Override + public SqmSelectionQuery descending(int element) { + addOrdering( element, SortOrder.DESCENDING ); + return this; + } + private void addOrdering(SingularAttribute attribute, SortOrder order) { sqm = sqm.copy( SqmCopyContext.noParamCopyContext() ); NodeBuilder nodeBuilder = sqm.nodeBuilder(); @@ -581,6 +595,28 @@ public class SqmSelectionQueryImpl extends AbstractSelectionQuery sqm.orderBy( orders ); } + private void addOrdering(int element, SortOrder order) { + sqm = sqm.copy( SqmCopyContext.noParamCopyContext() ); + int size = sqm.getSelection().getSelectionItems().size(); + if ( element < 1) { + throw new IllegalArgumentException("Cannot order by element " + element + " (the first select item is element 1)"); + } + if ( element > size) { + throw new IllegalArgumentException("Cannot order by element " + element + " (there are " + size + " select items)"); + } + NodeBuilder nodeBuilder = sqm.nodeBuilder(); + List orders = new ArrayList<>( sqm.getOrderList() ); + orders.add( new SqmSortSpecification( + new SqmAliasedNodeRef( + element, + nodeBuilder.getTypeConfiguration().standardBasicTypeForJavaType( Integer.class ), + nodeBuilder + ), + order + ) ); + sqm.orderBy( orders ); + } + @Override public SqmSelectionQuery unordered() { sqm = sqm.copy( SqmCopyContext.noParamCopyContext() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/order/OrderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/order/OrderTest.java index eccbfdb92e..bd90948a28 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/order/OrderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/order/OrderTest.java @@ -210,6 +210,47 @@ public class OrderTest { }); } + @Test void testAscendingDescendingBySelectElement(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery("delete Book").executeUpdate() ); + scope.inTransaction( session -> { + session.persist(new Book("9781932394153", "Hibernate in Action")); + session.persist(new Book("9781617290459", "Java Persistence with Hibernate")); + }); + scope.inSession(session -> { + List titlesAsc = session.createSelectionQuery("select isbn, title from Book", Object[].class) + .ascending(2) + .getResultList() + .stream().map(book -> book[1]) + .collect(toList()); + assertEquals("Hibernate in Action", titlesAsc.get(0)); + assertEquals("Java Persistence with Hibernate", titlesAsc.get(1)); + + List titlesDesc = session.createSelectionQuery("select isbn, title from Book", Object[].class) + .descending(2) + .getResultList() + .stream().map(book -> book[1]) + .collect(toList()); + assertEquals("Hibernate in Action", titlesDesc.get(1)); + assertEquals("Java Persistence with Hibernate", titlesDesc.get(0)); + + List isbnAsc = session.createSelectionQuery("select isbn, title from Book", Object[].class) + .ascending(1).descending(2) + .getResultList() + .stream().map(book -> book[1]) + .collect(toList()); + assertEquals("Hibernate in Action", isbnAsc.get(1)); + assertEquals("Java Persistence with Hibernate", isbnAsc.get(0)); + + List isbnDesc = session.createSelectionQuery("select isbn, title from Book", Object[].class) + .descending(1).descending(2) + .getResultList() + .stream().map(book -> book[1]) + .collect(toList()); + assertEquals("Hibernate in Action", isbnDesc.get(0)); + assertEquals("Java Persistence with Hibernate", isbnDesc.get(1)); + }); + } + @Entity(name="Book") static class Book { @Id String isbn;