Skip to content

Commit 6928547

Browse files
Allow single element contains for derived AOT queries.
1 parent e2dd095 commit 6928547

File tree

6 files changed

+100
-7
lines changed

6 files changed

+100
-7
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,26 @@ static RegexPlaceholder regex(int index, @Nullable String options) {
111111
return new RegexPlaceholder(index, options);
112112
}
113113

114+
/**
115+
* Create a placeholder that indicates the value should be treated as list.
116+
*
117+
* @param index zero-based index referring to the bindable method parameter.
118+
* @return new instance of {@link Placeholder}.
119+
*/
120+
static Placeholder asList(int index) {
121+
return asList(indexed(index));
122+
}
123+
124+
/**
125+
* Create a placeholder that indicates the wrapped placeholder should be treated as list.
126+
*
127+
* @param source the target placeholder
128+
* @return new instance of {@link Placeholder}.
129+
*/
130+
static Placeholder asList(Placeholder source) {
131+
return new AsListPlaceholder(source);
132+
}
133+
114134
/**
115135
* A placeholder expression used when rending queries to JSON.
116136
*
@@ -295,4 +315,17 @@ public String toString() {
295315
}
296316
}
297317

318+
record AsListPlaceholder(Placeholder placeholder) implements Placeholder {
319+
320+
@Override
321+
public String toString() {
322+
return getValue();
323+
}
324+
325+
@Override
326+
public String getValue() {
327+
return "[" + placeholder.getValue() + "]";
328+
}
329+
}
330+
298331
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.bson.conversions.Bson;
2525
import org.jspecify.annotations.NullUnmarked;
2626
import org.jspecify.annotations.Nullable;
27-
2827
import org.springframework.data.core.TypeInformation;
2928
import org.springframework.data.domain.Pageable;
3029
import org.springframework.data.domain.Range;
@@ -48,6 +47,7 @@
4847
import org.springframework.data.mongodb.core.query.TextCriteria;
4948
import org.springframework.data.mongodb.core.query.UpdateDefinition;
5049
import org.springframework.data.mongodb.repository.VectorSearch;
50+
import org.springframework.data.mongodb.repository.aot.AotPlaceholders.AsListPlaceholder;
5151
import org.springframework.data.mongodb.repository.aot.AotPlaceholders.Placeholder;
5252
import org.springframework.data.mongodb.repository.aot.AotPlaceholders.RegexPlaceholder;
5353
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor;
@@ -132,6 +132,10 @@ protected Criteria createContainingCriteria(Part part, MongoPersistentProperty p
132132
return criteria.raw("$regex", param);
133133
}
134134

135+
if (param instanceof AsListPlaceholder asListPlaceholder && !property.isCollectionLike()) {
136+
return super.createContainingCriteria(part, property, criteria, asListPlaceholder.placeholder());
137+
}
138+
135139
return super.createContainingCriteria(part, property, criteria, param);
136140
}
137141
}
@@ -226,7 +230,24 @@ public PlaceholderParameterAccessor(PartTree partTree, QueryMethod queryMethod)
226230
partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.ALWAYS)
227231
|| partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.WHEN_POSSIBLE) ? "i"
228232
: null));
229-
} else {
233+
} else if (partForIndex != null && (partForIndex.getType().equals(Type.IN)
234+
|| partForIndex.getType().equals(Type.NOT_IN) || partForIndex.getType().equals(Type.CONTAINING)
235+
|| partForIndex.getType().equals(Type.NOT_CONTAINING))) {
236+
237+
if (partForIndex.getProperty().isCollection()
238+
&& !TypeInformation.of(parameter.getType()).isCollectionLike()) {
239+
if (partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.ALWAYS)) {
240+
placeholders.add(parameter.getIndex(),
241+
AotPlaceholders.asList(AotPlaceholders.regex(parameter.getIndex(), "i")));
242+
} else {
243+
placeholders.add(parameter.getIndex(), AotPlaceholders.asList(parameter.getIndex()));
244+
}
245+
} else {
246+
placeholders.add(parameter.getIndex(), AotPlaceholders.indexed(parameter.getIndex()));
247+
}
248+
}
249+
250+
else {
230251
placeholders.add(parameter.getIndex(), AotPlaceholders.indexed(parameter.getIndex()));
231252
}
232253
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222

2323
import org.bson.Document;
2424
import org.jspecify.annotations.Nullable;
25-
2625
import org.springframework.data.domain.KeysetScrollPosition;
2726
import org.springframework.data.mongodb.core.query.Collation;
2827
import org.springframework.data.mongodb.core.query.Field;
2928
import org.springframework.data.mongodb.core.query.Meta;
3029
import org.springframework.data.mongodb.core.query.Query;
30+
import org.springframework.data.mongodb.repository.aot.AotPlaceholders.AsListPlaceholder;
3131
import org.springframework.data.mongodb.repository.aot.AotPlaceholders.RegexPlaceholder;
3232
import org.springframework.util.StringUtils;
3333

@@ -83,7 +83,7 @@ boolean isRegexPlaceholderAt(int index) {
8383
return false;
8484
}
8585

86-
return this.placeholders.get(index) instanceof RegexPlaceholder;
86+
return obtainAndPotentiallyUnwrapPlaceholder(index) instanceof RegexPlaceholder;
8787
}
8888

8989
@Nullable
@@ -92,7 +92,21 @@ String getRegexOptions(int index) {
9292
return null;
9393
}
9494

95-
return this.placeholders.get(index) instanceof RegexPlaceholder rgp ? rgp.regexOptions() : null;
95+
Object placeholderValue = obtainAndPotentiallyUnwrapPlaceholder(index);
96+
return placeholderValue instanceof RegexPlaceholder rgp ? rgp.regexOptions() : null;
97+
}
98+
99+
@Nullable Object obtainAndPotentiallyUnwrapPlaceholder(int index) {
100+
101+
if (this.placeholders.isEmpty()) {
102+
return null;
103+
}
104+
105+
Object placeholerValue = this.placeholders.get(index);
106+
if (placeholerValue instanceof AsListPlaceholder asListPlaceholder) {
107+
placeholerValue = asListPlaceholder.placeholder();
108+
}
109+
return placeholerValue;
96110
}
97111

98112
@Override

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,9 @@ CodeBlock queryParametersCodeBlock() {
195195
String regexOptions = source.getQuery().getRegexOptions(i);
196196

197197
if (StringUtils.hasText(regexOptions)) {
198-
formatted.add(CodeBlock.of("toRegex($L)", parameterName));
199-
} else {
200198
formatted.add(CodeBlock.of("toRegex($L, $S)", parameterName, regexOptions));
199+
} else {
200+
formatted.add(CodeBlock.of("toRegex($L)", parameterName));
201201
}
202202
} else {
203203
formatted.add(CodeBlock.of("$L", parameterName));

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,20 @@ void findBySkillsContains() {
13411341
assertThat(result).hasSize(1).contains(carter);
13421342
}
13431343

1344+
@Test // GH-5123
1345+
void findBySkillsContainsSingleElement() {
1346+
1347+
List<Person> result = repository.findBySkillsContains("Drums");
1348+
assertThat(result).hasSize(1).contains(carter);
1349+
}
1350+
1351+
@Test // GH-5123
1352+
void findBySkillsContainsSingleElementWithIgnoreCase() {
1353+
1354+
List<Person> result = repository.findBySkillsContainsIgnoreCase("drums");
1355+
assertThat(result).hasSize(1).contains(carter);
1356+
}
1357+
13441358
@Test // DATAMONGO-1425
13451359
void findBySkillsNotContains() {
13461360

@@ -1349,6 +1363,14 @@ void findBySkillsNotContains() {
13491363
assertThat(result).doesNotContain(carter);
13501364
}
13511365

1366+
@Test // GH-5123
1367+
void findBySkillsNotContainsSingleElement() {
1368+
1369+
List<Person> result = repository.findBySkillsNotContains("Drums");
1370+
assertThat(result).hasSize((int) (repository.count() - 1));
1371+
assertThat(result).doesNotContain(carter);
1372+
}
1373+
13521374
@Test // DATAMONGO-1424
13531375
void findsPersonsByFirstnameNotLike() {
13541376

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,11 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
117117
List<Person> findByFirstnameLikeOrderByLastnameAsc(Pattern firstname, Sort sort);
118118

119119
List<Person> findBySkillsContains(List<String> skills);
120+
List<Person> findBySkillsContains(String skill);
121+
List<Person> findBySkillsContainsIgnoreCase(String skill);
120122

121123
List<Person> findBySkillsNotContains(List<String> skills);
124+
List<Person> findBySkillsNotContains(String skill);
122125

123126
@Query("{'age' : { '$lt' : ?0 } }")
124127
List<Person> findByAgeLessThan(int age, Sort sort);

0 commit comments

Comments
 (0)