Browse Source

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.
pull/79/head
Thomas Darimont 12 years ago committed by Oliver Gierke
parent
commit
fe41202f96
  1. 111
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java
  2. 58
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  3. 27
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  4. 107
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java

111
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java

@ -17,6 +17,7 @@ package org.springframework.data.mongodb.repository.query; @@ -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; @@ -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; @@ -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> { @@ -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> { @@ -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> { @@ -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> { @@ -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> { @@ -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> { @@ -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> { @@ -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:
}

58
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -49,6 +49,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -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 { @@ -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));
}
}

27
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -36,6 +36,7 @@ import org.springframework.data.querydsl.QueryDslPredicateExecutor; @@ -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 @@ -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);
}

107
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java

@ -27,7 +27,9 @@ import java.lang.reflect.Method; @@ -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; @@ -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 { @@ -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 { @@ -317,10 +414,8 @@ public class MongoQueryCreatorUnitTests {
class User {
@Field("foo")
String username;
@Field("foo") String username;
@DBRef
User creator;
@DBRef User creator;
}
}

Loading…
Cancel
Save