diff --git a/documentation/src/main/asciidoc/introduction/Interacting.adoc b/documentation/src/main/asciidoc/introduction/Interacting.adoc index d8795848f7ff..470cda28df3a 100644 --- a/documentation/src/main/asciidoc/introduction/Interacting.adoc +++ b/documentation/src/main/asciidoc/introduction/Interacting.adoc @@ -1091,8 +1091,7 @@ The interface link:{doc-javadoc-url}org/hibernate/query/Path.html[`Path`] may be ---- List booksForPublisher = session.createSelectionQuery("from Book", Book.class) - .addRestriction(Path.root(Book_.publisher) - .get(Publisher_.name) + .addRestriction(Path.from(Book.class).to(Book_.publisher).to(Publisher_.name) .equalTo(publisherName)) .getResultList(); ---- diff --git a/hibernate-core/src/main/java/org/hibernate/query/NamedAttributeRange.java b/hibernate-core/src/main/java/org/hibernate/query/NamedAttributeRange.java index a445bd4ebff2..4b6bdb4a58d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/NamedAttributeRange.java +++ b/hibernate-core/src/main/java/org/hibernate/query/NamedAttributeRange.java @@ -13,7 +13,8 @@ import org.hibernate.query.range.Range; /** - * Restricts an attribute of an entity to a given {@link Range}. + * Restricts an attribute of an entity to a given {@link Range}, + * using a stringly-typed attribute reference. * * @param The entity type * @param The attribute type @@ -36,9 +37,10 @@ public Predicate toPredicate(Root root, CriteriaBuilder builder) { if ( !(attribute instanceof SingularAttribute) ) { throw new IllegalArgumentException( "Attribute '" + attributeName + "' is not singular" ); } - if ( range.getType()!=null && !range.getType().isAssignableFrom( attribute.getJavaType() ) ) { + final Class rangeType = range.getType(); + if ( rangeType != null && !rangeType.isAssignableFrom( attribute.getJavaType() ) ) { throw new IllegalArgumentException( "Attribute '" + attributeName - + "' is not assignable to range of type '" + range.getType().getName() + "'" ); + + "' is not assignable to range of type '" + rangeType.getName() + "'" ); } return range.toPredicate( root.get( attributeName ), builder ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/NamedPathElement.java b/hibernate-core/src/main/java/org/hibernate/query/NamedPathElement.java new file mode 100644 index 000000000000..40baa4007d7a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/NamedPathElement.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query; + +import jakarta.persistence.criteria.Root; + +/** + * A non-root element of a {@link Path}, using a stringly-typed + * attribute reference. + * + * @author Gavin King + */ +record NamedPathElement(Path parent, String attributeName, Class attributeType) + implements Path { + @Override + public Class getType() { + return attributeType; + } + + @Override + public jakarta.persistence.criteria.Path path(Root root) { + final jakarta.persistence.criteria.Path path = parent.path( root ).get( attributeName ); + if ( !attributeType.isAssignableFrom( path.getJavaType() ) ) { + throw new IllegalArgumentException( "Attribute '" + attributeName + + "' is not of type '" + attributeType.getName() + "'" ); + } + return path; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/Path.java b/hibernate-core/src/main/java/org/hibernate/query/Path.java index 4f26d4208d93..789fb42bed12 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Path.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Path.java @@ -18,7 +18,8 @@ * the root entity type of the query. *
  * session.createSelectionQuery("from Book", Book.class)
- *         .addRestriction(root(publisher).get(name).equalTo("Manning"))
+ *         .addRestriction(from(Book.class).to(Book_.publisher).to(Publisher_.name)
+ *                         .equalTo("Manning"))
  *         .getResultList()
  * 
* A compound path-based restriction has the same semantics as the @@ -37,15 +38,21 @@ public interface Path { jakarta.persistence.criteria.Path path(Root root); - default Path get(SingularAttribute attribute) { + Class getType(); + + default Path to(SingularAttribute attribute) { return new PathElement<>( this, attribute ); } - static Path root(SingularAttribute attribute) { - return new PathRoot().get( attribute ); + default Path to(String attributeName, Class attributeType) { + return new NamedPathElement<>( this, attributeName, attributeType ); + } + + static Path from(Class type) { + return new PathRoot<>( type ); } - default Restriction restrict(Range range) { + default Restriction restrict(Range range) { return new PathRange<>( this, range ); } @@ -65,5 +72,7 @@ default Restriction notIn(List values) { return in( values ).negated(); } - //TODO: between, lessThan, greaterThan? + default Restriction notNull() { + return restrict( Range.notNull( getType() ) ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/PathElement.java b/hibernate-core/src/main/java/org/hibernate/query/PathElement.java index cdbac1b5c117..8a68b59d9e4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/PathElement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/PathElement.java @@ -12,7 +12,13 @@ * * @author Gavin King */ -record PathElement(Path parent, SingularAttribute attribute) implements Path { +record PathElement(Path parent, SingularAttribute attribute) + implements Path { + @Override + public Class getType() { + return attribute.getJavaType(); + } + @Override public jakarta.persistence.criteria.Path path(Root root) { return parent.path( root ).get( attribute ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/PathRange.java b/hibernate-core/src/main/java/org/hibernate/query/PathRange.java index c67b8a111cef..3746a517855d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/PathRange.java +++ b/hibernate-core/src/main/java/org/hibernate/query/PathRange.java @@ -17,7 +17,7 @@ * * @author Gavin King */ -record PathRange(Path path, Range range) implements Restriction { +record PathRange(Path path, Range range) implements Restriction { @Override public Restriction negated() { return new Negation<>( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/PathRoot.java b/hibernate-core/src/main/java/org/hibernate/query/PathRoot.java index c8b35d741676..99b2455b7e9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/PathRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/PathRoot.java @@ -11,7 +11,12 @@ * * @author Gavin King */ -record PathRoot() implements Path { +record PathRoot(Class type) implements Path { + @Override + public Class getType() { + return type; + } + @Override @SuppressWarnings("unchecked") public jakarta.persistence.criteria.Path path(Root root) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/Restriction.java b/hibernate-core/src/main/java/org/hibernate/query/Restriction.java index 553f3ce9e987..d6f9da394ffa 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Restriction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Restriction.java @@ -203,6 +203,10 @@ static Restriction notContains(SingularAttribute attribute, St return contains( attribute, substring, caseSensitive ).negated(); } + static Restriction notNull(SingularAttribute attribute) { + return restrict( attribute, Range.notNull( attribute.getJavaType() ) ); + } + static Restriction all(List> restrictions) { return new Conjunction<>( restrictions ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/NamedCriteriaQueryMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/NamedCriteriaQueryMementoImpl.java index eac994273ec7..f967d9892dec 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/NamedCriteriaQueryMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/criteria/internal/NamedCriteriaQueryMementoImpl.java @@ -75,11 +75,7 @@ public SqmQueryImplementor toQuery(SharedSessionContractImplementor session) @Override public SqmSelectionQuery toSelectionQuery(Class resultType, SharedSessionContractImplementor session) { - return new SqmSelectionQueryImpl<>( - this, - resultType, - session - ); + return new SqmSelectionQueryImpl<>( this, resultType, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/NamedHqlQueryMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/NamedHqlQueryMementoImpl.java index d07661291502..f521b302b23e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/NamedHqlQueryMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/NamedHqlQueryMementoImpl.java @@ -137,11 +137,7 @@ public SqmQueryImplementor toQuery(SharedSessionContractImplementor session) @Override public SqmSelectionQuery toSelectionQuery(Class resultType, SharedSessionContractImplementor session) { - return new SqmSelectionQueryImpl<>( - this, - resultType, - session - ); + return new SqmSelectionQueryImpl<>( this, resultType, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/CaseInsensitiveValue.java b/hibernate-core/src/main/java/org/hibernate/query/range/CaseInsensitiveValue.java index 955ff13c7b90..f90a7d149ace 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/CaseInsensitiveValue.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/CaseInsensitiveValue.java @@ -23,13 +23,15 @@ record CaseInsensitiveValue(String value) implements Range { } @Override - public Predicate toPredicate(Path path, CriteriaBuilder builder) { + public Predicate toPredicate(Path path, CriteriaBuilder builder) { // TODO: it would be much better to not do use literal, // and let it be treated as a parameter, but we // we run into the usual bug with parameters in // manipulated SQM trees + @SuppressWarnings("unchecked") + final Path stringPath = (Path) path; // safe, because String is final final Expression literal = builder.literal( value.toLowerCase( Locale.ROOT ) ); - return builder.lower( path ).equalTo( literal ); + return builder.lower( stringPath ).equalTo( literal ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/EmptyRange.java b/hibernate-core/src/main/java/org/hibernate/query/range/EmptyRange.java index c9df845717c8..124831491b97 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/EmptyRange.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/EmptyRange.java @@ -15,12 +15,12 @@ */ record EmptyRange(Class type) implements Range { @Override - public Class getType() { + public Class getType() { return type; } @Override - public Predicate toPredicate(Path path, CriteriaBuilder builder) { + public Predicate toPredicate(Path path, CriteriaBuilder builder) { return builder.disjunction(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/FullRange.java b/hibernate-core/src/main/java/org/hibernate/query/range/FullRange.java index 156b0447dc09..ae48a1b04ad8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/FullRange.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/FullRange.java @@ -15,12 +15,12 @@ */ record FullRange(Class type) implements Range { @Override - public Class getType() { + public Class getType() { return type; } @Override - public Predicate toPredicate(Path path, CriteriaBuilder builder) { + public Predicate toPredicate(Path path, CriteriaBuilder builder) { return builder.conjunction(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/Interval.java b/hibernate-core/src/main/java/org/hibernate/query/range/Interval.java index cec5be129519..f9a627c25714 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/Interval.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/Interval.java @@ -16,7 +16,7 @@ record Interval>(LowerBound lowerBound, UpperBound upperBound) implements Range { @Override - public Predicate toPredicate(Path path, CriteriaBuilder builder) { + public Predicate toPredicate(Path path, CriteriaBuilder builder) { return lowerBound.open() || upperBound.open() ? builder.and( lowerBound.toPredicate( path, builder ), upperBound.toPredicate( path, builder ) ) : builder.between( path, builder.literal( lowerBound.bound() ), builder.literal( upperBound.bound() ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/LowerBound.java b/hibernate-core/src/main/java/org/hibernate/query/range/LowerBound.java index 8c51eb62d841..03ca6c7d64ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/LowerBound.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/LowerBound.java @@ -22,7 +22,7 @@ record LowerBound>(U bound, boolean open) implements Ran } @Override - public Predicate toPredicate(Path path, CriteriaBuilder builder) { + public Predicate toPredicate(Path path, CriteriaBuilder builder) { // TODO: it would be much better to not do use literal, // and let it be treated as a parameter, but we // we run into the usual bug with parameters in diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/NotNull.java b/hibernate-core/src/main/java/org/hibernate/query/range/NotNull.java new file mode 100644 index 000000000000..7d2b1faaa7e4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/range/NotNull.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.query.range; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; + +/** + * A {@link Range} containing every value of the given type, + * except the null value. + * + * @author Gavin King + */ +record NotNull(Class type) implements Range { + @Override + public Class getType() { + return type; + } + + @Override + public Predicate toPredicate(Path path, CriteriaBuilder builder) { + return path.isNotNull(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/Pattern.java b/hibernate-core/src/main/java/org/hibernate/query/range/Pattern.java index 741c44830cb2..ffe461967c25 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/Pattern.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/Pattern.java @@ -21,10 +21,12 @@ record Pattern(String pattern, boolean caseSensitive) implements Range { } @Override - public Predicate toPredicate(Path path, CriteriaBuilder builder) { + public Predicate toPredicate(Path path, CriteriaBuilder builder) { + @SuppressWarnings("unchecked") + final Path stringPath = (Path) path; // safe, because String is final return caseSensitive - ? builder.like( path, builder.literal( pattern ), '\\' ) - : builder.like( builder.lower( path ), + ? builder.like( stringPath, builder.literal( pattern ), '\\' ) + : builder.like( builder.lower( stringPath ), builder.literal( pattern.toLowerCase( Locale.ROOT ) ), '\\' ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/Range.java b/hibernate-core/src/main/java/org/hibernate/query/range/Range.java index af6cfe65c3a8..8d8813ec73b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/Range.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/Range.java @@ -39,7 +39,7 @@ public interface Range { * values. */ @Internal - Predicate toPredicate(Path path, CriteriaBuilder builder); + Predicate toPredicate(Path path, CriteriaBuilder builder); static Range singleValue(U value) { return new Value<>( value ); @@ -123,6 +123,10 @@ static Range full(Class type) { return new FullRange<>( type ); } + static Range notNull(Class type) { + return new NotNull<>( type ); + } + private static String escape(String literal) { final var result = new StringBuilder(); for ( int i = 0; i < literal.length(); i++ ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/UpperBound.java b/hibernate-core/src/main/java/org/hibernate/query/range/UpperBound.java index 4e7e3f113804..2f749796a380 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/UpperBound.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/UpperBound.java @@ -22,7 +22,7 @@ record UpperBound>(U bound, boolean open) implements Ran } @Override - public Predicate toPredicate(Path path, CriteriaBuilder builder) { + public Predicate toPredicate(Path path, CriteriaBuilder builder) { // TODO: it would be much better to not do use literal, // and let it be treated as a parameter, but we // we run into the usual bug with parameters in diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/Value.java b/hibernate-core/src/main/java/org/hibernate/query/range/Value.java index b592e1da29df..13d49c397557 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/Value.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/Value.java @@ -22,7 +22,7 @@ record Value(U value) implements Range { } @Override - public Predicate toPredicate(Path path, CriteriaBuilder builder) { + public Predicate toPredicate(Path path, CriteriaBuilder builder) { // TODO: it would be much better to not do use literal, // and let it be treated as a parameter, but we // we run into the usual bug with parameters in diff --git a/hibernate-core/src/main/java/org/hibernate/query/range/ValueList.java b/hibernate-core/src/main/java/org/hibernate/query/range/ValueList.java index 3afdd925f225..7c1fbc8e6dd1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/range/ValueList.java +++ b/hibernate-core/src/main/java/org/hibernate/query/range/ValueList.java @@ -25,7 +25,7 @@ record ValueList(List values) implements Range { } @Override - public Predicate toPredicate(Path path, CriteriaBuilder builder) { + public Predicate toPredicate(Path path, CriteriaBuilder builder) { return path.in( values.stream().map( builder::literal ).toList() ); } 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 6d040419d438..775eb232456c 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 @@ -169,12 +169,11 @@ public QuerySqmImpl( this.hql = hql; this.resultType = resultType; - this.sqm = hqlInterpretation.getSqmStatement(); + sqm = hqlInterpretation.getSqmStatement(); - this.parameterMetadata = hqlInterpretation.getParameterMetadata(); - this.domainParameterXref = hqlInterpretation.getDomainParameterXref(); - - this.parameterBindings = parameterMetadata.createBindings( session.getFactory() ); + parameterMetadata = hqlInterpretation.getParameterMetadata(); + domainParameterXref = hqlInterpretation.getDomainParameterXref(); + parameterBindings = parameterMetadata.createBindings( session.getFactory() ); if ( sqm instanceof SqmSelectStatement ) { hqlInterpretation.validateResultType( resultType ); @@ -186,7 +185,7 @@ public QuerySqmImpl( } setComment( hql ); - this.tupleMetadata = buildTupleMetadata( sqm, resultType ); + tupleMetadata = buildTupleMetadata( sqm, resultType ); } /** @@ -210,14 +209,10 @@ public QuerySqmImpl( setComment( hql ); domainParameterXref = DomainParameterXref.from( sqm ); - if ( ! domainParameterXref.hasParameters() ) { - parameterMetadata = ParameterMetadataImpl.EMPTY; - } - else { - parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ); - } - - this.parameterBindings = parameterMetadata.createBindings( producer.getFactory() ); + parameterMetadata = !domainParameterXref.hasParameters() + ? ParameterMetadataImpl.EMPTY + : new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ); + parameterBindings = parameterMetadata.createBindings( producer.getFactory() ); // Parameters might be created through HibernateCriteriaBuilder.value which we need to bind here for ( SqmParameter sqmParameter : domainParameterXref.getParameterResolutions().getSqmParameters() ) { @@ -232,11 +227,11 @@ public QuerySqmImpl( validateCriteriaQuery( queryPart ); selectStatement.validateResultType( expectedResultType ); } - else { + else if ( sqm instanceof AbstractSqmDmlStatement update ) { if ( expectedResultType != null ) { throw new IllegalQueryOperationException( "Result type given for a non-SELECT Query", hql, null ); } - ( (AbstractSqmDmlStatement) sqm ).validate( hql ); + update.validate( hql ); } resultType = expectedResultType; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java index f4a69bc8743d..2ca2d793912a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java @@ -18,7 +18,7 @@ import static org.hibernate.query.Order.asc; import static org.hibernate.query.Order.desc; -import static org.hibernate.query.Path.root; +import static org.hibernate.query.Path.from; import static org.hibernate.query.Restriction.all; import static org.hibernate.query.Restriction.any; import static org.hibernate.query.Restriction.between; @@ -42,7 +42,7 @@ public class RestrictionTest { @Test void test(SessionFactoryScope scope) { - scope.inTransaction( session -> session.createMutationQuery("delete Book").executeUpdate() ); + scope.getSessionFactory().getSchemaManager().truncate(); scope.inTransaction( session -> { session.persist(new Book("9781932394153", "Hibernate in Action", 400)); session.persist(new Book("9781617290459", "Java Persistence with Hibernate", 1000)); @@ -149,7 +149,7 @@ void test(SessionFactoryScope scope) { @Test void testPath(SessionFactoryScope scope) { - scope.inTransaction( session -> session.createMutationQuery( "delete Book" ).executeUpdate() ); + scope.getSessionFactory().getSchemaManager().truncate(); scope.inTransaction( session -> { Publisher pub = new Publisher(); pub.name = "Manning"; @@ -173,29 +173,47 @@ void testPath(SessionFactoryScope scope) { @SuppressWarnings( "unchecked" ) var version = (SingularAttribute) pubType.findSingularAttribute("version"); + scope.fromSession( session -> + session.createSelectionQuery( "from Book", Book.class) + .addRestriction( from(Book.class).equalTo( session.find(Book.class, "9781932394153") ) ) + .getSingleResult() ); + + List booksInIsbn = scope.fromSession( session -> + session.createSelectionQuery( "from Book", Book.class) + .addRestriction( from(Book.class).to(isbn).in( List.of("9781932394153", "9781617290459") ) ) + .setOrder( desc( isbn ) ) + .getResultList() ); + assertEquals( 2, booksInIsbn.size() ); List booksWithPub = scope.fromSession( session -> session.createSelectionQuery( "from Book", Book.class) - .addRestriction( root(publisher).get(name).equalTo("Manning") ) + .addRestriction( from(Book.class).to(publisher).to(name).equalTo("Manning") ) .setOrder( desc( title ) ) .getResultList() ); assertEquals( 2, booksWithPub.size() ); List noBookWithPub = scope.fromSession( session -> session.createSelectionQuery( "from Book", Book.class) - .addRestriction( root(publisher).get(name).notEqualTo("Manning") ) + .addRestriction( from(Book.class).to(publisher).to(name).notEqualTo("Manning") ) .setOrder( desc( title ) ) .getResultList() ); assertEquals( 0, noBookWithPub.size() ); List books = scope.fromSession( session -> session.createSelectionQuery( "from Book", Book.class) - .addRestriction( root(title).restrict( containing("hibernate", false) ) ) + .addRestriction( from(Book.class).to(title).restrict( containing("hibernate", false) ) ) .setOrder( desc( title ) ) .getResultList() ); assertEquals( 2, books.size() ); List booksWithPubVersion = scope.fromSession( session -> session.createSelectionQuery( "from Book", Book.class) - .addRestriction( root(publisher).get(version).restrict( greaterThan(5) ) ) + .addRestriction( from(Book.class).to(publisher).to(version).restrict( greaterThan(5) ) ) .getResultList() ); assertEquals( 0, booksWithPubVersion.size() ); + List unsafeTest = scope.fromSession( session -> + session.createSelectionQuery( "from Book", Book.class) + .addRestriction( from(Book.class) + .to("publisher", Publisher.class) + .to("name", String.class).equalTo("Manning") ) + .getResultList() ); + assertEquals( 2, unsafeTest.size() ); } @Entity(name="Book")