From c48b2c6b9bf6d010e2c4e1453c87925ac06a2bc5 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 13 Jun 2023 08:26:43 +0200 Subject: [PATCH] Use exact matching for IN clause with ignore case. Prior to this change the generated pattern would have matched more entries than it should have. The behavior is now aligned to its counterpart not using the IgnoreCase flag. Closes #4404 Original pull request: #4412 --- .../mongodb/core/query/MongoRegexCreator.java | 2 ++ .../repository/query/MongoQueryCreator.java | 16 ++++++++++++++-- ...AbstractPersonRepositoryIntegrationTests.java | 9 ++++++++- .../query/MongoQueryCreatorUnitTests.java | 12 ++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java index 9a97f1df3..6fb7e503c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/MongoRegexCreator.java @@ -107,7 +107,9 @@ public enum MongoRegexCreator { * @param source * @return * @since 2.2.14 + * @deprecated since 4.0.7 */ + @Deprecated(since = "4.0.7", forRemoval = true) public Object toCaseInsensitiveMatch(Object source) { return source instanceof String ? new BsonRegularExpression(Pattern.quote((String) source), "i") : source; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java index 8a187bd1b..ddfbc33f6 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryCreator.java @@ -25,7 +25,7 @@ import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - +import org.bson.BsonRegularExpression; import org.springframework.data.domain.Range; import org.springframework.data.domain.Range.Bound; import org.springframework.data.domain.Sort; @@ -390,7 +390,18 @@ class MongoQueryCreator extends AbstractQueryCreator { Streamable streamable = asStreamable(iterator.next()); if (!isSimpleComparisionPossible(part)) { - streamable = streamable.map(MongoRegexCreator.INSTANCE::toCaseInsensitiveMatch); + + MatchMode matchMode = toMatchMode(part.getType()); + String regexOptions = toRegexOptions(part); + + streamable = streamable.map(it -> { + if (it instanceof String value) { + + return new BsonRegularExpression(MongoRegexCreator.INSTANCE.toRegularExpression(value, matchMode), + regexOptions); + } + return it; + }); } return streamable.toList(); @@ -482,6 +493,7 @@ class MongoQueryCreator extends AbstractQueryCreator { return MatchMode.REGEX; case NEGATING_SIMPLE_PROPERTY: case SIMPLE_PROPERTY: + case IN: return MatchMode.EXACT; default: return MatchMode.DEFAULT; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java index 97dec780d..f1d5d09f4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java @@ -1451,9 +1451,16 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie assertThat(result.get(0).getId().equals(bart.getId())); } - @Test // GH-3395 + @Test // GH-3395, GH-4404 void caseInSensitiveInClause() { + assertThat(repository.findByLastnameIgnoreCaseIn("bEAuFoRd", "maTTheWs")).hasSize(3); + + repository.save(new Person("the-first", "The First")); + repository.save(new Person("the-first-one", "The First One")); + repository.save(new Person("the-second", "The Second")); + + assertThat(repository.findByLastnameIgnoreCaseIn("tHE fIRsT")).hasSize(1); } @Test // GH-3395 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java index f81ff21fe..158f69db7 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryCreatorUnitTests.java @@ -25,6 +25,7 @@ import java.lang.reflect.Method; import java.util.List; import java.util.regex.Pattern; +import org.bson.BsonRegularExpression; import org.bson.Document; import org.bson.types.ObjectId; import org.junit.jupiter.api.BeforeEach; @@ -273,6 +274,17 @@ class MongoQueryCreatorUnitTests { assertThat(query).isEqualTo(query(where("firstName").regex("^dave$", "i"))); } + @Test // GH-4404 + void createsQueryWithFindByInClauseHavingIgnoreCaseCorrectly() { + + PartTree tree = new PartTree("findAllByFirstNameInIgnoreCase", Person.class); + MongoQueryCreator creator = new MongoQueryCreator(tree, getAccessor(converter, List.of("da've", "carter")), context); + + Query query = creator.createQuery(); + assertThat(query).isEqualTo(query(where("firstName") + .in(List.of(new BsonRegularExpression("^\\Qda've\\E$", "i"), new BsonRegularExpression("^carter$", "i"))))); + } + @Test // DATAMONGO-770 void createsQueryWithFindByNotIgnoreCaseCorrectly() {