diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java index 82f9f10818..72fd8b65c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java @@ -133,4 +133,8 @@ public class ColumnReference implements OrderingExpression, SequencePart { return null; } + @Override + public String toDescriptiveText() { + return "column reference (" + columnExpression + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/DomainPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/DomainPath.java index 35588c2aff..f2ed64fb6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/DomainPath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/DomainPath.java @@ -25,4 +25,9 @@ public interface DomainPath extends OrderingExpression, SequencePart { default PluralAttributeMapping getPluralAttribute() { return getLhs().getPluralAttribute(); } + + @Override + default String toDescriptiveText() { + return "domain path (" + getNavigablePath().getFullPath() + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/FunctionExpression.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/FunctionExpression.java index 08e509d102..3a6f14e94e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/FunctionExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/FunctionExpression.java @@ -120,4 +120,9 @@ public class FunctionExpression implements OrderingExpression, FunctionRendering } sqlAppender.appendSql( ')' ); } + + @Override + public String toDescriptiveText() { + return "function (" + name + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/OrderingExpression.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/OrderingExpression.java index a3b26812f5..d1b77c50fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/OrderingExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/OrderingExpression.java @@ -27,6 +27,8 @@ public interface OrderingExpression extends Node { SqlAstNode resolve(QuerySpec ast, TableGroup tableGroup, String modelPartName, SqlAstCreationState creationState); + String toDescriptiveText(); + /** * Apply the SQL AST sort-specifications associated with this ordering-expression */ diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ParseTreeVisitor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ParseTreeVisitor.java index ec4d8d1f28..466087edb7 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ParseTreeVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ParseTreeVisitor.java @@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping.ordering.ast; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import org.hibernate.internal.util.QuotingHelper; import org.hibernate.query.sqm.NullPrecedence; @@ -68,9 +69,12 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor { } else { throw new OrderByComplianceViolation( - "`@OrderBy` expression (" + parsedSpec.expression().getText() - + ") resolved to `" + orderingExpression - + "` which is not a domain-model reference which violates the JPA specification" + String.format( + Locale.ROOT, + "`@OrderBy` expression (%s) is not a domain-model reference, which violates the Jakarta Persistence specification - %s", + parsedSpec.expression().getText(), + orderingExpression.toDescriptiveText() + ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SelfRenderingOrderingExpression.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SelfRenderingOrderingExpression.java index a823ab0a98..972334aca4 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SelfRenderingOrderingExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/SelfRenderingOrderingExpression.java @@ -66,4 +66,8 @@ public class SelfRenderingOrderingExpression extends SelfRenderingSqlFragmentExp ast.addSortSpecification( new SortSpecification( sortExpression, sortOrder, nullPrecedence ) ); } + @Override + public String toDescriptiveText() { + return "unknown"; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/AddressBook.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/AddressBook.java new file mode 100644 index 0000000000..ee6e70632f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/AddressBook.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.orm.test.mapping.collections.ordering; + +import java.util.Set; + +import jakarta.persistence.Basic; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OrderBy; + +/** + * @author Steve Ebersole + */ +@Entity +public class AddressBook { + @Id + private Integer id; + @Basic + private String name; + @OrderBy( "last_name" ) + @ElementCollection + private Set contacts; + + private AddressBook() { + // for Hibernate use + } + + public AddressBook(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; + } + + public Set getContacts() { + return contacts; + } + + public void setContacts(Set contacts) { + this.contacts = contacts; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/CompliantAddressBook.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/CompliantAddressBook.java new file mode 100644 index 0000000000..ab9cdb312a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/CompliantAddressBook.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.orm.test.mapping.collections.ordering; + +import java.util.Set; + +import jakarta.persistence.Basic; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OrderBy; + +/** + * @author Steve Ebersole + */ +@Entity +public class CompliantAddressBook { + @Id + private Integer id; + @Basic + private String name; + @OrderBy( "lastName" ) + @ElementCollection + private Set contacts; + + private CompliantAddressBook() { + // for Hibernate use + } + + public CompliantAddressBook(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; + } + + public Set getContacts() { + return contacts; + } + + public void setContacts(Set contacts) { + this.contacts = contacts; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/Contact.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/Contact.java new file mode 100644 index 0000000000..59b522b805 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/Contact.java @@ -0,0 +1,56 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.orm.test.mapping.collections.ordering; + +import jakarta.persistence.Basic; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; + +/** + * @author Steve Ebersole + */ +@Embeddable +public class Contact { + @Basic + @Column(name = "last_name") + private String lastName; + @Basic + @Column(name = "first_name") + private String firstName; + @Enumerated(EnumType.STRING) + private TypesOfThings stuff; + public Contact(String lastName, String firstName) { + this.lastName = lastName; + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public TypesOfThings getStuff() { + return stuff; + } + + public void setStuff(TypesOfThings stuff) { + this.stuff = stuff; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/OrderByComplianceTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/OrderByComplianceTests.java new file mode 100644 index 0000000000..39fcdece19 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/OrderByComplianceTests.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.orm.test.mapping.collections.ordering; + +import org.hibernate.SessionFactory; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.metamodel.mapping.ordering.ast.OrderByComplianceViolation; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * @author Steve Ebersole + */ +public class OrderByComplianceTests { + @Test + @ServiceRegistry( settings = @Setting( name= AvailableSettings.JPA_ORDER_BY_MAPPING_COMPLIANCE, value = "false" ) ) + @DomainModel( annotatedClasses = { TypesOfThings.class, Contact.class, AddressBook.class } ) + public void testNonCompliantBaseline(DomainModelScope scope) { + final SessionFactory sessionFactory = scope.getDomainModel().buildSessionFactory(); + assertThat( sessionFactory ).isNotNull(); + } + + @Test + @ServiceRegistry( settings = @Setting( name= AvailableSettings.JPA_ORDER_BY_MAPPING_COMPLIANCE, value = "true" ) ) + @DomainModel( annotatedClasses = { TypesOfThings.class, Contact.class, AddressBook.class } ) + public void testNonCompliantStrictly(DomainModelScope scope) { + try { + final SessionFactory sessionFactory = scope.getDomainModel().buildSessionFactory(); + assertThat( sessionFactory ).isNotNull(); + fail( "Expecting a failure here" ); + } + catch (OrderByComplianceViolation exception) { + assertThat( exception.getMessage() ).isEqualTo( + "`@OrderBy` expression (last_name) is not a domain-model reference " + + "which violates the Jakarta Persistence specification - column reference (last_name)" + ); + } + } + + @Test + @ServiceRegistry( settings = @Setting( name= AvailableSettings.JPA_ORDER_BY_MAPPING_COMPLIANCE, value = "false" ) ) + @DomainModel( annotatedClasses = { TypesOfThings.class, Contact.class, CompliantAddressBook.class } ) + public void testCompliantBaseline(DomainModelScope scope) { + final SessionFactory sessionFactory = scope.getDomainModel().buildSessionFactory(); + assertThat( sessionFactory ).isNotNull(); + } + + @Test + @ServiceRegistry( settings = @Setting( name= AvailableSettings.JPA_ORDER_BY_MAPPING_COMPLIANCE, value = "true" ) ) + @DomainModel( annotatedClasses = { TypesOfThings.class, Contact.class, CompliantAddressBook.class } ) + public void testCompliantStrictly(DomainModelScope scope) { + final SessionFactory sessionFactory = scope.getDomainModel().buildSessionFactory(); + assertThat( sessionFactory ).isNotNull(); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/TypesOfThings.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/TypesOfThings.java new file mode 100644 index 0000000000..971e071f21 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/collections/ordering/TypesOfThings.java @@ -0,0 +1,16 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.orm.test.mapping.collections.ordering; + +/** + * @author Steve Ebersole + */ +public enum TypesOfThings { + PUPPIES, + RAINDROPS, + SMOKE +}