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 a86378e1d..83489e61b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2015-2016 the original author or authors. + * Copyright 2015-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. @@ -15,7 +15,9 @@ */ package org.springframework.data.mongodb.repository.query; +import lombok.EqualsAndHashCode; import lombok.Value; +import lombok.experimental.UtilityClass; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -37,12 +39,13 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import com.mongodb.DBObject; import com.mongodb.util.JSON; /** - * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placholders within a + * {@link ExpressionEvaluatingParameterBinder} allows to evaluate, convert and bind parameters to placeholders within a * {@link String}. - * + * * @author Christoph Strobl * @author Thomas Darimont * @author Oliver Gierke @@ -56,7 +59,7 @@ class ExpressionEvaluatingParameterBinder { /** * Creates new {@link ExpressionEvaluatingParameterBinder} - * + * * @param expressionParser must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. */ @@ -73,7 +76,7 @@ class ExpressionEvaluatingParameterBinder { /** * 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}. @@ -90,7 +93,7 @@ class ExpressionEvaluatingParameterBinder { /** * 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}. @@ -110,16 +113,23 @@ class ExpressionEvaluatingParameterBinder { Matcher matcher = createReplacementPattern(bindingContext.getBindings()).matcher(input); StringBuffer buffer = new StringBuffer(); + int parameterIndex = 0; while (matcher.find()) { - ParameterBinding binding = bindingContext.getBindingFor(extractPlaceholder(matcher.group())); + Placeholder placeholder = extractPlaceholder(parameterIndex++, matcher); + ParameterBinding binding = bindingContext.getBindingFor(placeholder); String valueForBinding = getParameterValueForBinding(accessor, bindingContext.getParameters(), binding); // appendReplacement does not like unescaped $ sign and others, so we need to quote that stuff first matcher.appendReplacement(buffer, Matcher.quoteReplacement(valueForBinding)); + if (StringUtils.hasText(placeholder.getSuffix())) { + buffer.append(placeholder.getSuffix()); + } - if (binding.isQuoted()) { - postProcessQuotedBinding(buffer, valueForBinding); + if (placeholder.isQuoted()) { + postProcessQuotedBinding(buffer, valueForBinding, + !binding.isExpression() ? accessor.getBindableValue(binding.getParameterIndex()) : null, + binding.isExpression()); } } @@ -134,8 +144,10 @@ class ExpressionEvaluatingParameterBinder { * * @param buffer the {@link StringBuffer} to operate upon. * @param valueForBinding the actual binding value. + * @param raw the raw binding value + * @param isExpression {@literal true} if the binding value results from a SpEL expression. */ - private void postProcessQuotedBinding(StringBuffer buffer, String valueForBinding) { + private void postProcessQuotedBinding(StringBuffer buffer, String valueForBinding, Object raw, boolean isExpression) { int quotationMarkIndex = buffer.length() - valueForBinding.length() - 1; char quotationMark = buffer.charAt(quotationMarkIndex); @@ -151,7 +163,8 @@ class ExpressionEvaluatingParameterBinder { quotationMark = buffer.charAt(quotationMarkIndex); } - if (valueForBinding.startsWith("{")) { // remove quotation char before the complex object string + // remove quotation char before the complex object string + if (valueForBinding.startsWith("{") && (raw instanceof DBObject || isExpression)) { buffer.deleteCharAt(quotationMarkIndex); @@ -181,7 +194,12 @@ class ExpressionEvaluatingParameterBinder { : accessor.getBindableValue(binding.getParameterIndex()); if (value instanceof String && binding.isQuoted()) { - return ((String) value).startsWith("{") ? (String) value : ((String) value).replace("\"", "\\\""); + + if (binding.isExpression() && ((String) value).startsWith("{")) { + return (String) value; + } + + return QuotedString.unquote(JSON.serialize(value)); } if (value instanceof byte[]) { @@ -228,8 +246,9 @@ class ExpressionEvaluatingParameterBinder { for (ParameterBinding binding : bindings) { regex.append("|"); - regex.append(Pattern.quote(binding.getParameter())); - regex.append("['\"]?"); // potential quotation char (as in { foo : '?0' }). + regex.append("(" + Pattern.quote(binding.getParameter()) + ")"); + regex.append("([\\w.]*"); + regex.append("(\\W?['\"]|\\w*')?)"); } return Pattern.compile(regex.substring(1)); @@ -239,14 +258,40 @@ class ExpressionEvaluatingParameterBinder { * Extract the placeholder stripping any trailing trailing quotation mark that might have resulted from the * {@link #createReplacementPattern(List) pattern} used. * - * @param groupName The actual {@link Matcher#group() group}. + * @param parameterIndex The actual parameter index. + * @param matcher The actual {@link Matcher}. * @return */ - private Placeholder extractPlaceholder(String groupName) { + private Placeholder extractPlaceholder(int parameterIndex, Matcher matcher) { - return !groupName.endsWith("'") && !groupName.endsWith("\"") ? // - Placeholder.of(groupName, false) : // - Placeholder.of(groupName.substring(0, groupName.length() - 1), true); + String rawPlaceholder = matcher.group(parameterIndex * 3 + 1); + String suffix = matcher.group(parameterIndex * 3 + 2); + + if (!StringUtils.hasText(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)) { + + boolean quoted = QuotedString.endsWithQuote(suffix); + + return Placeholder.of(parameterIndex, rawPlaceholder, quoted, + quoted ? QuotedString.unquoteSuffix(suffix) : suffix); + } + return Placeholder.of(parameterIndex, rawPlaceholder, false, null); } /** @@ -317,8 +362,9 @@ class ExpressionEvaluatingParameterBinder { Map map = new LinkedHashMap(bindings.size(), 1); + int parameterIndex = 0; for (ParameterBinding binding : bindings) { - map.put(Placeholder.of(binding.getParameter(), binding.isQuoted()), binding); + map.put(Placeholder.of(parameterIndex++, binding.getParameter(), binding.isQuoted(), null), binding); } return map; @@ -332,10 +378,13 @@ class ExpressionEvaluatingParameterBinder { * @since 1.9 */ @Value(staticConstructor = "of") + @EqualsAndHashCode(exclude = { "quoted", "suffix" }) static class Placeholder { + private int parameterIndex; private final String parameter; private final boolean quoted; + private final String suffix; /* * (non-Javadoc) @@ -343,7 +392,46 @@ class ExpressionEvaluatingParameterBinder { */ @Override public String toString() { - return quoted ? String.format("'%s'", parameter) : parameter; + return quoted ? String.format("'%s'", parameter + (suffix != null ? suffix : "")) + : parameter + (suffix != null ? suffix : ""); + } + + } + + /** + * Utility to handle quoted strings using single/double quotes. + * + * @author Mark Paluch + */ + @UtilityClass + static class QuotedString { + + /** + * @param string + * @return {@literal true} if {@literal string} ends with a single/double quote. + */ + static boolean endsWithQuote(String string) { + return string.endsWith("'") || string.endsWith("\""); + } + + /** + * Remove trailing quoting from {@literal quoted}. + * + * @param quoted + * @return {@literal quoted} with removed quotes. + */ + public static String unquoteSuffix(String quoted) { + return quoted.substring(0, quoted.length() - 1); + } + + /** + * Remove leading and trailing quoting from {@literal quoted}. + * + * @param quoted + * @return {@literal quoted} with removed quotes. + */ + public static String unquote(String quoted) { + return quoted.substring(1, quoted.length() - 1); } } } 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 33d2e15e7..88c3c31d4 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-2015 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,10 +38,11 @@ import com.mongodb.util.JSON; /** * Query to use a plain JSON String to create the {@link Query} to actually execute. - * + * * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont + * @author Mark Paluch */ public class StringBasedMongoQuery extends AbstractMongoQuery { @@ -59,7 +60,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}. @@ -126,7 +127,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { return query; } - /* + /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isCountQuery() */ @@ -146,10 +147,10 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { /** * A parser that extracts the parameter bindings from a given query string. - * + * * @author Thomas Darimont */ - private static enum ParameterBindingParser { + private enum ParameterBindingParser { INSTANCE; @@ -169,7 +170,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { /** * 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 @@ -256,7 +257,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { } else if (value instanceof Pattern) { - String string = ((Pattern) value).toString().trim(); + String string = value.toString().trim(); Matcher valueMatcher = PARSEABLE_BINDING_PATTERN.matcher(string); while (valueMatcher.find()) { @@ -264,7 +265,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { int paramIndex = Integer.parseInt(valueMatcher.group(PARAMETER_INDEX_GROUP)); /* - * The pattern is used as a direct parameter replacement, e.g. 'field': ?1, + * The pattern is used as a direct parameter replacement, e.g. 'field': ?1, * therefore we treat it as not quoted to remain backwards compatible. */ boolean quoted = !string.equals(PARAMETER_PREFIX + paramIndex); @@ -297,8 +298,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { 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)); } @@ -315,7 +315,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { /** * A generic parameter binding with name or position information. - * + * * @author Thomas Darimont */ static class ParameterBinding { @@ -326,7 +326,7 @@ public class StringBasedMongoQuery extends AbstractMongoQuery { /** * 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 0cc59df1b..9e9827d96 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 @@ -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. @@ -23,6 +23,7 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import javax.xml.bind.DatatypeConverter; @@ -59,7 +60,7 @@ import com.mongodb.util.JSON; /** * Unit tests for {@link StringBasedMongoQuery}. - * + * * @author Oliver Gierke * @author Christoph Strobl * @author Thomas Darimont @@ -150,7 +151,7 @@ public class StringBasedMongoQueryUnitTests { public void bindsDbrefCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByHavingSizeFansNotZero"); - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] {}); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(new BasicQuery("{ fans : { $not : { $size : 0 } } }").getQueryObject())); @@ -198,7 +199,7 @@ public class StringBasedMongoQueryUnitTests { @Test public void shouldSupportRespectExistingQuotingInFindByTitleBeginsWithExplicitQuoting() throws Exception { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { "fun" }); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "fun"); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByTitleBeginsWithExplicitQuoting", String.class); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); @@ -212,7 +213,7 @@ public class StringBasedMongoQueryUnitTests { @Test public void shouldParseQueryWithParametersInExpression() throws Exception { - ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, new Object[] { 1, 2, 3, 4 }); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, 1, 2, 3, 4); StringBasedMongoQuery mongoQuery = createQueryForMethod("findByQueryWithParametersInExpression", int.class, int.class, int.class, int.class); @@ -407,9 +408,9 @@ public class StringBasedMongoQueryUnitTests { public void shouldQuoteStringReplacementCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews', password: 'foo"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews', password: 'foo"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(not(new BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews', password: 'foo"))); @@ -422,9 +423,9 @@ public class StringBasedMongoQueryUnitTests { public void shouldQuoteStringReplacementContainingQuotesCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews\", password: \"foo"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "Matthews\", password: \"foo"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is(not(new BasicDBObjectBuilder().add("lastname", "Matthews").add("password", "foo").get()))); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "Matthews\", password: \"foo"))); @@ -437,10 +438,10 @@ public class StringBasedMongoQueryUnitTests { public void shouldQuoteStringReplacementWithQuotationsCorrectly() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "\"Dave Matthews\", password: 'foo"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "\"Dave Matthews\", password: 'foo"))); } @@ -452,11 +453,10 @@ public class StringBasedMongoQueryUnitTests { public void shouldQuoteComplexQueryStringCorreclty() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"calamity\" }"); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"calamity\" }"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); - assertThat(query.getQueryObject(), - is((DBObject) new BasicDBObject("lastname", new BasicDBObject("$ne", "calamity")))); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "{ $ne : \"calamity\" }"))); } /** @@ -466,12 +466,119 @@ public class StringBasedMongoQueryUnitTests { public void shouldQuotationInQuotedComplexQueryString() throws Exception { StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameQuoted", String.class); - ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "{ $ne : \"\\\"calamity\\\"\" }"); - org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", "{ $ne : \"\\\"calamity\\\"\" }"))); + } + + /** + * @see DATAMONGO-1575 + */ + @Test + public void shouldTakeBsonParameterAsIs() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByWithBsonArgument", DBObject.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, + new BasicDBObject("$regex", "^calamity$")); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("arg0", Pattern.compile("^calamity$")))); + } + + /** + * @see DATAMONGO-1575 + */ + @Test + public void shouldReplaceParametersInInQuotedExpressionOfNestedQueryOperator() throws Exception { + + StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastnameRegex", String.class); + ConvertingParameterAccessor accessor = StubParameterAccessor.getAccessor(converter, "calamity"); + + org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accessor); + assertThat(query.getQueryObject(), is((DBObject) new BasicDBObject("lastname", Pattern.compile("^(calamity)")))); + } + + /** + * @see DATAMONGO-1603 + */ + @Test + 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"))); + } + + /** + * @see DATAMONGO-1575 + */ + @Test + 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"))); + } + + /** + * @see DATAMONGO-1575 + */ + @Test + 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("lastname", new BasicDBObject("$ne", "\"calamity\"")))); + 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 { @@ -494,6 +601,12 @@ public class StringBasedMongoQueryUnitTests { @Query("{ 'lastname' : '?0' }") Person findByLastnameQuoted(String lastname); + @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); @@ -538,5 +651,23 @@ public class StringBasedMongoQueryUnitTests { @Query("{ 'arg0' : ?0, 'arg1' : ?1 }") List findByStringWithWildcardChar(String arg0, String arg1); + + @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); } }