Skip to content

Commit 54a2a6c

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 54a2a6c

File tree

13 files changed

+512
-2
lines changed

13 files changed

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

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)