diff --git a/pom.xml b/pom.xml index 6a61f22b08..20e756e360 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 5.1.0-SNAPSHOT + 5.1.x-GH-5123-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index cab02fe276..1432b8a497 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -15,7 +15,7 @@ org.springframework.data spring-data-mongodb-parent - 5.1.0-SNAPSHOT + 5.1.x-GH-5123-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 0a758111af..24f2989705 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 5.1.0-SNAPSHOT + 5.1.x-GH-5123-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java index 5d4e45fa67..350afdef70 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java @@ -111,6 +111,26 @@ static RegexPlaceholder regex(int index, @Nullable String options) { return new RegexPlaceholder(index, options); } + /** + * Create a placeholder that indicates the value should be treated as list. + * + * @param index zero-based index referring to the bindable method parameter. + * @return new instance of {@link Placeholder}. + */ + static Placeholder asList(int index) { + return asList(indexed(index)); + } + + /** + * Create a placeholder that indicates the wrapped placeholder should be treated as list. + * + * @param source the target placeholder + * @return new instance of {@link Placeholder}. + */ + static Placeholder asList(Placeholder source) { + return new AsListPlaceholder(source); + } + /** * A placeholder expression used when rending queries to JSON. * @@ -295,4 +315,17 @@ public String toString() { } } + record AsListPlaceholder(Placeholder placeholder) implements Placeholder { + + @Override + public String toString() { + return getValue(); + } + + @Override + public String getValue() { + return "[" + placeholder.getValue() + "]"; + } + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java index 1ec311898f..b2d8152c0a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java @@ -24,7 +24,6 @@ import org.bson.conversions.Bson; import org.jspecify.annotations.NullUnmarked; import org.jspecify.annotations.Nullable; - import org.springframework.data.core.TypeInformation; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Range; @@ -48,6 +47,7 @@ import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.data.mongodb.core.query.UpdateDefinition; import org.springframework.data.mongodb.repository.VectorSearch; +import org.springframework.data.mongodb.repository.aot.AotPlaceholders.AsListPlaceholder; import org.springframework.data.mongodb.repository.aot.AotPlaceholders.Placeholder; import org.springframework.data.mongodb.repository.aot.AotPlaceholders.RegexPlaceholder; import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor; @@ -132,6 +132,10 @@ protected Criteria createContainingCriteria(Part part, MongoPersistentProperty p return criteria.raw("$regex", param); } + if (param instanceof AsListPlaceholder asListPlaceholder && !property.isCollectionLike()) { + return super.createContainingCriteria(part, property, criteria, asListPlaceholder.placeholder()); + } + return super.createContainingCriteria(part, property, criteria, param); } } @@ -226,7 +230,24 @@ public PlaceholderParameterAccessor(PartTree partTree, QueryMethod queryMethod) partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.ALWAYS) || partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.WHEN_POSSIBLE) ? "i" : null)); - } else { + } else if (partForIndex != null && (partForIndex.getType().equals(Type.IN) + || partForIndex.getType().equals(Type.NOT_IN) || partForIndex.getType().equals(Type.CONTAINING) + || partForIndex.getType().equals(Type.NOT_CONTAINING))) { + + if (partForIndex.getProperty().isCollection() + && !TypeInformation.of(parameter.getType()).isCollectionLike()) { + if (partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.ALWAYS)) { + placeholders.add(parameter.getIndex(), + AotPlaceholders.asList(AotPlaceholders.regex(parameter.getIndex(), "i"))); + } else { + placeholders.add(parameter.getIndex(), AotPlaceholders.asList(parameter.getIndex())); + } + } else { + placeholders.add(parameter.getIndex(), AotPlaceholders.indexed(parameter.getIndex())); + } + } + + else { placeholders.add(parameter.getIndex(), AotPlaceholders.indexed(parameter.getIndex())); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java index 4bc953ae55..043daa16d1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java @@ -22,12 +22,12 @@ import org.bson.Document; import org.jspecify.annotations.Nullable; - import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Field; import org.springframework.data.mongodb.core.query.Meta; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.repository.aot.AotPlaceholders.AsListPlaceholder; import org.springframework.data.mongodb.repository.aot.AotPlaceholders.RegexPlaceholder; import org.springframework.util.StringUtils; @@ -83,7 +83,7 @@ boolean isRegexPlaceholderAt(int index) { return false; } - return this.placeholders.get(index) instanceof RegexPlaceholder; + return obtainAndPotentiallyUnwrapPlaceholder(index) instanceof RegexPlaceholder; } @Nullable @@ -92,7 +92,21 @@ String getRegexOptions(int index) { return null; } - return this.placeholders.get(index) instanceof RegexPlaceholder rgp ? rgp.regexOptions() : null; + Object placeholderValue = obtainAndPotentiallyUnwrapPlaceholder(index); + return placeholderValue instanceof RegexPlaceholder rgp ? rgp.regexOptions() : null; + } + + @Nullable Object obtainAndPotentiallyUnwrapPlaceholder(int index) { + + if (this.placeholders.isEmpty()) { + return null; + } + + Object placeholerValue = this.placeholders.get(index); + if (placeholerValue instanceof AsListPlaceholder asListPlaceholder) { + placeholerValue = asListPlaceholder.placeholder(); + } + return placeholerValue; } @Override diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java index f6c9d07579..57414707dd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java @@ -195,9 +195,9 @@ CodeBlock queryParametersCodeBlock() { String regexOptions = source.getQuery().getRegexOptions(i); if (StringUtils.hasText(regexOptions)) { - formatted.add(CodeBlock.of("toRegex($L)", parameterName)); - } else { formatted.add(CodeBlock.of("toRegex($L, $S)", parameterName, regexOptions)); + } else { + formatted.add(CodeBlock.of("toRegex($L)", parameterName)); } } else { formatted.add(CodeBlock.of("$L", parameterName)); 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 e7a73dafb1..ab0f3966ac 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 @@ -1341,6 +1341,20 @@ void findBySkillsContains() { assertThat(result).hasSize(1).contains(carter); } + @Test // GH-5123 + void findBySkillsContainsSingleElement() { + + List result = repository.findBySkillsContains("Drums"); + assertThat(result).hasSize(1).contains(carter); + } + + @Test // GH-5123 + void findBySkillsContainsSingleElementWithIgnoreCase() { + + List result = repository.findBySkillsContainsIgnoreCase("drums"); + assertThat(result).hasSize(1).contains(carter); + } + @Test // DATAMONGO-1425 void findBySkillsNotContains() { @@ -1349,6 +1363,14 @@ void findBySkillsNotContains() { assertThat(result).doesNotContain(carter); } + @Test // GH-5123 + void findBySkillsNotContainsSingleElement() { + + List result = repository.findBySkillsNotContains("Drums"); + assertThat(result).hasSize((int) (repository.count() - 1)); + assertThat(result).doesNotContain(carter); + } + @Test // DATAMONGO-1424 void findsPersonsByFirstnameNotLike() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java index ace340e9d7..15983f9cf8 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java @@ -117,8 +117,11 @@ public interface PersonRepository extends MongoRepository, Query List findByFirstnameLikeOrderByLastnameAsc(Pattern firstname, Sort sort); List findBySkillsContains(List skills); + List findBySkillsContains(String skill); + List findBySkillsContainsIgnoreCase(String skill); List findBySkillsNotContains(List skills); + List findBySkillsNotContains(String skill); @Query("{'age' : { '$lt' : ?0 } }") List findByAgeLessThan(int age, Sort sort);