Skip to content

Commit 01e8201

Browse files
committed
Add support for NOT_IN, NOT_LIKE and NEGATING_SIMPLE_PROPERTY
Closes spring-projects#603
1 parent 63f2065 commit 01e8201

File tree

3 files changed

+101
-2
lines changed

3 files changed

+101
-2
lines changed

src/main/java/org/springframework/data/keyvalue/repository/query/PredicateQueryCreator.java

+7
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* {@link AbstractQueryCreator} to create {@link Predicate}-based {@link KeyValueQuery}s.
4242
*
4343
* @author Christoph Strobl
44+
* @author Tom Van Wemmel
4445
* @since 3.3
4546
*/
4647
public class PredicateQueryCreator extends AbstractQueryCreator<KeyValueQuery<Predicate<?>>, Predicate<?>> {
@@ -59,12 +60,16 @@ protected Predicate<?> create(Part part, Iterator<Object> iterator) {
5960
return PredicateBuilder.propertyValueOf(part).isFalse();
6061
case SIMPLE_PROPERTY:
6162
return PredicateBuilder.propertyValueOf(part).isEqualTo(iterator.next());
63+
case NEGATING_SIMPLE_PROPERTY:
64+
return PredicateBuilder.propertyValueOf(part).isEqualTo(iterator.next()).negate();
6265
case IS_NULL:
6366
return PredicateBuilder.propertyValueOf(part).isNull();
6467
case IS_NOT_NULL:
6568
return PredicateBuilder.propertyValueOf(part).isNotNull();
6669
case LIKE:
6770
return PredicateBuilder.propertyValueOf(part).contains(iterator.next());
71+
case NOT_LIKE:
72+
return PredicateBuilder.propertyValueOf(part).contains(iterator.next()).negate();
6873
case STARTING_WITH:
6974
return PredicateBuilder.propertyValueOf(part).startsWith(iterator.next());
7075
case AFTER:
@@ -86,6 +91,8 @@ protected Predicate<?> create(Part part, Iterator<Object> iterator) {
8691
return PredicateBuilder.propertyValueOf(part).matches(iterator.next());
8792
case IN:
8893
return PredicateBuilder.propertyValueOf(part).in(iterator.next());
94+
case NOT_IN:
95+
return PredicateBuilder.propertyValueOf(part).in(iterator.next()).negate();
8996
default:
9097
throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query", part.getType()));
9198

src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
* @author Christoph Strobl
3838
* @author Oliver Gierke
3939
* @author Mark Paluch
40+
* @author Tom Van Wemmel
4041
*/
4142
public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExpression>, String> {
4243

@@ -120,6 +121,9 @@ protected SpelExpression toPredicateExpression(PartTree tree) {
120121
case SIMPLE_PROPERTY:
121122
partBuilder.append("?.equals(").append("[").append(parameterIndex++).append("])");
122123
break;
124+
case NEGATING_SIMPLE_PROPERTY:
125+
partBuilder.append("?.equals(").append("[").append(parameterIndex++).append("]) == false");
126+
break;
123127
case IS_NULL:
124128
partBuilder.append(" == null");
125129
break;
@@ -129,6 +133,9 @@ protected SpelExpression toPredicateExpression(PartTree tree) {
129133
case LIKE:
130134
partBuilder.append("?.contains(").append("[").append(parameterIndex++).append("])");
131135
break;
136+
case NOT_LIKE:
137+
partBuilder.append("?.contains(").append("[").append(parameterIndex++).append("]) == false");
138+
break;
132139
case STARTING_WITH:
133140
partBuilder.append("?.startsWith(").append("[").append(parameterIndex++).append("])");
134141
break;
@@ -175,9 +182,16 @@ protected SpelExpression toPredicateExpression(PartTree tree) {
175182
partBuilder.append(")");
176183
break;
177184

185+
case NOT_IN:
186+
187+
partBuilder.append("[").append(parameterIndex++).append("].contains(");
188+
partBuilder.append("#it?.");
189+
partBuilder.append(part.getProperty().toDotPath().replace(".", "?."));
190+
partBuilder.append(") == false");
191+
break;
192+
178193
case CONTAINING:
179194
case NOT_CONTAINING:
180-
case NEGATING_SIMPLE_PROPERTY:
181195
case EXISTS:
182196
default:
183197
throw new InvalidDataAccessApiUsageException(
@@ -206,6 +220,6 @@ protected SpelExpression toPredicateExpression(PartTree tree) {
206220
}
207221

208222
private static boolean requiresInverseLookup(Part part) {
209-
return part.getType() == Type.IN;
223+
return part.getType() == Type.IN || part.getType() == Type.NOT_IN;
210224
}
211225
}

src/test/java/org/springframework/data/keyvalue/repository/query/AbstractQueryCreatorTestBase.java

+78
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
/**
4545
* @author Christoph Strobl
46+
* @author Tom Van Wemmel
4647
*/
4748
@ExtendWith(MockitoExtension.class)
4849
public abstract class AbstractQueryCreatorTestBase<QUERY_CREATOR extends AbstractQueryCreator<KeyValueQuery<CRITERIA>, ?>, CRITERIA> {
@@ -70,6 +71,17 @@ void equalsReturnsFalseWhenNotMatching() {
7071
assertThat(evaluate("findByFirstname", BRAN.firstname).against(RICKON)).isFalse();
7172
}
7273

74+
@Test
75+
// GH-603
76+
void notEqualsReturnsTrueWhenMatching() {
77+
assertThat(evaluate("findByFirstnameNot", BRAN.firstname).against(RICKON)).isTrue();
78+
}
79+
80+
@Test // GH-603
81+
void notEqualsReturnsFalseWhenNotMatching() {
82+
assertThat(evaluate("findByFirstnameNot", BRAN.firstname).against(BRAN)).isFalse();
83+
}
84+
7385
@Test // DATACMNS-525
7486
void isTrueAssertedProperlyWhenTrue() {
7587
assertThat(evaluate("findBySkinChangerIsTrue").against(BRAN)).isTrue();
@@ -130,6 +142,16 @@ void likeReturnsFalseWhenNotMatching() {
130142
assertThat(evaluate("findByFirstnameLike", "ra").against(ROBB)).isFalse();
131143
}
132144

145+
@Test // GH-603
146+
void notLikeReturnsTrueWhenMatching() {
147+
assertThat(evaluate("findByFirstnameNotLike", "ra").against(ROBB)).isTrue();
148+
}
149+
150+
@Test // GH-603
151+
void notLikeReturnsFalseWhenNotMatching() {
152+
assertThat(evaluate("findByFirstnameNotLike", "ob").against(ROBB)).isFalse();
153+
}
154+
133155
@Test // DATACMNS-525
134156
void endsWithReturnsTrueWhenMatching() {
135157
assertThat(evaluate("findByFirstnameEndingWith", "bb").against(ROBB)).isTrue();
@@ -310,6 +332,53 @@ void inMatchesNullValuesCorrectly() {
310332
.isTrue();
311333
}
312334

335+
@Test // GH-603
336+
void notInReturnsMatchCorrectly() {
337+
338+
ArrayList<String> list = new ArrayList<>();
339+
list.add(ROBB.firstname);
340+
341+
assertThat(evaluate("findByFirstnameNotIn", list).against(JON)).isTrue();
342+
}
343+
344+
@Test // GH-603
345+
void notInNotMatchingReturnsCorrectly() {
346+
347+
ArrayList<String> list = new ArrayList<>();
348+
list.add(ROBB.firstname);
349+
350+
assertThat(evaluate("findByFirstnameNotIn", list).against(ROBB)).isFalse();
351+
}
352+
353+
@Test // GH-603
354+
void notInWithNullCompareValuesCorrectly() {
355+
356+
ArrayList<String> list = new ArrayList<>();
357+
list.add(null);
358+
359+
assertThat(evaluate("findByFirstnameNotIn", list).against(JON)).isTrue();
360+
}
361+
362+
@Test // GH-603
363+
void notInWithNullSourceValuesMatchesCorrectly() {
364+
365+
ArrayList<String> list = new ArrayList<>();
366+
list.add(ROBB.firstname);
367+
368+
assertThat(evaluate("findByFirstnameNotIn", list).against(new PredicateQueryCreatorUnitTests.Person(null, 10)))
369+
.isTrue();
370+
}
371+
372+
@Test // GH-603
373+
void notInMatchesNullValuesCorrectly() {
374+
375+
ArrayList<String> list = new ArrayList<>();
376+
list.add(null);
377+
378+
assertThat(evaluate("findByFirstnameNotIn", list).against(new PredicateQueryCreatorUnitTests.Person(null, 10)))
379+
.isFalse();
380+
}
381+
313382
@Test // DATAKV-185
314383
void noDerivedQueryArgumentsMatchesAlways() {
315384

@@ -363,6 +432,9 @@ interface PersonRepository extends CrudRepository<Person, String> {
363432
// Type.SIMPLE_PROPERTY
364433
Person findByFirstname(String firstname);
365434

435+
// Type.NEGATING_SIMPLE_PROPERTY
436+
Person findByFirstnameNot(String firstname);
437+
366438
// Type.TRUE
367439
Person findBySkinChangerIsTrue();
368440

@@ -404,6 +476,9 @@ interface PersonRepository extends CrudRepository<Person, String> {
404476
// Type.LIKE
405477
Person findByFirstnameLike(String firstname);
406478

479+
// Type.NOT_LIKE
480+
Person findByFirstnameNotLike(String firstname);
481+
407482
// Type.ENDING_WITH
408483
Person findByFirstnameEndingWith(String firstname);
409484

@@ -417,6 +492,9 @@ interface PersonRepository extends CrudRepository<Person, String> {
417492
// Type.IN
418493
Person findByFirstnameIn(ArrayList<String> in);
419494

495+
// Type.NOT_IN
496+
Person findByFirstnameNotIn(ArrayList<String> in);
497+
420498
}
421499

422500
public interface Evaluation {

0 commit comments

Comments
 (0)