Skip to content

Commit a19e67e

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-1603 - Fix Placeholder not replaced correctly in @query.
Fix issues when placeholders are appended with other chars eg. '?0xyz' or have been reused multiple times within the query. Additional tests and fixes for complex quoted replacements eg. in regex query. Rely on placeholder quotation indication instead of binding one. Might be misleading when placeholder is used more than once. Original pull request: #441.
1 parent c03e3a0 commit a19e67e

File tree

3 files changed

+106
-8
lines changed

3 files changed

+106
-8
lines changed

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ private String replacePlaceholders(String input, MongoParameterAccessor accessor
126126
buffer.append(placeholder.getSuffix());
127127
}
128128

129-
if (binding.isQuoted() || placeholder.isQuoted()) {
129+
if (placeholder.isQuoted()) {
130130
postProcessQuotedBinding(buffer, valueForBinding,
131131
!binding.isExpression() ? accessor.getBindableValue(binding.getParameterIndex()) : null,
132132
binding.isExpression());
@@ -247,7 +247,8 @@ private Pattern createReplacementPattern(List<ParameterBinding> bindings) {
247247

248248
regex.append("|");
249249
regex.append("(" + Pattern.quote(binding.getParameter()) + ")");
250-
regex.append("(\\W?['\"])?"); // potential quotation char (as in { foo : '?0' }).
250+
regex.append("([\\w.]*");
251+
regex.append("(\\W?['\"]|\\w*')?)");
251252
}
252253

253254
return Pattern.compile(regex.substring(1));
@@ -265,15 +266,22 @@ private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) {
265266

266267
if (matcher.groupCount() > 1) {
267268

268-
String rawPlaceholder = matcher.group(parameterIndex * 2 + 1);
269-
String suffix = matcher.group(parameterIndex * 2 + 2);
269+
String rawPlaceholder = matcher.group(parameterIndex * 3 + 1);
270+
String suffix = matcher.group(parameterIndex * 3 + 2);
270271

271272
if (!StringUtils.hasText(rawPlaceholder)) {
272273

273274
rawPlaceholder = matcher.group();
274-
suffix = "" + rawPlaceholder.charAt(rawPlaceholder.length() - 1);
275+
if(rawPlaceholder.matches(".*\\d$")) {
276+
suffix = "";
277+
} else {
278+
int index = rawPlaceholder.replaceAll("[^\\?0-9]*$", "").length() - 1;
279+
if (index > 0 && rawPlaceholder.length() > index) {
280+
suffix = rawPlaceholder.substring(index+1);
281+
}
282+
}
275283
if (QuotedString.endsWithQuote(rawPlaceholder)) {
276-
rawPlaceholder = QuotedString.unquoteSuffix(rawPlaceholder);
284+
rawPlaceholder = rawPlaceholder.substring(0, rawPlaceholder.length() - (StringUtils.hasText(suffix) ? suffix.length() : 1));
277285
}
278286
}
279287

@@ -284,6 +292,7 @@ private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) {
284292
return Placeholder.of(parameterIndex, rawPlaceholder, quoted,
285293
quoted ? QuotedString.unquoteSuffix(suffix) : suffix);
286294
}
295+
return Placeholder.of(parameterIndex, rawPlaceholder, false, null);
287296
}
288297

289298
return Placeholder.of(parameterIndex, matcher.group(), false, null);

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,7 @@ private static void potentiallyAddBinding(String source, List<ParameterBinding>
339339
while (valueMatcher.find()) {
340340

341341
int paramIndex = Integer.parseInt(valueMatcher.group(PARAMETER_INDEX_GROUP));
342-
boolean quoted = (source.startsWith("'") && source.endsWith("'"))
343-
|| (source.startsWith("\"") && source.endsWith("\""));
342+
boolean quoted = source.startsWith("'") || source.startsWith("\"");
344343

345344
bindings.add(new ParameterBinding(paramIndex, quoted));
346345
}

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,78 @@ public void shouldReplaceParametersInInQuotedExpressionOfNestedQueryOperator() t
437437
assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", Pattern.compile("^(calamity)"))));
438438
}
439439

440+
@Test // DATAMONGO-1603
441+
public void shouldAllowReuseOfPlaceholderWithinQuery() throws Exception {
442+
443+
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByReusingPlaceholdersMultipleTimes", String.class,
444+
String.class);
445+
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
446+
447+
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
448+
assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", "calamity")
449+
.append("arg1", "regalia").append("arg2", "calamity")));
450+
}
451+
452+
@Test // DATAMONGO-1603
453+
public void shouldAllowReuseOfQuotedPlaceholderWithinQuery() throws Exception {
454+
455+
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByReusingPlaceholdersMultipleTimesWhenQuoted",
456+
String.class, String.class);
457+
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
458+
459+
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
460+
assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", "calamity")
461+
.append("arg1", "regalia").append("arg2", "calamity")));
462+
}
463+
464+
@Test // DATAMONGO-1603
465+
public void shouldAllowReuseOfQuotedPlaceholderWithinQueryAndIncludeSuffixCorrectly() throws Exception {
466+
467+
StringBasedMongoQuery mongoQuery = createQueryForMethod(
468+
"findByReusingPlaceholdersMultipleTimesWhenQuotedAndSomeStuffAppended", String.class, String.class);
469+
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
470+
471+
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
472+
assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", "calamity")
473+
.append("arg1", "regalia").append("arg2", "calamitys")));
474+
}
475+
476+
@Test // DATAMONGO-1603
477+
public void shouldAllowQuotedParameterWithSuffixAppended() throws Exception {
478+
479+
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByWhenQuotedAndSomeStuffAppended", String.class,
480+
String.class);
481+
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
482+
483+
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
484+
assertThat(query.getQueryObject(),
485+
is((DBObject) new BasicDBObject().append("arg0", "calamity").append("arg1", "regalias")));
486+
}
487+
488+
@Test // DATAMONGO-1603
489+
public void shouldCaptureReplacementWithComplexSuffixCorrectly() throws Exception {
490+
491+
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByMultiRegex", String.class);
492+
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity");
493+
494+
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
495+
496+
assertThat(query.getQueryObject(), is((DBObject) JSON.parse(
497+
"{ \"$or\" : [ { \"firstname\" : { \"$regex\" : \".*calamity.*\" , \"$options\" : \"i\"}} , { \"lastname\" : { \"$regex\" : \".*calamityxyz.*\" , \"$options\" : \"i\"}}]}")));
498+
}
499+
500+
@Test // DATAMONGO-1603
501+
public void shouldAllowPlaceholderReuseInQuotedValue() throws Exception {
502+
503+
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameRegex", String.class, String.class);
504+
ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
505+
506+
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
507+
508+
assertThat(query.getQueryObject(),
509+
is((DBObject) JSON.parse("{ 'lastname' : { '$regex' : '^(calamity|John regalia|regalia)'} }")));
510+
}
511+
440512
private StringBasedMongoQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
441513

