Add query derivation support for NOT_IN, NOT_LIKE and NEGATING_SIMPLE_PROPERTY predicates.

Closes #603
Original pull request: #604
This commit is contained in:
Tom Van Wemmel
2024-11-12 12:46:52 +01:00
committed by Mark Paluch
parent e32d8e25cb
commit 768d0a8a67
3 changed files with 101 additions and 2 deletions

View File

@@ -41,6 +41,7 @@ import org.springframework.util.ObjectUtils;
* {@link AbstractQueryCreator} to create {@link Predicate}-based {@link KeyValueQuery}s.
*
* @author Christoph Strobl
* @author Tom Van Wemmel
* @since 3.3
*/
public class PredicateQueryCreator extends AbstractQueryCreator<KeyValueQuery<Predicate<?>>, Predicate<?>> {
@@ -61,12 +62,16 @@ public class PredicateQueryCreator extends AbstractQueryCreator<KeyValueQuery<Pr
return PredicateBuilder.propertyValueOf(part).isFalse();
case SIMPLE_PROPERTY:
return PredicateBuilder.propertyValueOf(part).isEqualTo(iterator.next());
case NEGATING_SIMPLE_PROPERTY:
return PredicateBuilder.propertyValueOf(part).isEqualTo(iterator.next()).negate();
case IS_NULL:
return PredicateBuilder.propertyValueOf(part).isNull();
case IS_NOT_NULL:
return PredicateBuilder.propertyValueOf(part).isNotNull();
case LIKE:
return PredicateBuilder.propertyValueOf(part).contains(iterator.next());
case NOT_LIKE:
return PredicateBuilder.propertyValueOf(part).contains(iterator.next()).negate();
case STARTING_WITH:
return PredicateBuilder.propertyValueOf(part).startsWith(iterator.next());
case AFTER:
@@ -88,6 +93,8 @@ public class PredicateQueryCreator extends AbstractQueryCreator<KeyValueQuery<Pr
return PredicateBuilder.propertyValueOf(part).matches(iterator.next());
case IN:
return PredicateBuilder.propertyValueOf(part).in(iterator.next());
case NOT_IN:
return PredicateBuilder.propertyValueOf(part).in(iterator.next()).negate();
default:
throw new InvalidDataAccessApiUsageException(String.format("Found invalid part '%s' in query", part.getType()));

View File

@@ -37,6 +37,7 @@ import org.springframework.util.StringUtils;
* @author Christoph Strobl
* @author Oliver Gierke
* @author Mark Paluch
* @author Tom Van Wemmel
*/
public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExpression>, String> {
@@ -120,6 +121,9 @@ public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExp
case SIMPLE_PROPERTY:
partBuilder.append("?.equals(").append("[").append(parameterIndex++).append("])");
break;
case NEGATING_SIMPLE_PROPERTY:
partBuilder.append("?.equals(").append("[").append(parameterIndex++).append("]) == false");
break;
case IS_NULL:
partBuilder.append(" == null");
break;
@@ -129,6 +133,9 @@ public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExp
case LIKE:
partBuilder.append("?.contains(").append("[").append(parameterIndex++).append("])");
break;
case NOT_LIKE:
partBuilder.append("?.contains(").append("[").append(parameterIndex++).append("]) == false");
break;
case STARTING_WITH:
partBuilder.append("?.startsWith(").append("[").append(parameterIndex++).append("])");
break;
@@ -175,9 +182,16 @@ public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExp
partBuilder.append(")");
break;
case NOT_IN:
partBuilder.append("[").append(parameterIndex++).append("].contains(");
partBuilder.append("#it?.");
partBuilder.append(part.getProperty().toDotPath().replace(".", "?."));
partBuilder.append(") == false");
break;
case CONTAINING:
case NOT_CONTAINING:
case NEGATING_SIMPLE_PROPERTY:
case EXISTS:
default:
throw new InvalidDataAccessApiUsageException(
@@ -206,6 +220,6 @@ public class SpelQueryCreator extends AbstractQueryCreator<KeyValueQuery<SpelExp
}
private static boolean requiresInverseLookup(Part part) {
return part.getType() == Type.IN;
return part.getType() == Type.IN || part.getType() == Type.NOT_IN;
}
}

View File

@@ -43,6 +43,7 @@ import org.springframework.util.ObjectUtils;
/**
* @author Christoph Strobl
* @author Tom Van Wemmel
*/
@ExtendWith(MockitoExtension.class)
public abstract class AbstractQueryCreatorTestBase<QUERY_CREATOR extends AbstractQueryCreator<KeyValueQuery<CRITERIA>, ?>, CRITERIA> {
@@ -70,6 +71,17 @@ public abstract class AbstractQueryCreatorTestBase<QUERY_CREATOR extends Abstrac
assertThat(evaluate("findByFirstname", BRAN.firstname).against(RICKON)).isFalse();
}
@Test
// GH-603
void notEqualsReturnsTrueWhenMatching() {
assertThat(evaluate("findByFirstnameNot", BRAN.firstname).against(RICKON)).isTrue();
}
@Test // GH-603
void notEqualsReturnsFalseWhenNotMatching() {
assertThat(evaluate("findByFirstnameNot", BRAN.firstname).against(BRAN)).isFalse();
}
@Test // DATACMNS-525
void isTrueAssertedProperlyWhenTrue() {
assertThat(evaluate("findBySkinChangerIsTrue").against(BRAN)).isTrue();
@@ -130,6 +142,16 @@ public abstract class AbstractQueryCreatorTestBase<QUERY_CREATOR extends Abstrac
assertThat(evaluate("findByFirstnameLike", "ra").against(ROBB)).isFalse();
}
@Test // GH-603
void notLikeReturnsTrueWhenMatching() {
assertThat(evaluate("findByFirstnameNotLike", "ra").against(ROBB)).isTrue();
}
@Test // GH-603
void notLikeReturnsFalseWhenNotMatching() {
assertThat(evaluate("findByFirstnameNotLike", "ob").against(ROBB)).isFalse();
}
@Test // DATACMNS-525
void endsWithReturnsTrueWhenMatching() {
assertThat(evaluate("findByFirstnameEndingWith", "bb").against(ROBB)).isTrue();
@@ -310,6 +332,53 @@ public abstract class AbstractQueryCreatorTestBase<QUERY_CREATOR extends Abstrac
.isTrue();
}
@Test // GH-603
void notInReturnsMatchCorrectly() {
ArrayList<String> list = new ArrayList<>();
list.add(ROBB.firstname);
assertThat(evaluate("findByFirstnameNotIn", list).against(JON)).isTrue();
}
@Test // GH-603
void notInNotMatchingReturnsCorrectly() {
ArrayList<String> list = new ArrayList<>();
list.add(ROBB.firstname);
assertThat(evaluate("findByFirstnameNotIn", list).against(ROBB)).isFalse();
}
@Test // GH-603
void notInWithNullCompareValuesCorrectly() {
ArrayList<String> list = new ArrayList<>();
list.add(null);
assertThat(evaluate("findByFirstnameNotIn", list).against(JON)).isTrue();
}
@Test // GH-603
void notInWithNullSourceValuesMatchesCorrectly() {
ArrayList<String> list = new ArrayList<>();
list.add(ROBB.firstname);
assertThat(evaluate("findByFirstnameNotIn", list).against(new PredicateQueryCreatorUnitTests.Person(null, 10)))
.isTrue();
}
@Test // GH-603
void notInMatchesNullValuesCorrectly() {
ArrayList<String> list = new ArrayList<>();
list.add(null);
assertThat(evaluate("findByFirstnameNotIn", list).against(new PredicateQueryCreatorUnitTests.Person(null, 10)))
.isFalse();
}
@Test // DATAKV-185
void noDerivedQueryArgumentsMatchesAlways() {
@@ -363,6 +432,9 @@ public abstract class AbstractQueryCreatorTestBase<QUERY_CREATOR extends Abstrac
// Type.SIMPLE_PROPERTY
Person findByFirstname(String firstname);
// Type.NEGATING_SIMPLE_PROPERTY
Person findByFirstnameNot(String firstname);
// Type.TRUE
Person findBySkinChangerIsTrue();
@@ -404,6 +476,9 @@ public abstract class AbstractQueryCreatorTestBase<QUERY_CREATOR extends Abstrac
// Type.LIKE
Person findByFirstnameLike(String firstname);
// Type.NOT_LIKE
Person findByFirstnameNotLike(String firstname);
// Type.ENDING_WITH
Person findByFirstnameEndingWith(String firstname);
@@ -417,6 +492,9 @@ public abstract class AbstractQueryCreatorTestBase<QUERY_CREATOR extends Abstrac
// Type.IN
Person findByFirstnameIn(ArrayList<String> in);
// Type.NOT_IN
Person findByFirstnameNotIn(ArrayList<String> in);
}
public interface Evaluation {