Skip to content

Commit 442c27f

Browse files
committed
HHH-18979 endsWith(), startsWith(), contains()
1 parent 05d99ea commit 442c27f

File tree

4 files changed

+77
-2
lines changed

4 files changed

+77
-2
lines changed

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ static <T, U extends Comparable<U>> Restriction<T> lessThanOrEqual(SingularAttri
119119
return restrict( attribute, Range.lessThanOrEqualTo( upperBound ) );
120120
}
121121

122+
static <T> Restriction<T> like(
123+
SingularAttribute<T, String> attribute,
124+
String pattern, boolean caseSensitive,
125+
char charWildcard, char stringWildcard) {
126+
return like( attribute, escape( pattern, charWildcard, stringWildcard ), caseSensitive );
127+
}
128+
122129
static <T> Restriction<T> like(SingularAttribute<T, String> attribute, String pattern, boolean caseSensitive) {
123130
return restrict( attribute, Range.pattern( pattern, caseSensitive ) );
124131
}
@@ -135,6 +142,22 @@ static <T> Restriction<T> notLike(SingularAttribute<T, String> attribute, String
135142
return like( attribute, pattern, caseSensitive ).negated();
136143
}
137144

145+
static <T> Restriction<T> startsWith(SingularAttribute<T, String> attribute, String prefix) {
146+
return like( attribute, escape( prefix ) + '%' );
147+
}
148+
149+
static <T> Restriction<T> endWith(SingularAttribute<T, String> attribute, String suffix) {
150+
return like( attribute, '%' + escape( suffix ) );
151+
}
152+
153+
static <T> Restriction<T> contains(SingularAttribute<T, String> attribute, String substring) {
154+
return like( attribute, '%' + escape( substring ) + '%' );
155+
}
156+
157+
static <T> Restriction<T> notContains(SingularAttribute<T, String> attribute, String substring) {
158+
return contains( attribute, substring ).negated();
159+
}
160+
138161
@SafeVarargs
139162
static <T> Restriction<T> and(Restriction<T>... restrictions) {
140163
return new Conjunction<>( java.util.List.of( restrictions ) );
@@ -148,4 +171,36 @@ static <T> Restriction<T> or(Restriction<T>... restrictions) {
148171
static <T> Restriction<T> unrestricted() {
149172
return new Unrestricted<>();
150173
}
174+
175+
private static String escape(String literal, char charWildcard, char stringWildcard) {
176+
final var result = new StringBuilder();
177+
for ( int i = 0; i < literal.length(); i++ ) {
178+
final char ch = literal.charAt( i );
179+
if ( ch == charWildcard ) {
180+
result.append( '_' );
181+
}
182+
else if ( ch == stringWildcard ) {
183+
result.append( '%' );
184+
}
185+
else {
186+
if ( ch=='%' || ch=='_' || ch=='\\' ) {
187+
result.append('\\');
188+
}
189+
result.append( ch );
190+
}
191+
}
192+
return result.toString();
193+
}
194+
195+
private static String escape(String literal) {
196+
final var result = new StringBuilder();
197+
for ( int i = 0; i < literal.length(); i++ ) {
198+
final char ch = literal.charAt( i );
199+
if ( ch=='%' || ch=='_' || ch=='\\' ) {
200+
result.append('\\');
201+
}
202+
result.append( ch );
203+
}
204+
return result.toString();
205+
}
151206
}

hibernate-core/src/main/java/org/hibernate/query/range/Pattern.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ record Pattern(String pattern, boolean caseSensitive) implements Range<String> {
1717
@Override
1818
public Predicate toPredicate(Path<String> path, CriteriaBuilder builder) {
1919
return caseSensitive
20-
? builder.like( path, builder.literal( pattern ) )
20+
? builder.like( path, builder.literal( pattern ), '\\' )
2121
: builder.like( builder.lower( path ),
22-
builder.literal( pattern.toLowerCase( Locale.ROOT ) ) );
22+
builder.literal( pattern.toLowerCase( Locale.ROOT ) ),
23+
'\\' );
2324
}
2425

2526
@Override

hibernate-core/src/main/java/org/hibernate/query/range/Range.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ static Range<String> pattern(String pattern, boolean caseSensitive) {
7979
return new Pattern( pattern, caseSensitive );
8080
}
8181

82+
static Range<String> pattern(String pattern) {
83+
return pattern( pattern, true );
84+
}
85+
8286
static <U> Range<U> empty(Class<U> type) {
8387
return new EmptyRange<>( type );
8488
}

hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,21 @@ void test(SessionFactoryScope scope) {
9191
.addRestriction( Restriction.unrestricted().negated() )
9292
.getResultList() );
9393
assertEquals( 0, noBooks.size() );
94+
List<Book> books1 = scope.fromSession( session ->
95+
session.createSelectionQuery( "from Book", Book.class)
96+
.addRestriction( Restriction.endWith(title, "Hibernate") )
97+
.getResultList() );
98+
assertEquals( 1, books1.size() );
99+
List<Book> books2 = scope.fromSession( session ->
100+
session.createSelectionQuery( "from Book", Book.class)
101+
.addRestriction( Restriction.like(title, "*Hibernat?", false, '?', '*') )
102+
.getResultList() );
103+
assertEquals( 1, books2.size() );
104+
List<Book> books3 = scope.fromSession( session ->
105+
session.createSelectionQuery( "from Book", Book.class)
106+
.addRestriction( Restriction.contains(title, "Hibernate") )
107+
.getResultList() );
108+
assertEquals( 2, books3.size() );
94109
}
95110

96111
@Entity(name="Book")

0 commit comments

Comments
 (0)