Browse Source

Consider context when binding string parameters.

Closes: #5095
4.5.x
Christoph Strobl 1 month ago
parent
commit
21c229731f
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 14
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java
  2. 8
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  3. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  4. 63
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java

14
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReader.java

@ -68,6 +68,8 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { @@ -68,6 +68,8 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)");
private static final Pattern EXPRESSION_BINDING_PATTERN = Pattern.compile("[\\?:][#$]\\{.*\\}");
private static final Pattern SPEL_PARAMETER_BINDING_PATTERN = Pattern.compile("('\\?(\\d+)'|\\?(\\d+))");
private static final String QUOTE_START = "\\Q";
private static final String QUOTE_END = "\\E";
private final ParameterBindingContext bindingContext;
@ -456,7 +458,13 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { @@ -456,7 +458,13 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
String group = matcher.group();
int index = computeParameterIndex(group);
computedValue = computedValue.replace(group, nullSafeToString(getBindableValueForIndex(index)));
String bindValue = nullSafeToString(getBindableValueForIndex(index));
if(isQuoted(tokenValue)) {
bindValue = bindValue.replaceAll("\\%s".formatted(QUOTE_START), Matcher.quoteReplacement("\\%s".formatted(QUOTE_START))) //
.replaceAll("\\%s".formatted(QUOTE_END), Matcher.quoteReplacement("\\%s".formatted(QUOTE_END)));
}
computedValue = computedValue.replace(group, bindValue);
}
if (isRegularExpression) {
@ -482,6 +490,10 @@ public class ParameterBindingJsonReader extends AbstractBsonReader { @@ -482,6 +490,10 @@ public class ParameterBindingJsonReader extends AbstractBsonReader {
return ObjectUtils.nullSafeToString(value);
}
private static boolean isQuoted(String value) {
return value.contains(QUOTE_START) || value.contains(QUOTE_END);
}
private static int computeParameterIndex(String parameter) {
return NumberUtils.parseNumber(parameter.replace("?", "").replace("'", ""), Integer.class);
}

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

@ -728,6 +728,14 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie @@ -728,6 +728,14 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie
assertThat(result.get(0)).isEqualTo(dave);
}
@Test // DATAMONGO-770
void findByFirstnameStartingWith() {
String inputString = "\\E.*\\Q";
List<Person> result = repository.findByFirstnameStartingWith(inputString);
assertThat(result).isEmpty();
}
@Test // DATAMONGO-770
void findByFirstnameEndingWithIgnoreCase() {

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

@ -273,6 +273,8 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query @@ -273,6 +273,8 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
// DATAMONGO-770
List<Person> findByFirstnameNotIgnoreCase(String firstName);
List<Person> findByFirstnameStartingWith(String firstName);
// DATAMONGO-770
List<Person> findByFirstnameStartingWithIgnoreCase(String firstName);

63
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/util/json/ParameterBindingJsonReaderUnitTests.java

@ -24,6 +24,7 @@ import java.util.Collections; @@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;
import org.bson.BsonBinary;
import org.bson.BsonBinarySubType;
@ -31,6 +32,9 @@ import org.bson.BsonRegularExpression; @@ -31,6 +32,9 @@ import org.bson.BsonRegularExpression;
import org.bson.Document;
import org.bson.codecs.DecoderContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.spel.ExpressionDependencies;
@ -84,47 +88,12 @@ class ParameterBindingJsonReaderUnitTests { @@ -84,47 +88,12 @@ class ParameterBindingJsonReaderUnitTests {
assertThat(target).isEqualTo(new Document("lastname", "100"));
}
@Test // GH-4806
void regexConsidersOptions() {
@ParameterizedTest // GH-4806
@MethodSource("treatNestedStringParametersArgs")
void treatNestedStringParameters(String source, String value, Object expected) {
Document target = parse("{ 'c': /^true$/i }");
BsonRegularExpression pattern = target.get("c", BsonRegularExpression.class);
assertThat(pattern.getPattern()).isEqualTo("^true$");
assertThat(pattern.getOptions()).isEqualTo("i");
}
@Test // GH-4806
void regexConsidersBindValueWithOptions() {
Document target = parse("{ 'c': /^?0$/i }", "foo");
BsonRegularExpression pattern = target.get("c", BsonRegularExpression.class);
assertThat(pattern.getPattern()).isEqualTo("^foo$");
assertThat(pattern.getOptions()).isEqualTo("i");
}
@Test // GH-4806
void treatsQuotedValueThatLooksLikeRegexAsPlainString() {
Document target = parse("{ 'c': '/^?0$/i' }", "foo");
assertThat(target.get("c")).isInstanceOf(String.class);
}
@Test // GH-4806
void treatsStringParameterValueThatLooksLikeRegexAsPlainString() {
Document target = parse("{ 'c': ?0 }", "/^foo$/i");
assertThat(target.get("c")).isInstanceOf(String.class);
}
@Test
void bindValueToRegex() {
Document target = parse("{ 'lastname' : { '$regex' : '^(?0)'} }", "kohlin");
assertThat(target).isEqualTo(Document.parse("{ 'lastname' : { '$regex' : '^(kohlin)'} }"));
Document target = parse(source, value);
assertThat(target.get("value")).isEqualTo(expected);
}
@Test
@ -634,6 +603,20 @@ class ParameterBindingJsonReaderUnitTests { @@ -634,6 +603,20 @@ class ParameterBindingJsonReaderUnitTests {
assertThat(value.getType()).isEqualTo(BsonBinarySubType.UUID_STANDARD.getValue());
}
static Stream<Arguments> treatNestedStringParametersArgs() {
return Stream.of( //
Arguments.of("{ 'value': '/^?0$/i' }", "foo", "/^foo$/i"),
Arguments.of("{ 'value': /^true$/i }", null, new BsonRegularExpression("^true$", "i")),
Arguments.of("{ 'value': /^?0$/i }", "foo", new BsonRegularExpression("^foo$", "i")), //
Arguments.of("{ 'value': '/^?0$/i' }", "\\Qfoo\\E", "/^\\Qfoo\\E$/i"),
Arguments.of("{ 'value': '?0' }", "/^foo$/i", "/^foo$/i"), //
Arguments.of("{ 'value': /^\\Q?0\\E/}", "foo", new BsonRegularExpression("^\\Qfoo\\E")), //
Arguments.of("{ 'value': /^\\Q?0\\E/}", "\\E.*", new BsonRegularExpression("^\\Q\\\\E.*\\E")), //
Arguments.of("{ 'value': ?0 }", "/^foo$/i", "/^foo$/i"), //
Arguments.of("{ 'value': { '$regex' : '^(?0)'} }", "foo", new Document("$regex", "^(foo)")) //
);
}
private static Document parse(String json, Object... args) {
ParameterBindingJsonReader reader = new ParameterBindingJsonReader(json, args);

Loading…
Cancel
Save