HHH-16276 - More readable exception for non-compliant @OrderBy expressions

This commit is contained in:
Steve Ebersole 2023-03-15 19:16:17 -05:00
parent e5aa1413d8
commit eaeb7f38ae
11 changed files with 282 additions and 3 deletions

View File

@ -133,4 +133,8 @@ public class ColumnReference implements OrderingExpression, SequencePart {
return null; return null;
} }
@Override
public String toDescriptiveText() {
return "column reference (" + columnExpression + ")";
}
} }

View File

@ -25,4 +25,9 @@ public interface DomainPath extends OrderingExpression, SequencePart {
default PluralAttributeMapping getPluralAttribute() { default PluralAttributeMapping getPluralAttribute() {
return getLhs().getPluralAttribute(); return getLhs().getPluralAttribute();
} }
@Override
default String toDescriptiveText() {
return "domain path (" + getNavigablePath().getFullPath() + ")";
}
} }

View File

@ -120,4 +120,9 @@ public class FunctionExpression implements OrderingExpression, FunctionRendering
} }
sqlAppender.appendSql( ')' ); sqlAppender.appendSql( ')' );
} }
@Override
public String toDescriptiveText() {
return "function (" + name + ")";
}
} }

View File

@ -27,6 +27,8 @@ public interface OrderingExpression extends Node {
SqlAstNode resolve(QuerySpec ast, TableGroup tableGroup, String modelPartName, SqlAstCreationState creationState); SqlAstNode resolve(QuerySpec ast, TableGroup tableGroup, String modelPartName, SqlAstCreationState creationState);
String toDescriptiveText();
/** /**
* Apply the SQL AST sort-specifications associated with this ordering-expression * Apply the SQL AST sort-specifications associated with this ordering-expression
*/ */

View File

@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping.ordering.ast;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.hibernate.internal.util.QuotingHelper; import org.hibernate.internal.util.QuotingHelper;
import org.hibernate.query.sqm.NullPrecedence; import org.hibernate.query.sqm.NullPrecedence;
@ -68,9 +69,12 @@ public class ParseTreeVisitor extends OrderingParserBaseVisitor<Object> {
} }
else { else {
throw new OrderByComplianceViolation( throw new OrderByComplianceViolation(
"`@OrderBy` expression (" + parsedSpec.expression().getText() String.format(
+ ") resolved to `" + orderingExpression Locale.ROOT,
+ "` which is not a domain-model reference which violates the JPA specification" "`@OrderBy` expression (%s) is not a domain-model reference, which violates the Jakarta Persistence specification - %s",
parsedSpec.expression().getText(),
orderingExpression.toDescriptiveText()
)
); );
} }
} }

View File

@ -66,4 +66,8 @@ public class SelfRenderingOrderingExpression extends SelfRenderingSqlFragmentExp
ast.addSortSpecification( new SortSpecification( sortExpression, sortOrder, nullPrecedence ) ); ast.addSortSpecification( new SortSpecification( sortExpression, sortOrder, nullPrecedence ) );
} }
@Override
public String toDescriptiveText() {
return "unknown";
}
} }

View File

@ -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<Contact> 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<Contact> getContacts() {
return contacts;
}
public void setContacts(Set<Contact> contacts) {
this.contacts = contacts;
}
}

View File

@ -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<Contact> 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<Contact> getContacts() {
return contacts;
}
public void setContacts(Set<Contact> contacts) {
this.contacts = contacts;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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
}