Skip to content

Commit e8a303b

Browse files
author
Thomas Darimont
committed
DATAMONGO-770 - Add support for case-insensitive queries.
Case-insensitive queries are now rendered as regex expressions with "i" option. The regex options to use are now derived from the query Part.
1 parent 78235b4 commit e8a303b

File tree

4 files changed

+284
-15
lines changed

4 files changed

+284
-15
lines changed

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

Lines changed: 101 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

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

20+
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.Iterator;
2223

@@ -35,6 +36,7 @@
3536
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor.PotentiallyConvertingIterator;
3637
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
3738
import org.springframework.data.repository.query.parser.Part;
39+
import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
3840
import org.springframework.data.repository.query.parser.Part.Type;
3941
import org.springframework.data.repository.query.parser.PartTree;
4042
import org.springframework.util.Assert;
@@ -99,7 +101,7 @@ protected Criteria create(Part part, Iterator<Object> iterator) {
99101

100102
PersistentPropertyPath<MongoPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
101103
MongoPersistentProperty property = path.getLeafProperty();
102-
Criteria criteria = from(part.getType(), property,
104+
Criteria criteria = from(part, property,
103105
where(path.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)),
104106
(PotentiallyConvertingIterator) iterator);
105107

@@ -120,7 +122,7 @@ protected Criteria and(Part part, Criteria base, Iterator<Object> iterator) {
120122
PersistentPropertyPath<MongoPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
121123
MongoPersistentProperty property = path.getLeafProperty();
122124

123-
return from(part.getType(), property,
125+
return from(part, property,
124126
base.and(path.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)),
125127
(PotentiallyConvertingIterator) iterator);
126128
}
@@ -165,9 +167,11 @@ protected Query complete(Criteria criteria, Sort sort) {
165167
* @param parameters
166168
* @return
167169
*/
168-
private Criteria from(Type type, MongoPersistentProperty property, Criteria criteria,
170+
private Criteria from(Part part, MongoPersistentProperty property, Criteria criteria,
169171
PotentiallyConvertingIterator parameters) {
170172

173+
Type type = part.getType();
174+
171175
switch (type) {
172176
case AFTER:
173177
case GREATER_THAN:
@@ -193,8 +197,7 @@ private Criteria from(Type type, MongoPersistentProperty property, Criteria crit
193197
case STARTING_WITH:
194198
case ENDING_WITH:
195199
case CONTAINING:
196-
String value = parameters.next().toString();
197-
return criteria.regex(toLikeRegex(value, type));
200+
return addAppropriateLikeRegexTo(criteria, part, parameters.next().toString());
198201
case REGEX:
199202
return criteria.regex(parameters.next().toString());
200203
case EXISTS:
@@ -220,19 +223,103 @@ private Criteria from(Type type, MongoPersistentProperty property, Criteria crit
220223
criteria.maxDistance(distance.getNormalizedValue());
221224
}
222225
return criteria;
223-
224226
case WITHIN:
227+
225228
Object parameter = parameters.next();
226229
return criteria.within((Shape) parameter);
227230
case SIMPLE_PROPERTY:
228-
return criteria.is(parameters.nextConverted(property));
231+
232+
return isSimpleComparisionPossible(part) ? criteria.is(parameters.nextConverted(property))
233+
: createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, false);
234+
229235
case NEGATING_SIMPLE_PROPERTY:
230-
return criteria.ne(parameters.nextConverted(property));
236+
237+
return isSimpleComparisionPossible(part) ? criteria.ne(parameters.nextConverted(property))
238+
: createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, true);
231239
default:
232240
throw new IllegalArgumentException("Unsupported keyword!");
233241
}
234242
}
235243

244+
private boolean isSimpleComparisionPossible(Part part) {
245+
246+
switch (part.shouldIgnoreCase()) {
247+
case NEVER:
248+
return true;
249+
case WHEN_POSSIBLE:
250+
return part.getProperty().getType() != String.class;
251+
case ALWAYS:
252+
return false;
253+
default:
254+
return true;
255+
}
256+
}
257+
258+
/**
259+
* Creates and extends the given criteria with a like-regex if necessary.
260+
*
261+
* @param part
262+
* @param property
263+
* @param criteria
264+
* @param parameters
265+
* @param shouldNegateExpression
266+
* @return the criteria extended with the like-regex.
267+
*/
268+
private Criteria createLikeRegexCriteriaOrThrow(Part part, MongoPersistentProperty property, Criteria criteria,
269+
PotentiallyConvertingIterator parameters, boolean shouldNegateExpression) {
270+
271+
switch (part.shouldIgnoreCase()) {
272+
273+
case ALWAYS:
274+
if (part.getProperty().getType() != String.class) {
275+
throw new IllegalArgumentException(String.format("part %s must be of type String but was %s",
276+
part.getProperty(), part.getType()));
277+
}
278+
// fall-through
279+
280+
case WHEN_POSSIBLE:
281+
if (shouldNegateExpression) {
282+
criteria = criteria.not();
283+
}
284+
return addAppropriateLikeRegexTo(criteria, part, parameters.nextConverted(property).toString());
285+
286+
case NEVER:
287+
// intentional no-op
288+
}
289+
290+
throw new IllegalArgumentException(String.format("part.shouldCaseIgnore must be one of %s, but was %s",
291+
Arrays.asList(IgnoreCaseType.ALWAYS, IgnoreCaseType.WHEN_POSSIBLE), part.shouldIgnoreCase()));
292+
}
293+
294+
/**
295+
* Creates an appropriate like-regex and appends it to the given criteria.
296+
*
297+
* @param criteria
298+
* @param part
299+
* @param value
300+
* @return the criteria extended with the regex.
301+
*/
302+
private Criteria addAppropriateLikeRegexTo(Criteria criteria, Part part, String value) {
303+
304+
return criteria.regex(toLikeRegex(value, part), toRegexOptions(part));
305+
}
306+
307+
/**
308+
* @param part
309+
* @return the regex options or {@literal null}.
310+
*/
311+
private String toRegexOptions(Part part) {
312+
313+
String regexOptions = null;
314+
switch (part.shouldIgnoreCase()) {
315+
case WHEN_POSSIBLE:
316+
case ALWAYS:
317+
regexOptions = "i";
318+
case NEVER:
319+
}
320+
return regexOptions;
321+
}
322+
236323
/**
237324
* Returns the next element from the given {@link Iterator} expecting it to be of a certain type.
238325
*
@@ -265,7 +352,9 @@ private Object[] nextAsArray(PotentiallyConvertingIterator iterator, MongoPersis
265352
return new Object[] { next };
266353
}
267354

268-
private String toLikeRegex(String source, Type type) {
355+
private String toLikeRegex(String source, Part part) {
356+
357+
Type type = part.getType();
269358

270359
switch (type) {
271360
case STARTING_WITH:
@@ -277,6 +366,9 @@ private String toLikeRegex(String source, Type type) {
277366
case CONTAINING:
278367
source = "*" + source + "*";
279368
break;
369+
case SIMPLE_PROPERTY:
370+
case NEGATING_SIMPLE_PROPERTY:
371+
source = "^" + source + "$";
280372
default:
281373
}
282374

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,4 +680,61 @@ public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() {
680680
assertThat(results.isLastPage(), is(true));
681681
assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS));
682682
}
683+
684+
/**
685+
* @see DATAMONGO-770
686+
*/
687+
@Test
688+
public void findByFirstNameIgnoreCase() {
689+
690+
List<Person> result = repository.findByFirstnameIgnoreCase("dave");
691+
692+
assertThat(result.size(), is(1));
693+
assertThat(result.get(0), is(dave));
694+
}
695+
696+
/**
697+
* @see DATAMONGO-770
698+
*/
699+
@Test
700+
public void findByFirstnameNotIgnoreCase() {
701+
702+
List<Person> result = repository.findByFirstnameNotIgnoreCase("dave");
703+
704+
assertThat(result.size(), is(6));
705+
assertThat(result, not(hasItem(dave)));
706+
}
707+
708+
/**
709+
* @see DATAMONGO-770
710+
*/
711+
@Test
712+
public void findByFirstnameStartingWithIgnoreCase() {
713+
714+
List<Person> result = repository.findByFirstnameStartingWithIgnoreCase("da");
715+
assertThat(result.size(), is(1));
716+
assertThat(result.get(0), is(dave));
717+
}
718+
719+
/**
720+
* @see DATAMONGO-770
721+
*/
722+
@Test
723+
public void findByFirstnameEndingWithIgnoreCase() {
724+
725+
List<Person> result = repository.findByFirstnameEndingWithIgnoreCase("VE");
726+
assertThat(result.size(), is(1));
727+
assertThat(result.get(0), is(dave));
728+
}
729+
730+
/**
731+
* @see DATAMONGO-770
732+
*/
733+
@Test
734+
public void findByFirstnameContainingIgnoreCase() {
735+
736+
List<Person> result = repository.findByFirstnameContainingIgnoreCase("AV");
737+
assertThat(result.size(), is(1));
738+
assertThat(result.get(0), is(dave));
739+
}
683740
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,30 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
218218
*/
219219
@Query(value = "{ 'lastname' : ?0 }", count = true)
220220
long someCountQuery(String lastname);
221+
222+
/**
223+
* @see DATAMONGO-770
224+
*/
225+
List<Person> findByFirstnameIgnoreCase(String firstName);
226+
227+
/**
228+
* @see DATAMONGO-770
229+
*/
230+
List<Person> findByFirstnameNotIgnoreCase(String firstName);
231+
232+
/**
233+
* @see DATAMONGO-770
234+
*/
235+
List<Person> findByFirstnameStartingWithIgnoreCase(String firstName);
236+
237+
/**
238+
* @see DATAMONGO-770
239+
*/
240+
List<Person> findByFirstnameEndingWithIgnoreCase(String firstName);
241+
242+
/**
243+
* @see DATAMONGO-770
244+
*/
245+
List<Person> findByFirstnameContainingIgnoreCase(String firstName);
246+
221247
}

0 commit comments

Comments
 (0)