From f67a66bf25cfbe5183d893edab20dad4ade86ef9 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 13 Apr 2017 15:39:37 +0200 Subject: [PATCH] DATAKV-169 - Add support for IN keyword in derived queries. We now support `In` keyword in derived finder methods (eg. `findByFirstnameIn(Collection firstnames)`) when using SpEL based queries. Original pull request: #23. --- .../repository/query/SpelQueryCreator.java | 22 +++++++-- .../query/SpelQueryCreatorUnitTests.java | 49 +++++++++++++++++++ .../data/map/AbstractRepositoryUnitTests.java | 42 +++++++++++++++- 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java b/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java index 90108a8..a5e27f4 100644 --- a/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java +++ b/src/main/java/org/springframework/data/keyvalue/repository/query/SpelQueryCreator.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; +import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.repository.query.parser.PartTree.OrPart; import org.springframework.expression.spel.standard.SpelExpression; @@ -107,8 +108,12 @@ public class SpelQueryCreator extends AbstractQueryCreator partIter = orPart.iterator(); partIter.hasNext();) { Part part = partIter.next(); - partBuilder.append("#it?."); - partBuilder.append(part.getProperty().toDotPath().replace(".", "?.")); + + if(!requiresInverseLookup(part)) { + + partBuilder.append("#it?."); + partBuilder.append(part.getProperty().toDotPath().replace(".", "?.")); + } // TODO: check if we can have caseinsensitive search if (!part.shouldIgnoreCase().equals(IgnoreCaseType.NEVER)) { @@ -173,6 +178,13 @@ public class SpelQueryCreator extends AbstractQueryCreator list = new ArrayList<>(); + list.add(ROBB.firstname); + + assertThat(evaluate("findByFirstnameIn", list).against(ROBB), is(true)); + } + + @Test // DATAKV-169 + public void inNotMatchingReturnsCorrectly() throws Exception { + + ArrayList list = new ArrayList<>(); + list.add(ROBB.firstname); + + assertThat(evaluate("findByFirstnameIn", list).against(JON), is(false)); + } + + @Test // DATAKV-169 + public void inWithNullCompareValuesCorrectly() throws Exception { + + ArrayList list = new ArrayList<>(); + list.add(null); + + assertThat(evaluate("findByFirstnameIn", list).against(JON), is(false)); + } + + @Test // DATAKV-169 + public void inWithNullSourceValuesMatchesCorrectly() throws Exception { + + ArrayList list = new ArrayList<>(); + list.add(ROBB.firstname); + + assertThat(evaluate("findByFirstnameIn", list).against(new Person(null, 10)), is(false)); + } + + @Test // DATAKV-169 + public void inMatchesNullValuesCorrectly() throws Exception { + + ArrayList list = new ArrayList<>(); + list.add(null); + + assertThat(evaluate("findByFirstnameIn", list).against(new Person(null, 10)), is(true)); + } + private Evaluation evaluate(String methodName, Object... args) throws Exception { return new Evaluation((SpelExpression) createQueryForMethodWithArgs(methodName, args).getCritieria()); } @@ -344,6 +390,9 @@ public class SpelQueryCreatorUnitTests { // Type.REGEX Person findByLastnameMatches(String lastname); + // Type.IN + Person findByFirstnameIn(ArrayList in); + } static class Evaluation { diff --git a/src/test/java/org/springframework/data/map/AbstractRepositoryUnitTests.java b/src/test/java/org/springframework/data/map/AbstractRepositoryUnitTests.java index 1d88dcb..d2200ea 100644 --- a/src/test/java/org/springframework/data/map/AbstractRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/map/AbstractRepositoryUnitTests.java @@ -168,13 +168,51 @@ public abstract class AbstractRepositoryUnitTests result = repository.findByFirstnameIn(Arrays.asList(CERSEI.getFirstname(), JAIME.getFirstname())); + + assertThat(result, hasSize(2)); + assertThat(result, is(containsInAnyOrder(CERSEI, JAIME))); + } + + @Test // DATAKV-169 + public void findsByValueInCollectionCorrectlyWhenTargetPathContainsNullValue() { + + repository.save(LENNISTERS); + repository.save(new Person(null, 10)); + + List result = repository.findByFirstnameIn(Arrays.asList(CERSEI.getFirstname(), JAIME.getFirstname())); + + assertThat(result, hasSize(2)); + assertThat(result, is(containsInAnyOrder(CERSEI, JAIME))); + } + + @Test // DATAKV-169 + public void findsByValueInCollectionCorrectlyWhenTargetPathAndCollectionContainNullValue() { + + repository.save(LENNISTERS); + + Person personWithNullAsFirstname = new Person(null, 10); + repository.save(personWithNullAsFirstname); + + List result = repository + .findByFirstnameIn(Arrays.asList(CERSEI.getFirstname(), JAIME.getFirstname(), null)); + + assertThat(result, hasSize(3)); + assertThat(result, is(containsInAnyOrder(CERSEI, JAIME, personWithNullAsFirstname))); + } + protected KeyValueRepositoryFactory createKeyValueRepositoryFactory(KeyValueOperations operations) { return new KeyValueRepositoryFactory(operations); } protected abstract T getRepository(KeyValueRepositoryFactory factory); - public static interface PersonRepository extends CrudRepository, KeyValueRepository { + public interface PersonRepository extends CrudRepository, KeyValueRepository { List findByAge(int age); @@ -193,6 +231,8 @@ public abstract class AbstractRepositoryUnitTests findByAgeGreaterThan(int age, Sort sort); List findByAgeGreaterThan(int age, Sort sort, Class projectionType); + + List findByFirstnameIn(List firstname); } interface PersonSummary {