diff --git a/pom.xml b/pom.xml
index 75fa63795e..8aa9317b84 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.11.0.BUILD-SNAPSHOT
+ 1.11.0.DATAMONGO-1603-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml
index dc5a04a7ad..dddca18c0e 100644
--- a/spring-data-mongodb-cross-store/pom.xml
+++ b/spring-data-mongodb-cross-store/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.11.0.BUILD-SNAPSHOT
+ 1.11.0.DATAMONGO-1603-SNAPSHOT
../pom.xml
@@ -48,7 +48,7 @@
org.springframework.data
spring-data-mongodb
- 1.11.0.BUILD-SNAPSHOT
+ 1.11.0.DATAMONGO-1603-SNAPSHOT
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 9876e11ee3..92dd13937a 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.11.0.BUILD-SNAPSHOT
+ 1.11.0.DATAMONGO-1603-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml
index c0436bac90..5d90e8c7a7 100644
--- a/spring-data-mongodb-log4j/pom.xml
+++ b/spring-data-mongodb-log4j/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.11.0.BUILD-SNAPSHOT
+ 1.11.0.DATAMONGO-1603-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index ea1b594b99..e171a2ef31 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -11,7 +11,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.11.0.BUILD-SNAPSHOT
+ 1.11.0.DATAMONGO-1603-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java
index 154ecefdf8..83489e61b7 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ExpressionEvaluatingParameterBinder.java
@@ -45,7 +45,7 @@
/**
* {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a
* {@link String}.
- *
+ *
* @author Christoph Strobl
* @author Thomas Darimont
* @author Oliver Gierke
@@ -59,7 +59,7 @@ class ExpressionEvaluatingParameterBinder {
/**
* Creates new {@link ExpressionEvaluatingParameterBinder}
- *
+ *
* @param expressionParser must not be {@literal null}.
* @param evaluationContextProvider must not be {@literal null}.
*/
@@ -76,7 +76,7 @@ public ExpressionEvaluatingParameterBinder(SpelExpressionParser expressionParser
/**
* Bind values provided by {@link MongoParameterAccessor} to placeholders in {@literal raw} while considering
* potential conversions and parameter types.
- *
+ *
* @param raw can be {@literal null} or empty.
* @param accessor must not be {@literal null}.
* @param bindingContext must not be {@literal null}.
@@ -93,7 +93,7 @@ public String bind(String raw, MongoParameterAccessor accessor, BindingContext b
/**
* Replaced the parameter placeholders with the actual parameter values from the given {@link ParameterBinding}s.
- *
+ *
* @param input must not be {@literal null} or empty.
* @param accessor must not be {@literal null}.
* @param bindingContext must not be {@literal null}.
@@ -126,7 +126,7 @@ private String replacePlaceholders(String input, MongoParameterAccessor accessor
buffer.append(placeholder.getSuffix());
}
- if (binding.isQuoted() || placeholder.isQuoted()) {
+ if (placeholder.isQuoted()) {
postProcessQuotedBinding(buffer, valueForBinding,
!binding.isExpression() ? accessor.getBindableValue(binding.getParameterIndex()) : null,
binding.isExpression());
@@ -247,7 +247,8 @@ private Pattern createReplacementPattern(List bindings) {
regex.append("|");
regex.append("(" + Pattern.quote(binding.getParameter()) + ")");
- regex.append("(\\W?['\"])?"); // potential quotation char (as in { foo : '?0' }).
+ regex.append("([\\w.]*");
+ regex.append("(\\W?['\"]|\\w*')?)");
}
return Pattern.compile(regex.substring(1));
@@ -263,30 +264,34 @@ private Pattern createReplacementPattern(List bindings) {
*/
private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) {
- if (matcher.groupCount() > 1) {
-
- String rawPlaceholder = matcher.group(parameterIndex * 2 + 1);
- String suffix = matcher.group(parameterIndex * 2 + 2);
+ String rawPlaceholder = matcher.group(parameterIndex * 3 + 1);
+ String suffix = matcher.group(parameterIndex * 3 + 2);
- if (!StringUtils.hasText(rawPlaceholder)) {
+ if (!StringUtils.hasText(rawPlaceholder)) {
- rawPlaceholder = matcher.group();
- suffix = "" + rawPlaceholder.charAt(rawPlaceholder.length() - 1);
- if (QuotedString.endsWithQuote(rawPlaceholder)) {
- rawPlaceholder = QuotedString.unquoteSuffix(rawPlaceholder);
+ rawPlaceholder = matcher.group();
+ if (rawPlaceholder.matches(".*\\d$")) {
+ suffix = "";
+ } else {
+ int index = rawPlaceholder.replaceAll("[^\\?0-9]*$", "").length() - 1;
+ if (index > 0 && rawPlaceholder.length() > index) {
+ suffix = rawPlaceholder.substring(index + 1);
}
}
+ if (QuotedString.endsWithQuote(rawPlaceholder)) {
+ rawPlaceholder = rawPlaceholder.substring(0,
+ rawPlaceholder.length() - (StringUtils.hasText(suffix) ? suffix.length() : 1));
+ }
+ }
- if (StringUtils.hasText(suffix)) {
+ if (StringUtils.hasText(suffix)) {
- boolean quoted = QuotedString.endsWithQuote(suffix);
+ boolean quoted = QuotedString.endsWithQuote(suffix);
- return Placeholder.of(parameterIndex, rawPlaceholder, quoted,
- quoted ? QuotedString.unquoteSuffix(suffix) : suffix);
- }
+ return Placeholder.of(parameterIndex, rawPlaceholder, quoted,
+ quoted ? QuotedString.unquoteSuffix(suffix) : suffix);
}
-
- return Placeholder.of(parameterIndex, matcher.group(), false, null);
+ return Placeholder.of(parameterIndex, rawPlaceholder, false, null);
}
/**
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java
index c79f872ad1..2ea13d1a08 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2011-2016 the original author or authors.
+ * Copyright 2011-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,7 +38,7 @@
/**
* Query to use a plain JSON String to create the {@link Query} to actually execute.
- *
+ *
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
@@ -61,7 +61,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
/**
* Creates a new {@link StringBasedMongoQuery} for the given {@link MongoQueryMethod} and {@link MongoOperations}.
- *
+ *
* @param method must not be {@literal null}.
* @param mongoOperations must not be {@literal null}.
* @param expressionParser must not be {@literal null}.
@@ -99,7 +99,6 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati
this.parameterBinder = new ExpressionEvaluatingParameterBinder(expressionParser, evaluationContextProvider);
-
if (method.hasAnnotatedQuery()) {
org.springframework.data.mongodb.repository.Query queryAnnotation = method.getQueryAnnotation();
@@ -127,10 +126,10 @@ public StringBasedMongoQuery(String query, MongoQueryMethod method, MongoOperati
@Override
protected Query createQuery(ConvertingParameterAccessor accessor) {
- String queryString = parameterBinder.bind(this.query, accessor, new BindingContext(getQueryMethod()
- .getParameters(), queryParameterBindings));
- String fieldsString = parameterBinder.bind(this.fieldSpec, accessor, new BindingContext(getQueryMethod()
- .getParameters(), fieldSpecParameterBindings));
+ String queryString = parameterBinder.bind(this.query, accessor,
+ new BindingContext(getQueryMethod().getParameters(), queryParameterBindings));
+ String fieldsString = parameterBinder.bind(this.fieldSpec, accessor,
+ new BindingContext(getQueryMethod().getParameters(), fieldSpecParameterBindings));
Query query = new BasicQuery(queryString, fieldsString).with(accessor.getSort());
@@ -141,7 +140,7 @@ protected Query createQuery(ConvertingParameterAccessor accessor) {
return query;
}
- /*
+ /*
* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isCountQuery()
*/
@@ -168,7 +167,8 @@ protected boolean isDeleteQuery() {
return this.isDeleteQuery;
}
- private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery, boolean isDeleteQuery) {
+ private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
+ boolean isDeleteQuery) {
return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
}
@@ -188,7 +188,7 @@ private static int countBooleanValues(boolean... values) {
/**
* A parser that extracts the parameter bindings from a given query string.
- *
+ *
* @author Thomas Darimont
*/
private enum ParameterBindingParser {
@@ -211,7 +211,7 @@ private enum ParameterBindingParser {
/**
* Returns a list of {@link ParameterBinding}s found in the given {@code input} or an
* {@link Collections#emptyList()}.
- *
+ *
* @param input can be {@literal null} or empty.
* @param bindings must not be {@literal null}.
* @return
@@ -306,7 +306,7 @@ private static void collectParameterReferencesIntoBindings(List
while (valueMatcher.find()) {
int paramIndex = Integer.parseInt(valueMatcher.group(PARAMETER_INDEX_GROUP));
- boolean quoted = (source.startsWith("'") && source.endsWith("'"))
- || (source.startsWith("\"") && source.endsWith("\""));
+ boolean quoted = source.startsWith("'") || source.startsWith("\"");
bindings.add(new ParameterBinding(paramIndex, quoted));
}
@@ -357,7 +356,7 @@ private static int getIndexOfExpressionParameter(String input, int position) {
/**
* A generic parameter binding with name or position information.
- *
+ *
* @author Thomas Darimont
*/
static class ParameterBinding {
@@ -368,7 +367,7 @@ static class ParameterBinding {
/**
* Creates a new {@link ParameterBinding} with the given {@code parameterIndex} and {@code quoted} information.
- *
+ *
* @param parameterIndex
* @param quoted whether or not the parameter is already quoted.
*/
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java
index 8d9237bcbe..919c584750 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java
@@ -60,7 +60,7 @@
/**
* Unit tests for {@link StringBasedMongoQuery}.
- *
+ *
* @author Oliver Gierke
* @author Christoph Strobl
* @author Thomas Darimont
@@ -437,6 +437,78 @@ public void shouldReplaceParametersInInQuotedExpressionOfNestedQueryOperator() t
assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", Pattern.compile("^(calamity)"))));
}
+ @Test // DATAMONGO-1603
+ public void shouldAllowReuseOfPlaceholderWithinQuery() throws Exception {
+
+ StringBasedMongoQuery mongoQuery = createQueryForMethod("findByReusingPlaceholdersMultipleTimes", String.class,
+ String.class);
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
+
+ org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
+ assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", "calamity")
+ .append("arg1", "regalia").append("arg2", "calamity")));
+ }
+
+ @Test // DATAMONGO-1603
+ public void shouldAllowReuseOfQuotedPlaceholderWithinQuery() throws Exception {
+
+ StringBasedMongoQuery mongoQuery = createQueryForMethod("findByReusingPlaceholdersMultipleTimesWhenQuoted",
+ String.class, String.class);
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
+
+ org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
+ assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", "calamity")
+ .append("arg1", "regalia").append("arg2", "calamity")));
+ }
+
+ @Test // DATAMONGO-1603
+ public void shouldAllowReuseOfQuotedPlaceholderWithinQueryAndIncludeSuffixCorrectly() throws Exception {
+
+ StringBasedMongoQuery mongoQuery = createQueryForMethod(
+ "findByReusingPlaceholdersMultipleTimesWhenQuotedAndSomeStuffAppended", String.class, String.class);
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
+
+ org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
+ assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject().append("arg0", "calamity")
+ .append("arg1", "regalia").append("arg2", "calamitys")));
+ }
+
+ @Test // DATAMONGO-1603
+ public void shouldAllowQuotedParameterWithSuffixAppended() throws Exception {
+
+ StringBasedMongoQuery mongoQuery = createQueryForMethod("findByWhenQuotedAndSomeStuffAppended", String.class,
+ String.class);
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
+
+ org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
+ assertThat(query.getQueryObject(),
+ is((DBObject) new BasicDBObject().append("arg0", "calamity").append("arg1", "regalias")));
+ }
+
+ @Test // DATAMONGO-1603
+ public void shouldCaptureReplacementWithComplexSuffixCorrectly() throws Exception {
+
+ StringBasedMongoQuery mongoQuery = createQueryForMethod("findByMultiRegex", String.class);
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity");
+
+ org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
+
+ assertThat(query.getQueryObject(), is((DBObject) JSON.parse(
+ "{ \"$or\" : [ { \"firstname\" : { \"$regex\" : \".*calamity.*\" , \"$options\" : \"i\"}} , { \"lastname\" : { \"$regex\" : \".*calamityxyz.*\" , \"$options\" : \"i\"}}]}")));
+ }
+
+ @Test // DATAMONGO-1603
+ public void shouldAllowPlaceholderReuseInQuotedValue() throws Exception {
+
+ StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameRegex", String.class, String.class);
+ ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity", "regalia");
+
+ org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor);
+
+ assertThat(query.getQueryObject(),
+ is((DBObject) JSON.parse("{ 'lastname' : { '$regex' : '^(calamity|John regalia|regalia)'} }")));
+ }
+
private StringBasedMongoQuery createQueryForMethod(String name, Class>... parameters) throws Exception {
Method method = SampleRepository.class.getMethod(name, parameters);
@@ -460,6 +532,9 @@ private interface SampleRepository extends Repository {
@Query("{ 'lastname' : { '$regex' : '^(?0)'} }")
Person findByLastnameRegex(String lastname);
+ @Query("{'$or' : [{'firstname': {'$regex': '.*?0.*', '$options': 'i'}}, {'lastname' : {'$regex': '.*?0xyz.*', '$options': 'i'}} ]}")
+ Person findByMultiRegex(String arg0);
+
@Query("{ 'address' : ?0 }")
Person findByAddress(Address address);
@@ -510,5 +585,20 @@ private interface SampleRepository extends Repository {
@Query("{ 'arg0' : ?0 }")
List findByWithBsonArgument(DBObject arg0);
+
+ @Query("{ 'arg0' : ?0, 'arg1' : ?1, 'arg2' : ?0 }")
+ List findByReusingPlaceholdersMultipleTimes(String arg0, String arg1);
+
+ @Query("{ 'arg0' : ?0, 'arg1' : ?1, 'arg2' : '?0' }")
+ List findByReusingPlaceholdersMultipleTimesWhenQuoted(String arg0, String arg1);
+
+ @Query("{ 'arg0' : '?0', 'arg1' : ?1, 'arg2' : '?0s' }")
+ List findByReusingPlaceholdersMultipleTimesWhenQuotedAndSomeStuffAppended(String arg0, String arg1);
+
+ @Query("{ 'arg0' : '?0', 'arg1' : '?1s' }")
+ List findByWhenQuotedAndSomeStuffAppended(String arg0, String arg1);
+
+ @Query("{ 'lastname' : { '$regex' : '^(?0|John ?1|?1)'} }") // use spel or some regex string this is fucking bad
+ Person findByLastnameRegex(String lastname, String alternative);
}
}