Browse Source

DATAMONGO-1232 - IngoreCase in criteria now escapes query.

We now quote the original criteria before actually wrapping it inside of an regular expression for case insensitive search. This happens not only to case insensitive is, startsWith, endsWith criteria but also to those using like. In that case we quote the part between leading and trailing wildcard if required.

Original pull request: #301.
pull/302/merge
Christoph Strobl 11 years ago committed by Oliver Gierke
parent
commit
5c0707d221
  1. 47
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java
  2. 96
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java

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

@ -20,6 +20,7 @@ import static org.springframework.data.mongodb.core.query.Criteria.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.regex.Pattern;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -45,6 +46,7 @@ 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.Part.Type;
import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/** /**
* Custom query creator to create Mongo criterias. * Custom query creator to create Mongo criterias.
@ -56,6 +58,7 @@ import org.springframework.util.Assert;
class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> { class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
private static final Logger LOG = LoggerFactory.getLogger(MongoQueryCreator.class); private static final Logger LOG = LoggerFactory.getLogger(MongoQueryCreator.class);
private static final Pattern PUNCTATION_PATTERN = Pattern.compile("\\p{Punct}");
private final MongoParameterAccessor accessor; private final MongoParameterAccessor accessor;
private final boolean isGeoNearQuery; private final boolean isGeoNearQuery;
@ -389,25 +392,59 @@ class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
private String toLikeRegex(String source, Part part) { private String toLikeRegex(String source, Part part) {
Type type = part.getType(); Type type = part.getType();
String regex = prepareAndEscapeStringBeforeApplyingLikeRegex(source, part);
switch (type) { switch (type) {
case STARTING_WITH: case STARTING_WITH:
source = "^" + source; regex = "^" + regex;
break; break;
case ENDING_WITH: case ENDING_WITH:
source = source + "$"; regex = regex + "$";
break; break;
case CONTAINING: case CONTAINING:
case NOT_CONTAINING: case NOT_CONTAINING:
source = "*" + source + "*"; regex = ".*" + regex + ".*";
break; break;
case SIMPLE_PROPERTY: case SIMPLE_PROPERTY:
case NEGATING_SIMPLE_PROPERTY: case NEGATING_SIMPLE_PROPERTY:
source = "^" + source + "$"; regex = "^" + regex + "$";
default: default:
} }
return source.replaceAll("\\*", ".*"); return regex;
}
private String prepareAndEscapeStringBeforeApplyingLikeRegex(String source, Part qpart) {
if (!ObjectUtils.nullSafeEquals(Type.LIKE, qpart.getType())) {
return PUNCTATION_PATTERN.matcher(source).find() ? Pattern.quote(source) : source;
}
if (source.equals("*")) {
return ".*";
}
StringBuilder sb = new StringBuilder();
boolean leadingWildcard = source.startsWith("*");
boolean trailingWildcard = source.endsWith("*");
String valueToUse = source.substring(leadingWildcard ? 1 : 0,
trailingWildcard ? source.length() - 1 : source.length());
if (PUNCTATION_PATTERN.matcher(valueToUse).find()) {
valueToUse = Pattern.quote(valueToUse);
}
if (leadingWildcard) {
sb.append(".*");
}
sb.append(valueToUse);
if (trailingWildcard) {
sb.append(".*");
}
return sb.toString();
} }
private boolean isSpherical(MongoPersistentProperty property) { private boolean isSpherical(MongoPersistentProperty property) {

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

@ -565,6 +565,102 @@ public class MongoQueryCreatorUnitTests {
assertThat(new MongoQueryCreator(tree, accessor, context).createQuery(), is(notNullValue())); assertThat(new MongoQueryCreator(tree, accessor, context).createQuery(), is(notNullValue()));
} }
/**
* @see DATAMONGO-1232
*/
@Test
public void ignoreCaseShouldEscapeSource() {
PartTree tree = new PartTree("findByUsernameIgnoreCase", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "con.flux+");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex("^\\Qcon.flux+\\E$", "i"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void ignoreCaseShouldEscapeSourceWhenUsedForStartingWith() {
PartTree tree = new PartTree("findByUsernameStartingWithIgnoreCase", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "dawns.light+");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex("^\\Qdawns.light+\\E", "i"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void ignoreCaseShouldEscapeSourceWhenUsedForEndingWith() {
PartTree tree = new PartTree("findByUsernameEndingWithIgnoreCase", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "new.ton+");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex("\\Qnew.ton+\\E$", "i"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void likeShouldEscapeSourceWhenUsedWithLeadingAndTrailingWildcard() {
PartTree tree = new PartTree("findByUsernameLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "*fire.fight+*");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex(".*\\Qfire.fight+\\E.*"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void likeShouldEscapeSourceWhenUsedWithLeadingWildcard() {
PartTree tree = new PartTree("findByUsernameLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "*steel.heart+");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex(".*\\Qsteel.heart+\\E"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void likeShouldEscapeSourceWhenUsedWithTrailingWildcard() {
PartTree tree = new PartTree("findByUsernameLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "cala.mity+*");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex("\\Qcala.mity+\\E.*"))));
}
/**
* @see DATAMONGO-1232
*/
@Test
public void likeShouldBeTreatedCorrectlyWhenUsedWithWildcardOnly() {
PartTree tree = new PartTree("findByUsernameLike", User.class);
ConvertingParameterAccessor accessor = getAccessor(converter, "*");
Query query = new MongoQueryCreator(tree, accessor, context).createQuery();
assertThat(query, is(query(where("username").regex(".*"))));
}
interface PersonRepository extends Repository<Person, Long> { interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname); List<Person> findByLocationNearAndFirstname(Point location, Distance maxDistance, String firstname);

Loading…
Cancel
Save