diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java
index 8b6bb03875..19d14bdb44 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java
@@ -686,10 +686,28 @@ public RegexMatch regexMatch(String regex, String options) {
private RegexMatch createRegexMatch() {
return usesFieldRef() ? RegexMatch.valueOf(fieldReference) : RegexMatch.valueOf(expression);
}
+
+ public ReplaceOne replaceOne(String find,String replacement) {
+ return createReplaceOne().find(find).replacement(replacement);
+ }
+
+ private ReplaceOne createReplaceOne() {
+ return usesFieldRef() ? ReplaceOne.valueOf(fieldReference) : ReplaceOne.valueOf(expression);
+ }
+
+ public ReplaceAll replaceAll(String find,String replacement) {
+ return createReplaceAll().find(find).replacement(replacement);
+ }
+
+ private ReplaceAll createReplaceAll() {
+ return usesFieldRef() ? ReplaceAll.valueOf(fieldReference) : ReplaceAll.valueOf(expression);
+ }
private boolean usesFieldRef() {
return fieldReference != null;
}
+
+
}
/**
@@ -2078,4 +2096,260 @@ protected String getMongoMethod() {
return "$regexMatch";
}
}
+
+ /**
+ * {@link AggregationExpression} for {@code $replaceOne} which replaces the first instance of a search string in an
+ * input string with a replacement string.
+ * NOTE: Requires MongoDB 4.4 or later.
+ */
+ public static class ReplaceOne extends AbstractAggregationExpression {
+
+ protected ReplaceOne(Object value) {
+ super(value);
+ }
+
+ /**
+ * Creates new {@link ReplaceOne} using the value of the provided {@link Field fieldReference} as {@literal input}
+ * value.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return new instance of {@link ReplaceOne}.
+ */
+ public static ReplaceOne valueOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+
+ return new ReplaceOne(Collections.singletonMap("input", Fields.field(fieldReference)));
+ }
+
+ /**
+ * Creates new {@link ReplaceOne} using the result of the provided {@link AggregationExpression} as {@literal input}
+ * value.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link ReplaceOne}.
+ */
+ public static ReplaceOne valueOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+
+ return new ReplaceOne(Collections.singletonMap("input", expression));
+ }
+
+ /**
+ * The string to use to replace the first matched instance of {@code find} in input.
+ *
+ * @param replacement must not be {@literal null}.
+ * @return new instance of {@link ReplaceOne}.
+ */
+ public ReplaceOne replacement(String replacement) {
+
+ Assert.notNull(replacement, "Replacement must not be null!");
+
+ return new ReplaceOne(append("replacement", replacement));
+ }
+
+ /**
+ * Specifies the reference to the {@link Field field} holding the string to use to replace the first matched
+ * instance of {@code find} in input.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return new instance of {@link ReplaceOne}.
+ */
+ public ReplaceOne replacementOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+
+ return new ReplaceOne(append("replacement", Fields.field(fieldReference)));
+ }
+
+ /**
+ * Specifies the {@link AggregationExpression} evaluating to the string to use to replace the first matched instance
+ * of {@code find} in {@code input}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link ReplaceOne}.
+ */
+ public ReplaceOne replacementOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+
+ return new ReplaceOne(append("replacement", expression));
+ }
+
+ /**
+ * The string to search for within the given input field.
+ *
+ * @param find must not be {@literal null}.
+ * @return new instance of {@link ReplaceOne}.
+ */
+ public ReplaceOne find(String searchStr) {
+
+ Assert.notNull(searchStr, "Search string must not be null!");
+
+ Map search = append("find", searchStr);
+
+ return new ReplaceOne(search);
+ }
+
+ /**
+ * Specify the reference to the {@link Field field} holding the string to search for within the given input field.
+ *
+ * @param find must not be {@literal null}.
+ * @return new instance of {@link ReplaceOne}.
+ */
+ public ReplaceOne findOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "fieldReference must not be null!");
+
+ return new ReplaceOne(append("find", fieldReference));
+ }
+
+ /**
+ * Specify the {@link AggregationExpression} evaluating to the the string to search for within the given input
+ * field.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link ReplaceOne}.
+ */
+ public ReplaceOne findOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+
+ return new ReplaceOne(append("find", expression));
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$replaceOne";
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $replaceAll} which replaces all instances of a search string in an input
+ * string with a replacement string.
+ * NOTE: Requires MongoDB 4.4 or later.
+ */
+ public static class ReplaceAll extends AbstractAggregationExpression {
+
+ protected ReplaceAll(Object value) {
+ super(value);
+ }
+
+ /**
+ * Creates new {@link ReplaceAll} using the value of the provided {@link Field fieldReference} as {@literal input}
+ * value.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return new instance of {@link ReplaceAll}.
+ */
+ public static ReplaceAll valueOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+
+ return new ReplaceAll(Collections.singletonMap("input", Fields.field(fieldReference)));
+ }
+
+ /**
+ * Creates new {@link ReplaceAll} using the result of the provided {@link AggregationExpression} as {@literal input}
+ * value.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link ReplaceAll}.
+ */
+ public static ReplaceAll valueOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+
+ return new ReplaceAll(Collections.singletonMap("input", expression));
+ }
+
+ /**
+ * The string to use to replace the first matched instance of {@code find} in input.
+ *
+ * @param replacement must not be {@literal null}.
+ * @return new instance of {@link ReplaceAll}.
+ */
+ public ReplaceAll replacement(String replacement) {
+
+ Assert.notNull(replacement, "Replacement must not be null!");
+
+ return new ReplaceAll(append("replacement", replacement));
+ }
+
+ /**
+ * Specifies the reference to the {@link Field field} holding the string to use to replace the first matched
+ * instance of {@code find} in input.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return new instance of {@link ReplaceAll}.
+ */
+ public ReplaceAll replacementOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+
+ return new ReplaceAll(append("replacement", Fields.field(fieldReference)));
+ }
+
+ /**
+ * Specifies the {@link AggregationExpression} evaluating to the string to use to replace the first matched instance
+ * of {@code find} in input.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link ReplaceAll}.
+ */
+ public ReplaceAll replacementOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+
+ return new ReplaceAll(append("replacement", expression));
+ }
+
+ /**
+ * The string to search for within the given input field.
+ *
+ * @param find must not be {@literal null}.
+ * @return new instance of {@link ReplaceAll}.
+ */
+ public ReplaceAll find(String searchStr) {
+
+ Assert.notNull(searchStr, "Search string must not be null!");
+
+ Map search = append("find", searchStr);
+
+ return new ReplaceAll(search);
+ }
+
+ /**
+ * Specify the reference to the {@link Field field} holding the string to search for within the given input field.
+ *
+ * @param find must not be {@literal null}.
+ * @return new instance of {@link ReplaceAll}.
+ */
+ public ReplaceAll findOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "fieldReference must not be null!");
+
+ return new ReplaceAll(append("find", fieldReference));
+ }
+
+ /**
+ * Specify the {@link AggregationExpression} evaluating to the string to search for within the given input field.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link ReplaceAll}.
+ */
+ public ReplaceAll findOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+
+ return new ReplaceAll(append("find", expression));
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$replaceAll";
+ }
+ }
+
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
index dc7a3cc982..5d9e839ae0 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
@@ -124,6 +124,8 @@ public class MethodReferenceNode extends ExpressionNode {
map.put("regexFind", mapArgRef().forOperator("$regexFind").mappingParametersTo("input", "regex" , "options"));
map.put("regexFindAll", mapArgRef().forOperator("$regexFindAll").mappingParametersTo("input", "regex" , "options"));
map.put("regexMatch", mapArgRef().forOperator("$regexMatch").mappingParametersTo("input", "regex" , "options"));
+ map.put("replaceOne", mapArgRef().forOperator("$replaceOne").mappingParametersTo("input", "find" , "replacement"));
+ map.put("replaceAll", mapArgRef().forOperator("$replaceAll").mappingParametersTo("input", "find" , "replacement"));
// TEXT SEARCH OPERATORS
map.put("meta", singleArgRef().forOperator("$meta"));
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
index 899e02a172..fd4aa70988 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
@@ -864,6 +864,20 @@ void shouldRenderRegexMatchWithOptionsFromFieldReference() {
assertThat(transform("regexMatch(field1,'e',field2)"))
.isEqualTo("{ \"$regexMatch\" : {\"input\" : \"$field1\" , \"regex\" : \"e\" , \"options\" : \"$field2\"}}");
}
+
+ @Test
+ void shouldRenderReplaceOne() {
+
+ assertThat(transform("replaceOne(field,'bar','baz')"))
+ .isEqualTo("{ \"$replaceOne\" : {\"input\" : \"$field\" , \"find\" : \"bar\" , \"replacement\" : \"baz\"}}");
+ }
+
+ @Test
+ void shouldRenderReplaceAll() {
+
+ assertThat(transform("replaceAll(field,'bar','baz')"))
+ .isEqualTo("{ \"$replaceAll\" : {\"input\" : \"$field\" , \"find\" : \"bar\" , \"replacement\" : \"baz\"}}");
+ }
@Test // DATAMONGO-2077
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/StringOperatorsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/StringOperatorsUnitTests.java
index d8ba5129e0..35f9611fc5 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/StringOperatorsUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/StringOperatorsUnitTests.java
@@ -281,4 +281,35 @@ void shouldRenderRegexFindWithOptionsExpression() {
.isEqualTo("{ $regexFind: { \"input\" : \"$shrewd\", \"regex\" : \"e\" , \"options\" : " + EXPRESSION_STRING
+ " } } ");
}
+
+ @Test
+ void shouldRenderReplaceOne() {
+
+ assertThat(StringOperators.valueOf("bar").replaceOne("foobar","baz").toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo("{ $replaceOne : {\"find\" : \"foobar\", \"input\" : \"$bar\", \"replacement\" : \"baz\"}}");
+ }
+
+ @Test
+ void shouldRenderReplaceOneForExpression() {
+
+ assertThat(StringOperators.valueOf(EXPRESSION).replaceOne("a","s").toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo("{ $replaceOne : {\"find\" : \"a\", \"input\" : " + EXPRESSION_STRING + ", \"replacement\" : \"s\"}}");
+ }
+
+ @Test
+ void shouldRenderReplaceAll() {
+
+ assertThat(StringOperators.valueOf("bar").replaceAll("foobar","baz").toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo("{ $replaceAll : {\"find\" : \"foobar\", \"input\" : \"$bar\", \"replacement\" : \"baz\"}}");
+ }
+
+ @Test
+ void shouldRenderReplaceAllForExpression() {
+
+ assertThat(StringOperators.valueOf(EXPRESSION).replaceAll("a","s").toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo("{ $replaceAll : {\"find\" : \"a\", \"input\" : " + EXPRESSION_STRING + ", \"replacement\" : \"s\"}}");
+ }
+
+
+
}
diff --git a/spring-data-mongodb/src/test/resources/reactive-infrastructure.xml b/spring-data-mongodb/src/test/resources/reactive-infrastructure.xml
index 896bb26812..1abf2324bf 100644
--- a/spring-data-mongodb/src/test/resources/reactive-infrastructure.xml
+++ b/spring-data-mongodb/src/test/resources/reactive-infrastructure.xml
@@ -5,7 +5,7 @@
-
+