Skip to content

Commit f327d94

Browse files
committed
HHH-18979 prototype for Restrictions
We will need an API like for this for out Jakarta Data 1.1 implementation. But it's actually really quite generally useful to be able to add some restrictions to a query without needing to jump through the hoops of the JPA Criteria API. In some ways, this reminds me of the old Hibernate criteria API from pre-JPA days (but typesafe, of course).
1 parent c4c6967 commit f327d94

File tree

13 files changed

+514
-2
lines changed

13 files changed

+514
-2
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.query;
6+
7+
import jakarta.persistence.criteria.CriteriaBuilder;
8+
import jakarta.persistence.criteria.Expression;
9+
import jakarta.persistence.criteria.Path;
10+
import jakarta.persistence.criteria.Predicate;
11+
import jakarta.persistence.criteria.Root;
12+
import jakarta.persistence.metamodel.SingularAttribute;
13+
import org.hibernate.Incubating;
14+
import org.hibernate.Internal;
15+
16+
import java.util.Locale;
17+
18+
/**
19+
* Specifies an allowed set of range of values for a value being restricted.
20+
*
21+
* @param <U> The type of the value being restricted
22+
*
23+
* @see Restriction
24+
*
25+
* @author Gavin King
26+
*
27+
* @since 7.0
28+
*/
29+
@Incubating
30+
interface Domain<U> {
31+
32+
/**
33+
* Return a JPA Criteria {@link Predicate} constraining the given
34+
* attribute of the given root entity to this domain of allowed
35+
* values.
36+
*/
37+
@Internal
38+
<X> Predicate toPredicate(Path<? extends X> root, SingularAttribute<X,U> attribute, CriteriaBuilder builder);
39+
40+
/**
41+
* Restricts to a single literal value.
42+
*/
43+
record Value<U>(U value) implements Domain<U> {
44+
@Override
45+
public <X> Predicate toPredicate(Path<? extends X> root, SingularAttribute<X, U> attribute, CriteriaBuilder builder) {
46+
// TODO: it would be much better to not do use literal,
47+
// and let it be treated as a parameter, but we
48+
// we run into the usual bug with parameters in
49+
// manipulated SQM trees
50+
final Expression<U> literal = builder.literal( value );
51+
return root.get(attribute).equalTo( literal );
52+
}
53+
}
54+
55+
/**
56+
* Restricts to a list of literal values.
57+
*/
58+
record List<U>(java.util.List<U> values) implements Domain<U> {
59+
@Override
60+
public <X> Predicate toPredicate(Path<? extends X> root, SingularAttribute<X, U> attribute, CriteriaBuilder builder) {
61+
return root.get(attribute).in(values.stream().map(builder::literal).toList());
62+
}
63+
}
64+
65+
/**
66+
* Restricts a string by a pattern.
67+
*/
68+
record Pattern(String pattern, boolean caseSensitive) implements Domain<String> {
69+
@Override
70+
public <X> Predicate toPredicate(Path<? extends X> root, SingularAttribute<X, String> attribute, CriteriaBuilder builder) {
71+
return caseSensitive
72+
? builder.like( root.get(attribute), builder.literal(pattern) )
73+
: builder.like( builder.lower(root.get(attribute)), builder.literal(pattern.toLowerCase(Locale.ROOT)) );
74+
}
75+
}
76+
77+
/**
78+
* Restricts to all values higher than a given lower bound.
79+
*/
80+
record LowerBound<U extends Comparable<U>>(U bound, boolean open) implements Domain<U> {
81+
static <U extends Comparable<U>> LowerBound<U> greaterThan(U bound) {
82+
return new LowerBound<>(bound, true);
83+
}
84+
static <U extends Comparable<U>> LowerBound<U> greaterThanOrEqualTo(U bound) {
85+
return new LowerBound<>(bound, false);
86+
}
87+
88+
@Override
89+
public <X> Predicate toPredicate(Path<? extends X> root, SingularAttribute<X, U> attribute, CriteriaBuilder builder) {
90+
// TODO: it would be much better to not do use literal,
91+
// and let it be treated as a parameter, but we
92+
// we run into the usual bug with parameters in
93+
// manipulated SQM trees
94+
final Expression<U> literal = builder.literal( bound );
95+
return open
96+
? builder.greaterThan( root.get(attribute), literal )
97+
: builder.greaterThanOrEqualTo( root.get(attribute), literal );
98+
}
99+
}
100+
101+
/**
102+
* Restricts to all values lower than a given upper bound.
103+
*/
104+
record UpperBound<U extends Comparable<U>>(U bound, boolean open) implements Domain<U> {
105+
static <U extends Comparable<U>> UpperBound<U> lessThan(U bound) {
106+
return new UpperBound<>(bound, true);
107+
}
108+
static <U extends Comparable<U>> UpperBound<U> lessThanOrEqualTo(U bound) {
109+
return new UpperBound<>(bound, false);
110+
}
111+
112+
@Override
113+
public <X> Predicate toPredicate(Path<? extends X> root, SingularAttribute<X, U> attribute, CriteriaBuilder builder) {
114+
// TODO: it would be much better to not do use literal,
115+
// and let it be treated as a parameter, but we
116+
// we run into the usual bug with parameters in
117+
// manipulated SQM trees
118+
final Expression<U> literal = builder.literal( bound );
119+
return open
120+
? builder.lessThan( root.get(attribute), literal )
121+
: builder.lessThanOrEqualTo( root.get(attribute), literal );
122+
}
123+
}
124+
125+
/**
126+
* Restricts to an upper-bounded and lower-bounded interval.
127+
*/
128+
record Interval<U extends Comparable<U>>(LowerBound<U> lowerBound, UpperBound<U> upperBound)
129+
implements Domain<U> {
130+
static <U extends Comparable<U>> Interval<U> open(U lowerBound, U upperBound) {
131+
return new Interval<>( new LowerBound<>(lowerBound, true),
132+
new UpperBound<>(upperBound, true) );
133+
}
134+
static <U extends Comparable<U>> Interval<U> closed(U lowerBound, U upperBound) {
135+
return new Interval<>( new LowerBound<>(lowerBound, false),
136+
new UpperBound<>(upperBound, false) );
137+
}
138+
139+
@Override
140+
public <X> Predicate toPredicate(Path<? extends X> root, SingularAttribute<X, U> attribute, CriteriaBuilder builder) {
141+
return lowerBound.open || upperBound.open
142+
? builder.and( lowerBound.toPredicate( root, attribute, builder ),
143+
upperBound.toPredicate( root, attribute, builder ) )
144+
: builder.between( root.get(attribute),
145+
builder.literal(lowerBound.bound), builder.literal(upperBound.bound) );
146+
}
147+
}
148+
149+
/**
150+
* Restricts an attribute of an entity to a given {@link Domain}.
151+
*
152+
* @param <X> The entity type
153+
* @param <U> The attribute type
154+
*/
155+
class AttributeRestriction<X,U> implements Restriction<X> {
156+
private final SingularAttribute<X, U> attribute;
157+
private final Domain<U> domain;
158+
159+
AttributeRestriction(SingularAttribute<X, U> attribute, Domain<U> domain) {
160+
this.attribute = attribute;
161+
this.domain = domain;
162+
}
163+
164+
public Restriction<X> negated() {
165+
return new Negated<>(this);
166+
}
167+
168+
@Override
169+
public Predicate toPredicate(Root<? extends X> root, CriteriaBuilder builder) {
170+
return domain.toPredicate( root, attribute, builder );
171+
}
172+
}
173+
174+
/**
175+
* Negates a restriction; a logical NOT.
176+
*
177+
* @param restriction The restriction to be negated
178+
* @param <X> The entity type
179+
*/
180+
record Negated<X>(Restriction<X> restriction) implements Restriction<X> {
181+
@Override
182+
public Restriction<X> negated() {
183+
return restriction;
184+
}
185+
186+
@Override
187+
public Predicate toPredicate(Root<? extends X> root, CriteriaBuilder builder) {
188+
return builder.not(restriction.toPredicate(root, builder));
189+
}
190+
}
191+
192+
/**
193+
* A compound restriction constructed using logical AND.
194+
*
195+
* @param restrictions The restrictions to be AND-ed
196+
* @param <X> The entity type
197+
*/
198+
record Conjunction<X>(java.util.List<? extends Restriction<? super X>> restrictions)
199+
implements Restriction<X> {
200+
@Override
201+
public Restriction<X> negated() {
202+
return new Disjunction<>( restrictions.stream().map(Restriction::negated).toList() );
203+
}
204+
205+
@Override
206+
public Predicate toPredicate(Root<? extends X> root, CriteriaBuilder builder) {
207+
return builder.and( restrictions.stream()
208+
.map( restriction -> restriction.toPredicate(root, builder) )
209+
.toList() );
210+
}
211+
}
212+
213+
/**
214+
* A compound restriction constructed using logical OR.
215+
*
216+
* @param restrictions The restrictions to be OR-ed
217+
* @param <X> The entity type
218+
*/
219+
record Disjunction<X>(java.util.List<? extends Restriction<? super X>> restrictions)
220+
implements Restriction<X> {
221+
@Override
222+
public Restriction<X> negated() {
223+
return new Conjunction<>( restrictions.stream().map( Restriction::negated ).toList() );
224+
}
225+
226+
@Override
227+
public Predicate toPredicate(Root<? extends X> root, CriteriaBuilder builder) {
228+
return builder.or( restrictions.stream()
229+
.map( restriction -> restriction.toPredicate(root, builder) )
230+
.toList() );
231+
}
232+
}
233+
}