442514
Method method = SampleRepository.class.getMethod(name, parameters);
@@ -460,6 +532,9 @@ private interface SampleRepository extends Repository<Person, Long> {
460532
@Query("{ 'lastname' : { '$regex' : '^(?0)'} }")
461533
Person findByLastnameRegex(String lastname);
462534

535+
@Query("{'$or' : [{'firstname': {'$regex': '.*?0.*', '$options': 'i'}}, {'lastname' : {'$regex': '.*?0xyz.*', '$options': 'i'}} ]}")
536+
Person findByMultiRegex(String arg0);
537+
463538
@Query("{ 'address' : ?0 }")
464539
Person findByAddress(Address address);
465540

@@ -510,5 +585,20 @@ private interface SampleRepository extends Repository<Person, Long> {
510585

511586
@Query("{ 'arg0' : ?0 }")
512587
List<Person> findByWithBsonArgument(DBObject arg0);
588+
589+
@Query("{ 'arg0' : ?0, 'arg1' : ?1, 'arg2' : ?0 }")
590+
List<Person> findByReusingPlaceholdersMultipleTimes(String arg0, String arg1);
591+
592+
@Query("{ 'arg0' : ?0, 'arg1' : ?1, 'arg2' : '?0' }")
593+
List<Person> findByReusingPlaceholdersMultipleTimesWhenQuoted(String arg0, String arg1);
594+
595+
@Query("{ 'arg0' : '?0', 'arg1' : ?1, 'arg2' : '?0s' }")
596+
List<Person> findByReusingPlaceholdersMultipleTimesWhenQuotedAndSomeStuffAppended(String arg0, String arg1);
597+
598+
@Query("{ 'arg0' : '?0', 'arg1' : '?1s' }")
599+
List<Person> findByWhenQuotedAndSomeStuffAppended(String arg0, String arg1);
600+
601+
@Query("{ 'lastname' : { '$regex' : '^(?0|John ?1|?1)'} }") // use spel or some regex string this is fucking bad
602+
Person findByLastnameRegex(String lastname, String alternative);
513603
}
514604
}

0 commit comments

Comments
 (0)