Skip to content

DATAMONGO-770 - Add support for case-insensitive queries. #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static org.springframework.data.mongodb.core.query.Criteria.*;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

Expand All @@ -35,6 +36,7 @@
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;
Expand Down Expand Up @@ -99,7 +101,7 @@ protected Criteria create(Part part, Iterator<Object> iterator) {

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);

Expand All @@ -120,7 +122,7 @@ protected Criteria and(Part part, Criteria base, Iterator<Object> iterator) {
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);
}
Expand Down Expand Up @@ -165,9 +167,11 @@ protected Query complete(Criteria criteria, Sort sort) {
* @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:
Expand All @@ -193,8 +197,7 @@ private Criteria from(Type type, MongoPersistentProperty property, Criteria crit
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:
Expand All @@ -220,19 +223,103 @@ private Criteria from(Type type, MongoPersistentProperty property, Criteria crit
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.
*
Expand Down Expand Up @@ -265,7 +352,9 @@ private Object[] nextAsArray(PotentiallyConvertingIterator iterator, MongoPersis
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:
Expand All @@ -277,6 +366,9 @@ private String toLikeRegex(String source, Type type) {
case CONTAINING:
source = "*" + source + "*";
break;
case SIMPLE_PROPERTY:
case NEGATING_SIMPLE_PROPERTY:
source = "^" + source + "$";
default:
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,4 +680,61 @@ public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() {
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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,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);

}
Loading