hibernate-core/src/main/java/org/hibernate/query/Order.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
*
5252
* @see SelectionQuery#setOrder(Order)
5353
* @see SelectionQuery#setOrder(java.util.List)
54+
* @see Restriction
5455
*
5556
* @author Gavin King
5657
*

hibernate-core/src/main/java/org/hibernate/query/Query.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,9 @@ default Query<R> setPage(Page page) {
927927
@Override @Incubating
928928
Query<R> setOrder(Order<? super R> order);
929929

930+
@Override
931+
Query<R> addRestriction(Restriction<? super R> restriction);
932+
930933
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
931934
// deprecated methods
932935

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.query;
6+
7+
import jakarta.persistence.criteria.CriteriaBuilder;
8+
import jakarta.persistence.criteria.Predicate;
9+
import jakarta.persistence.criteria.Root;
10+
import jakarta.persistence.metamodel.SingularAttribute;
11+
import org.hibernate.Incubating;
12+
import org.hibernate.Internal;
13+
import org.hibernate.query.Domain.AttributeRestriction;
14+
import org.hibernate.query.Domain.Conjunction;
15+
import org.hibernate.query.Domain.Disjunction;
16+
import org.hibernate.query.Domain.Interval;
17+
import org.hibernate.query.Domain.List;
18+
import org.hibernate.query.Domain.LowerBound;
19+
import org.hibernate.query.Domain.Pattern;
20+
import org.hibernate.query.Domain.UpperBound;
21+
import org.hibernate.query.Domain.Value;
22+
23+
/**
24+
* A rule for restricting query results.
25+
* <p>
26+
* This allows restrictions to be added to a {@link Query} by calling
27+
* {@link SelectionQuery#addRestriction(Restriction)}.
28+
* <pre>
29+
* session.createSelectionQuery("from Book", Book.class)
30+
* .addRestriction(Restriction.like(Book_.title, "%Hibernate%", false))
31+
* .addRestriction(Restriction.greaterThan(Book_.pages, 100))
32+
* .setOrder(Order.desc(Book_.title))
33+
* .getResultList() );
34+
* </pre>
35+
*
36+
* @param <X> The entity result type of the query
37+
*
38+
* @apiNote This class is similar to {@code jakarta.data.Restriction}, and
39+
* is used by Hibernate Data Repositories to implement Jakarta Data
40+
* query methods.
41+
*
42+
* @see SelectionQuery#addRestriction(Restriction)
43+
* @see Order
44+
*
45+
* @author Gavin King
46+
*
47+
* @since 7.0
48+
*
49+
*/
50+
@Incubating
51+
public interface Restriction<X> {
52+
53+
/**
54+
* Negate this restriction.
55+
*/
56+
Restriction<X> negated();
57+
58+
/**
59+
* Return a JPA Criteria {@link Predicate} constraining the given
60+
* root entity by this restriction.
61+
*/
62+
@Internal
63+
Predicate toPredicate(Root<? extends X> root, CriteriaBuilder builder);
64+
65+
static <T,U> Restriction<T> equal(SingularAttribute<T,U> attribute, U value) {
66+
return new AttributeRestriction<>( attribute, new Value<>(value) );
67+
}
68+
69+
static <T,U> Restriction<T> unequal(SingularAttribute<T,U> attribute, U value) {
70+
return new AttributeRestriction<>( attribute, new Value<>(value) ).negated();
71+
}
72+
73+
static <T,U> Restriction<T> in(SingularAttribute<T,U> attribute, java.util.List<U> values) {
74+
return new AttributeRestriction<>( attribute, new List<>(values) );
75+
}
76+
77+
static <T,U> Restriction<T> notIn(SingularAttribute<T,U> attribute, java.util.List<U> values) {
78+
return new AttributeRestriction<>( attribute, new List<>(values) ).negated();
79+
}
80+
81+
static <T,U extends Comparable<U>> Restriction<T> between(SingularAttribute<T,U> attribute, U lowerBound, U upperBound) {
82+
return new AttributeRestriction<>( attribute, Interval.closed(lowerBound, upperBound) );
83+
}
84+
85+
static <T,U extends Comparable<U>> Restriction<T> notBetween(SingularAttribute<T,U> attribute, U lowerBound, U upperBound) {
86+
return new AttributeRestriction<>( attribute, Interval.closed(lowerBound, upperBound) ).negated();
87+
}
88+
89+
static <T,U extends Comparable<U>> Restriction<T> greaterThan(SingularAttribute<T,U> attribute, U lowerBound) {
90+
return new AttributeRestriction<>( attribute, LowerBound.greaterThan(lowerBound) );
91+
}
92+
93+
static <T,U extends Comparable<U>> Restriction<T> lessThan(SingularAttribute<T,U> attribute, U upperBound) {
94+
return new AttributeRestriction<>( attribute, UpperBound.lessThan(upperBound) );
95+
}
96+
97+
static <T,U extends Comparable<U>> Restriction<T> greaterThanOrEqual(SingularAttribute<T,U> attribute, U lowerBound) {
98+
return new AttributeRestriction<>( attribute, LowerBound.greaterThanOrEqualTo(lowerBound) );
99+
}
100+
101+
static <T,U extends Comparable<U>> Restriction<T> lessThanOrEqual(SingularAttribute<T,U> attribute, U upperBound) {
102+
return new AttributeRestriction<>( attribute, UpperBound.lessThanOrEqualTo(upperBound) );
103+
}
104+
105+
static <T> Restriction<T> like(SingularAttribute<T,String> attribute, String pattern) {
106+
return like( attribute, pattern, true );
107+
}
108+
109+
static <T> Restriction<T> like(SingularAttribute<T,String> attribute, String pattern, boolean caseSensitive) {
110+
return new AttributeRestriction<>( attribute, new Pattern(pattern, caseSensitive) );
111+
}
112+
113+
static <T> Restriction<T> notLike(SingularAttribute<T,String> attribute, String pattern) {
114+
return notLike( attribute, pattern, true );
115+
}
116+
117+
static <T> Restriction<T> notLike(SingularAttribute<T,String> attribute, String pattern, boolean caseSensitive) {
118+
return new AttributeRestriction<>( attribute, new Pattern(pattern, caseSensitive) ).negated();
119+
}
120+
121+
@SafeVarargs
122+
static <T> Restriction<T> and(Restriction<T>... restrictions) {
123+
return new Conjunction<>( java.util.List.of(restrictions) );
124+
}
125+
126+
@SafeVarargs
127+
static <T> Restriction<T> or(Restriction<T>... restrictions) {
128+
return new Disjunction<>( java.util.List.of(restrictions) );
129+
}
130+
}

0 commit comments

Comments
 (0)