DATAMONGO-770 - Add support for IgnoreCase in query derivation.
We now support IgnoreCase and AllIgnoreCase in predicate expression for derived queries. Original pull request: #78.
This commit is contained in:
committed by
Oliver Gierke
parent
78235b4799
commit
fe41202f96
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
@@ -35,6 +36,7 @@ import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator;
|
||||
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.util.Assert;
|
||||
@@ -43,6 +45,7 @@ import org.springframework.util.Assert;
|
||||
* Custom query creator to create Mongo criterias.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
|
||||
@@ -99,7 +102,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
|
||||
PersistentPropertyPath<MongoPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||
MongoPersistentProperty property = path.getLeafProperty();
|
||||
Criteria criteria = from(part.getType(), property,
|
||||
Criteria criteria = from(part, property,
|
||||
where(path.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)),
|
||||
(PotentiallyConvertingIterator) iterator);
|
||||
|
||||
@@ -120,7 +123,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
PersistentPropertyPath<MongoPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||
MongoPersistentProperty property = path.getLeafProperty();
|
||||
|
||||
return from(part.getType(), property,
|
||||
return from(part, property,
|
||||
base.and(path.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)),
|
||||
(PotentiallyConvertingIterator) iterator);
|
||||
}
|
||||
@@ -165,9 +168,11 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
* @param parameters
|
||||
* @return
|
||||
*/
|
||||
private Criteria from(Type type, MongoPersistentProperty property, Criteria criteria,
|
||||
private Criteria from(Part part, MongoPersistentProperty property, Criteria criteria,
|
||||
PotentiallyConvertingIterator parameters) {
|
||||
|
||||
Type type = part.getType();
|
||||
|
||||
switch (type) {
|
||||
case AFTER:
|
||||
case GREATER_THAN:
|
||||
@@ -193,8 +198,7 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
case STARTING_WITH:
|
||||
case ENDING_WITH:
|
||||
case CONTAINING:
|
||||
String value = parameters.next().toString();
|
||||
return criteria.regex(toLikeRegex(value, type));
|
||||
return addAppropriateLikeRegexTo(criteria, part, parameters.next().toString());
|
||||
case REGEX:
|
||||
return criteria.regex(parameters.next().toString());
|
||||
case EXISTS:
|
||||
@@ -220,19 +224,103 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
criteria.maxDistance(distance.getNormalizedValue());
|
||||
}
|
||||
return criteria;
|
||||
|
||||
case WITHIN:
|
||||
|
||||
Object parameter = parameters.next();
|
||||
return criteria.within((Shape) parameter);
|
||||
case SIMPLE_PROPERTY:
|
||||
return criteria.is(parameters.nextConverted(property));
|
||||
|
||||
return isSimpleComparisionPossible(part) ? criteria.is(parameters.nextConverted(property))
|
||||
: createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, false);
|
||||
|
||||
case NEGATING_SIMPLE_PROPERTY:
|
||||
return criteria.ne(parameters.nextConverted(property));
|
||||
|
||||
return isSimpleComparisionPossible(part) ? criteria.ne(parameters.nextConverted(property))
|
||||
: createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, true);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported keyword!");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSimpleComparisionPossible(Part part) {
|
||||
|
||||
switch (part.shouldIgnoreCase()) {
|
||||
case NEVER:
|
||||
return true;
|
||||
case WHEN_POSSIBLE:
|
||||
return part.getProperty().getType() != String.class;
|
||||
case ALWAYS:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and extends the given criteria with a like-regex if necessary.
|
||||
*
|
||||
* @param part
|
||||
* @param property
|
||||
* @param criteria
|
||||
* @param parameters
|
||||
* @param shouldNegateExpression
|
||||
* @return the criteria extended with the like-regex.
|
||||
*/
|
||||
private Criteria createLikeRegexCriteriaOrThrow(Part part, MongoPersistentProperty property, Criteria criteria,
|
||||
PotentiallyConvertingIterator parameters, boolean shouldNegateExpression) {
|
||||
|
||||
switch (part.shouldIgnoreCase()) {
|
||||
|
||||
case ALWAYS:
|
||||
if (part.getProperty().getType() != String.class) {
|
||||
throw new IllegalArgumentException(String.format("part %s must be of type String but was %s",
|
||||
part.getProperty(), part.getType()));
|
||||
}
|
||||
// fall-through
|
||||
|
||||
case WHEN_POSSIBLE:
|
||||
if (shouldNegateExpression) {
|
||||
criteria = criteria.not();
|
||||
}
|
||||
return addAppropriateLikeRegexTo(criteria, part, parameters.nextConverted(property).toString());
|
||||
|
||||
case NEVER:
|
||||
// intentional no-op
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("part.shouldCaseIgnore must be one of %s, but was %s",
|
||||
Arrays.asList(IgnoreCaseType.ALWAYS, IgnoreCaseType.WHEN_POSSIBLE), part.shouldIgnoreCase()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an appropriate like-regex and appends it to the given criteria.
|
||||
*
|
||||
* @param criteria
|
||||
* @param part
|
||||
* @param value
|
||||
* @return the criteria extended with the regex.
|
||||
*/
|
||||
private Criteria addAppropriateLikeRegexTo(Criteria criteria, Part part, String value) {
|
||||
|
||||
return criteria.regex(toLikeRegex(value, part), toRegexOptions(part));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param part
|
||||
* @return the regex options or {@literal null}.
|
||||
*/
|
||||
private String toRegexOptions(Part part) {
|
||||
|
||||
String regexOptions = null;
|
||||
switch (part.shouldIgnoreCase()) {
|
||||
case WHEN_POSSIBLE:
|
||||
case ALWAYS:
|
||||
regexOptions = "i";
|
||||
case NEVER:
|
||||
}
|
||||
return regexOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next element from the given {@link Iterator} expecting it to be of a certain type.
|
||||
*
|
||||
@@ -265,7 +353,9 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
return new Object[] { next };
|
||||
}
|
||||
|
||||
private String toLikeRegex(String source, Type type) {
|
||||
private String toLikeRegex(String source, Part part) {
|
||||
|
||||
Type type = part.getType();
|
||||
|
||||
switch (type) {
|
||||
case STARTING_WITH:
|
||||
@@ -277,6 +367,9 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
|
||||
case CONTAINING:
|
||||
source = "*" + source + "*";
|
||||
break;
|
||||
case SIMPLE_PROPERTY:
|
||||
case NEGATING_SIMPLE_PROPERTY:
|
||||
source = "^" + source + "$";
|
||||
default:
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
* Base class for tests for {@link PersonRepository}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
@@ -680,4 +681,61 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
||||
assertThat(results.isLastPage(), is(true));
|
||||
assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void findByFirstNameIgnoreCase() {
|
||||
|
||||
List<Person> result = repository.findByFirstnameIgnoreCase("dave");
|
||||
|
||||
assertThat(result.size(), is(1));
|
||||
assertThat(result.get(0), is(dave));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void findByFirstnameNotIgnoreCase() {
|
||||
|
||||
List<Person> result = repository.findByFirstnameNotIgnoreCase("dave");
|
||||
|
||||
assertThat(result.size(), is(6));
|
||||
assertThat(result, not(hasItem(dave)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void findByFirstnameStartingWithIgnoreCase() {
|
||||
|
||||
List<Person> result = repository.findByFirstnameStartingWithIgnoreCase("da");
|
||||
assertThat(result.size(), is(1));
|
||||
assertThat(result.get(0), is(dave));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void findByFirstnameEndingWithIgnoreCase() {
|
||||
|
||||
List<Person> result = repository.findByFirstnameEndingWithIgnoreCase("VE");
|
||||
assertThat(result.size(), is(1));
|
||||
assertThat(result.get(0), is(dave));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void findByFirstnameContainingIgnoreCase() {
|
||||
|
||||
List<Person> result = repository.findByFirstnameContainingIgnoreCase("AV");
|
||||
assertThat(result.size(), is(1));
|
||||
assertThat(result.get(0), is(dave));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.springframework.data.querydsl.QueryDslPredicateExecutor;
|
||||
* Sample repository managing {@link Person} entities.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public interface PersonRepository extends MongoRepository<Person, String>, QueryDslPredicateExecutor<Person> {
|
||||
|
||||
@@ -218,4 +219,30 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
||||
*/
|
||||
@Query(value = "{ 'lastname' : ?0 }", count = true)
|
||||
long someCountQuery(String lastname);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
List<Person> findByFirstnameIgnoreCase(String firstName);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
List<Person> findByFirstnameNotIgnoreCase(String firstName);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
List<Person> findByFirstnameStartingWithIgnoreCase(String firstName);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
List<Person> findByFirstnameEndingWithIgnoreCase(String firstName);
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
List<Person> findByFirstnameContainingIgnoreCase(String firstName);
|
||||
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
@@ -55,17 +57,19 @@ import org.springframework.data.util.TypeInformation;
|
||||
* Unit test for {@link MongoQueryCreator}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MongoQueryCreatorUnitTests {
|
||||
|
||||
Method findByFirstname, findByFirstnameAndFriend, findByFirstnameNotNull;
|
||||
|
||||
@Mock
|
||||
MongoConverter converter;
|
||||
@Mock MongoConverter converter;
|
||||
|
||||
MappingContext<?, MongoPersistentProperty> context;
|
||||
|
||||
@Rule public ExpectedException expection = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
public void setUp() throws SecurityException, NoSuchMethodException {
|
||||
|
||||
@@ -310,6 +314,99 @@ public class MongoQueryCreatorUnitTests {
|
||||
assertThat(query, is(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void createsQueryWithFindByIgnoreCaseCorrectly() {
|
||||
|
||||
PartTree tree = new PartTree("findByfirstNameIgnoreCase", Person.class);
|
||||
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
assertThat(query, is(query(where("firstName").regex("^dave$", "i"))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void createsQueryWithFindByNotIgnoreCaseCorrectly() {
|
||||
|
||||
PartTree tree = new PartTree("findByFirstNameNotIgnoreCase", Person.class);
|
||||
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
assertThat(query.toString(), is(query(where("firstName").not().regex("^dave$", "i")).toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void createsQueryWithFindByStartingWithIgnoreCaseCorrectly() {
|
||||
|
||||
PartTree tree = new PartTree("findByFirstNameStartingWithIgnoreCase", Person.class);
|
||||
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
assertThat(query, is(query(where("firstName").regex("^dave", "i"))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void createsQueryWithFindByEndingWithIgnoreCaseCorrectly() {
|
||||
|
||||
PartTree tree = new PartTree("findByFirstNameEndingWithIgnoreCase", Person.class);
|
||||
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
assertThat(query, is(query(where("firstName").regex("dave$", "i"))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void createsQueryWithFindByContainingIgnoreCaseCorrectly() {
|
||||
|
||||
PartTree tree = new PartTree("findByFirstNameContainingIgnoreCase", Person.class);
|
||||
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave"), context);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
assertThat(query, is(query(where("firstName").regex(".*dave.*", "i"))));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void shouldThrowExceptionForQueryWithFindByIgnoreCaseOnNonStringProperty() {
|
||||
|
||||
expection.expect(IllegalArgumentException.class);
|
||||
expection.expectMessage("must be of type String");
|
||||
|
||||
PartTree tree = new PartTree("findByFirstNameAndAgeIgnoreCase", Person.class);
|
||||
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "foo", 42), context);
|
||||
|
||||
creator.createQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-770
|
||||
*/
|
||||
@Test
|
||||
public void shouldOnlyGenerateLikeExpressionsForStringPropertiesIfAllIgnoreCase() {
|
||||
|
||||
PartTree tree = new PartTree("findByFirstNameAndAgeAllIgnoreCase", Person.class);
|
||||
MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, "dave", 42), context);
|
||||
|
||||
Query query = creator.createQuery();
|
||||
assertThat(query, is(query(where("firstName").regex("^dave$", "i").and("age").is(42))));
|
||||
}
|
||||
|
||||
interface PersonRepository extends Repository<Person, Long> {
|
||||
|
||||
List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname);
|
||||
@@ -317,10 +414,8 @@ public class MongoQueryCreatorUnitTests {
|
||||
|
||||
class User {
|
||||
|
||||
@Field("foo")
|
||||
String username;
|
||||
@Field("foo") String username;
|
||||
|
||||
@DBRef
|
||||
User creator;
|
||||
@DBRef User creator;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user