Skip to content

Commit fe41202

Browse files
Thomas Darimontodrotbohm
Thomas Darimont
authored andcommitted
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.
1 parent 78235b4 commit fe41202

File tree

4 files changed

+288
-15
lines changed

4 files changed

+288
-15
lines changed

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

+102-9
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;
@@ -43,6 +45,7 @@
4345
* Custom query creator to create Mongo criterias.
4446
*
4547
* @author Oliver Gierke
48+
* @author Thomas Darimont
4649
*/
4750
class MongoQueryCreator extends AbstractQueryCreator<Query, Criteria> {
4851

@@ -99,7 +102,7 @@ protected Criteria create(Part part, Iterator<Object> iterator) {
99102

100103
PersistentPropertyPath<MongoPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
101104
MongoPersistentProperty property = path.getLeafProperty();
102-
Criteria criteria = from(part.getType(), property,
105+
Criteria criteria = from(part, property,
103106
where(path.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)),
104107
(PotentiallyConvertingIterator) iterator);
105108

@@ -120,7 +123,7 @@ protected Criteria and(Part part, Criteria base, Iterator<Object> iterator) {
120123
PersistentPropertyPath<MongoPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
121124
MongoPersistentProperty property = path.getLeafProperty();
122125

123-
return from(part.getType(), property,
126+
return from(part, property,
124127
base.and(path.toDotPath(MongoPersistentProperty.PropertyToFieldNameConverter.INSTANCE)),
125128
(PotentiallyConvertingIterator) iterator);
126129
}
@@ -165,9 +168,11 @@ protected Query complete(Criteria criteria, Sort sort) {
165168
* @param parameters
166169
* @return
167170
*/
168-
private Criteria from(Type type, MongoPersistentProperty property, Criteria criteria,
171+
private Criteria from(Part part, MongoPersistentProperty property, Criteria criteria,
169172
PotentiallyConvertingIterator parameters) {
170173

174+
Type type = part.getType();
175+
171176
switch (type) {
172177
case AFTER:
173178
case GREATER_THAN:
@@ -193,8 +198,7 @@ private Criteria from(Type type, MongoPersistentProperty property, Criteria crit
193198
case STARTING_WITH:
194199
case ENDING_WITH:
195200
case CONTAINING:
196-
String value = parameters.next().toString();
197-
return criteria.regex(toLikeRegex(value, type));
201+
return addAppropriateLikeRegexTo(criteria, part, parameters.next().toString());
198202
case REGEX:
199203
return criteria.regex(parameters.next().toString());
200204
case EXISTS:
@@ -220,19 +224,103 @@ private Criteria from(Type type, MongoPersistentProperty property, Criteria crit
220224
criteria.maxDistance(distance.getNormalizedValue());
221225
}
222226
return criteria;
223-
224227
case WITHIN:
228+
225229
Object parameter = parameters.next();
226230
return criteria.within((Shape) parameter);
227231
case SIMPLE_PROPERTY:
228-
return criteria.is(parameters.nextConverted(property));
232+
233+
return isSimpleComparisionPossible(part) ? criteria.is(parameters.nextConverted(property))
234+
: createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, false);
235+
229236
case NEGATING_SIMPLE_PROPERTY:
230-
return criteria.ne(parameters.nextConverted(property));
237+
238+
return isSimpleComparisionPossible(part) ? criteria.ne(parameters.nextConverted(property))
239+
: createLikeRegexCriteriaOrThrow(part, property, criteria, parameters, true);
231240
default:
232241
throw new IllegalArgumentException("Unsupported keyword!");
233242
}
234243
}
235244

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

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

270360
switch (type) {
271361
case STARTING_WITH:
@@ -277,6 +367,9 @@ private String toLikeRegex(String source, Type type) {
277367
case CONTAINING:
278368
source = "*" + source + "*";
279369
break;
370+
case SIMPLE_PROPERTY:
371+
case NEGATING_SIMPLE_PROPERTY:
372+
source = "^" + source + "$";
280373
default:
281374
}
282375

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

+58
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* Base class for tests for {@link PersonRepository}.
5050
*
5151
* @author Oliver Gierke
52+
* @author Thomas Darimont
5253
*/
5354
@RunWith(SpringJUnit4ClassRunner.class)
5455
public abstract class AbstractPersonRepositoryIntegrationTests {
@@ -680,4 +681,61 @@ public void executesGeoPageQueryForWithPageRequestForJustOneElementEmptyPage() {
680681
assertThat(results.isLastPage(), is(true));
681682
assertThat(results.getAverageDistance().getMetric(), is((Metric) Metrics.KILOMETERS));
682683
}
684+
685+
/**
686+
* @see DATAMONGO-770
687+
*/
688+
@Test
689+
public void findByFirstNameIgnoreCase() {
690+
691+
List<Person> result = repository.findByFirstnameIgnoreCase("dave");
692+
693+
assertThat(result.size(), is(1));
694+
assertThat(result.get(0), is(dave));
695+
}
696+
697+
/**
698+
* @see DATAMONGO-770
699+
*/
700+
@Test
701+
public void findByFirstnameNotIgnoreCase() {
702+
703+
List<Person> result = repository.findByFirstnameNotIgnoreCase("dave");
704+
705+
assertThat(result.size(), is(6));
706+
assertThat(result, not(hasItem(dave)));
707+
}
708+
709+
/**
710+
* @see DATAMONGO-770
711+
*/
712+
@Test
713+
public void findByFirstnameStartingWithIgnoreCase() {
714+
715+
List<Person> result = repository.findByFirstnameStartingWithIgnoreCase("da");
716+
assertThat(result.size(), is(1));
717+
assertThat(result.get(0), is(dave));
718+
}
719+
720+
/**
721+
* @see DATAMONGO-770
722+
*/
723+
@Test
724+
public void findByFirstnameEndingWithIgnoreCase() {
725+
726+
List<Person> result = repository.findByFirstnameEndingWithIgnoreCase("VE");
727+
assertThat(result.size(), is(1));
728+
assertThat(result.get(0), is(dave));
729+
}
730+
731+
/**
732+
* @see DATAMONGO-770
733+
*/
734+
@Test
735+
public void findByFirstnameContainingIgnoreCase() {
736+
737+
List<Person> result = repository.findByFirstnameContainingIgnoreCase("AV");
738+
assertThat(result.size(), is(1));
739+
assertThat(result.get(0), is(dave));
740+
}
683741
}

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

+27
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
* Sample repository managing {@link Person} entities.
3737
*
3838
* @author Oliver Gierke
39+
* @author Thomas Darimont
3940
*/
4041
public interface PersonRepository extends MongoRepository<Person, String>, QueryDslPredicateExecutor<Person> {
4142

@@ -218,4 +219,30 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
218219
*/
219220
@Query(value = "{ 'lastname' : ?0 }", count = true)
220221
long someCountQuery(String lastname);
222+
223+
/**
224+
* @see DATAMONGO-770
225+
*/
226+
List<Person> findByFirstnameIgnoreCase(String firstName);
227+
228+
/**
229+
* @see DATAMONGO-770
230+
*/
231+
List<Person> findByFirstnameNotIgnoreCase(String firstName);
232+
233+
/**
234+
* @see DATAMONGO-770
235+
*/
236+
List<Person> findByFirstnameStartingWithIgnoreCase(String firstName);
237+
238+
/**
239+
* @see DATAMONGO-770
240+
*/
241+
List<Person> findByFirstnameEndingWithIgnoreCase(String firstName);
242+
243+
/**
244+
* @see DATAMONGO-770
245+
*/
246+
List<Person> findByFirstnameContainingIgnoreCase(String firstName);
247+
221248
}

0 commit comments

Comments
 (0)