Skip to content

Commit 494c22b

Browse files
divyajnu08christophstrobl
authored andcommitted
Add support for $replaceOne & $replaceAll aggregation operators.
Closes: #3695 Original Pull Request: #3861
1 parent 030f120 commit 494c22b

File tree

4 files changed

+321
-0
lines changed

4 files changed

+321
-0
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java

+274
Original file line numberDiff line numberDiff line change
@@ -686,10 +686,28 @@ public RegexMatch regexMatch(String regex, String options) {
686686
private RegexMatch createRegexMatch() {
687687
return usesFieldRef() ? RegexMatch.valueOf(fieldReference) : RegexMatch.valueOf(expression);
688688
}
689+
690+
public ReplaceOne replaceOne(String find,String replacement) {
691+
return createReplaceOne().find(find).replacement(replacement);
692+
}
693+
694+
private ReplaceOne createReplaceOne() {
695+
return usesFieldRef() ? ReplaceOne.valueOf(fieldReference) : ReplaceOne.valueOf(expression);
696+
}
697+
698+
public ReplaceAll replaceAll(String find,String replacement) {
699+
return createReplaceAll().find(find).replacement(replacement);
700+
}
701+
702+
private ReplaceAll createReplaceAll() {
703+
return usesFieldRef() ? ReplaceAll.valueOf(fieldReference) : ReplaceAll.valueOf(expression);
704+
}
689705

690706
private boolean usesFieldRef() {
691707
return fieldReference != null;
692708
}
709+
710+
693711
}
694712

695713
/**
@@ -2078,4 +2096,260 @@ protected String getMongoMethod() {
20782096
return "$regexMatch";
20792097
}
20802098
}
2099+
2100+
/**
2101+
* {@link AggregationExpression} for {@code $replaceOne} which replaces the first instance of a search string in an
2102+
* input string with a replacement string. <br />
2103+
* <strong>NOTE:</strong> Requires MongoDB 4.4 or later.
2104+
*/
2105+
public static class ReplaceOne extends AbstractAggregationExpression {
2106+
2107+
protected ReplaceOne(Object value) {
2108+
super(value);
2109+
}
2110+
2111+
/**
2112+
* Creates new {@link ReplaceOne} using the value of the provided {@link Field fieldReference} as {@literal input}
2113+
* value.
2114+
*
2115+
* @param fieldReference must not be {@literal null}.
2116+
* @return new instance of {@link ReplaceOne}.
2117+
*/
2118+
public static ReplaceOne valueOf(String fieldReference) {
2119+
2120+
Assert.notNull(fieldReference, "FieldReference must not be null!");
2121+
2122+
return new ReplaceOne(Collections.singletonMap("input", Fields.field(fieldReference)));
2123+
}
2124+
2125+
/**
2126+
* Creates new {@link ReplaceOne} using the result of the provided {@link AggregationExpression} as {@literal input}
2127+
* value.
2128+
*
2129+
* @param expression must not be {@literal null}.
2130+
* @return new instance of {@link ReplaceOne}.
2131+
*/
2132+
public static ReplaceOne valueOf(AggregationExpression expression) {
2133+
2134+
Assert.notNull(expression, "Expression must not be null!");
2135+
2136+
return new ReplaceOne(Collections.singletonMap("input", expression));
2137+
}
2138+
2139+
/**
2140+
* The string to use to replace the first matched instance of {@code find} in input.
2141+
*
2142+
* @param replacement must not be {@literal null}.
2143+
* @return new instance of {@link ReplaceOne}.
2144+
*/
2145+
public ReplaceOne replacement(String replacement) {
2146+
2147+
Assert.notNull(replacement, "Replacement must not be null!");
2148+
2149+
return new ReplaceOne(append("replacement", replacement));
2150+
}
2151+
2152+
/**
2153+
* Specifies the reference to the {@link Field field} holding the string to use to replace the first matched
2154+
* instance of {@code find} in input.
2155+
*
2156+
* @param fieldReference must not be {@literal null}.
2157+
* @return new instance of {@link ReplaceOne}.
2158+
*/
2159+
public ReplaceOne replacementOf(String fieldReference) {
2160+
2161+
Assert.notNull(fieldReference, "FieldReference must not be null!");
2162+
2163+
return new ReplaceOne(append("replacement", Fields.field(fieldReference)));
2164+
}
2165+
2166+
/**
2167+
* Specifies the {@link AggregationExpression} evaluating to the string to use to replace the first matched instance
2168+
* of {@code find} in {@code input}.
2169+
*
2170+
* @param expression must not be {@literal null}.
2171+
* @return new instance of {@link ReplaceOne}.
2172+
*/
2173+
public ReplaceOne replacementOf(AggregationExpression expression) {
2174+
2175+
Assert.notNull(expression, "Expression must not be null!");
2176+
2177+
return new ReplaceOne(append("replacement", expression));
2178+
}
2179+
2180+
/**
2181+
* The string to search for within the given input field.
2182+
*
2183+
* @param find must not be {@literal null}.
2184+
* @return new instance of {@link ReplaceOne}.
2185+
*/
2186+
public ReplaceOne find(String searchStr) {
2187+
2188+
Assert.notNull(searchStr, "Search string must not be null!");
2189+
2190+
Map<String, Object> search = append("find", searchStr);
2191+
2192+
return new ReplaceOne(search);
2193+
}
2194+
2195+
/**
2196+
* Specify the reference to the {@link Field field} holding the string to search for within the given input field.
2197+
*
2198+
* @param find must not be {@literal null}.
2199+
* @return new instance of {@link ReplaceOne}.
2200+
*/
2201+
public ReplaceOne findOf(String fieldReference) {
2202+
2203+
Assert.notNull(fieldReference, "fieldReference must not be null!");
2204+
2205+
return new ReplaceOne(append("find", fieldReference));
2206+
}
2207+
2208+
/**
2209+
* Specify the {@link AggregationExpression} evaluating to the the string to search for within the given input
2210+
* field.
2211+
*
2212+
* @param expression must not be {@literal null}.
2213+
* @return new instance of {@link ReplaceOne}.
2214+
*/
2215+
public ReplaceOne findOf(AggregationExpression expression) {
2216+
2217+
Assert.notNull(expression, "Expression must not be null!");
2218+
2219+
return new ReplaceOne(append("find", expression));
2220+
}
2221+
2222+
@Override
2223+
protected String getMongoMethod() {
2224+
return "$replaceOne";
2225+
}
2226+
}
2227+
2228+
/**
2229+
* {@link AggregationExpression} for {@code $replaceAll} which replaces all instances of a search string in an input
2230+
* string with a replacement string. <br />
2231+
* <strong>NOTE:</strong> Requires MongoDB 4.4 or later.
2232+
*/
2233+
public static class ReplaceAll extends AbstractAggregationExpression {
2234+
2235+
protected ReplaceAll(Object value) {
2236+
super(value);
2237+
}
2238+
2239+
/**
2240+
* Creates new {@link ReplaceAll} using the value of the provided {@link Field fieldReference} as {@literal input}
2241+
* value.
2242+
*
2243+
* @param fieldReference must not be {@literal null}.
2244+
* @return new instance of {@link ReplaceAll}.
2245+
*/
2246+
public static ReplaceAll valueOf(String fieldReference) {
2247+
2248+
Assert.notNull(fieldReference, "FieldReference must not be null!");
2249+
2250+
return new ReplaceAll(Collections.singletonMap("input", Fields.field(fieldReference)));
2251+
}
2252+
2253+
/**
2254+
* Creates new {@link ReplaceAll} using the result of the provided {@link AggregationExpression} as {@literal input}
2255+
* value.
2256+
*
2257+
* @param expression must not be {@literal null}.
2258+
* @return new instance of {@link ReplaceAll}.
2259+
*/
2260+
public static ReplaceAll valueOf(AggregationExpression expression) {
2261+
2262+
Assert.notNull(expression, "Expression must not be null!");
2263+
2264+
return new ReplaceAll(Collections.singletonMap("input", expression));
2265+
}
2266+
2267+
/**
2268+
* The string to use to replace the first matched instance of {@code find} in input.
2269+
*
2270+
* @param replacement must not be {@literal null}.
2271+
* @return new instance of {@link ReplaceAll}.
2272+
*/
2273+
public ReplaceAll replacement(String replacement) {
2274+
2275+
Assert.notNull(replacement, "Replacement must not be null!");
2276+
2277+
return new ReplaceAll(append("replacement", replacement));
2278+
}
2279+
2280+
/**
2281+
* Specifies the reference to the {@link Field field} holding the string to use to replace the first matched
2282+
* instance of {@code find} in input.
2283+
*
2284+
* @param fieldReference must not be {@literal null}.
2285+
* @return new instance of {@link ReplaceAll}.
2286+
*/
2287+
public ReplaceAll replacementOf(String fieldReference) {
2288+
2289+
Assert.notNull(fieldReference, "FieldReference must not be null!");
2290+
2291+
return new ReplaceAll(append("replacement", Fields.field(fieldReference)));
2292+
}
2293+
2294+
/**
2295+
* Specifies the {@link AggregationExpression} evaluating to the string to use to replace the first matched instance
2296+
* of {@code find} in input.
2297+
*
2298+
* @param expression must not be {@literal null}.
2299+
* @return new instance of {@link ReplaceAll}.
2300+
*/
2301+
public ReplaceAll replacementOf(AggregationExpression expression) {
2302+
2303+
Assert.notNull(expression, "Expression must not be null!");
2304+
2305+
return new ReplaceAll(append("replacement", expression));
2306+
}
2307+
2308+
/**
2309+
* The string to search for within the given input field.
2310+
*
2311+
* @param find must not be {@literal null}.
2312+
* @return new instance of {@link ReplaceAll}.
2313+
*/
2314+
public ReplaceAll find(String searchStr) {
2315+
2316+
Assert.notNull(searchStr, "Search string must not be null!");
2317+
2318+
Map<String, Object> search = append("find", searchStr);
2319+
2320+
return new ReplaceAll(search);
2321+
}
2322+
2323+
/**
2324+
* Specify the reference to the {@link Field field} holding the string to search for within the given input field.
2325+
*
2326+
* @param find must not be {@literal null}.
2327+
* @return new instance of {@link ReplaceAll}.
2328+
*/
2329+
public ReplaceAll findOf(String fieldReference) {
2330+
2331+
Assert.notNull(fieldReference, "fieldReference must not be null!");
2332+
2333+
return new ReplaceAll(append("find", fieldReference));
2334+
}
2335+
2336+
/**
2337+
* Specify the {@link AggregationExpression} evaluating to the string to search for within the given input field.
2338+
*
2339+
* @param expression must not be {@literal null}.
2340+
* @return new instance of {@link ReplaceAll}.
2341+
*/
2342+
public ReplaceAll findOf(AggregationExpression expression) {
2343+
2344+
Assert.notNull(expression, "Expression must not be null!");
2345+
2346+
return new ReplaceAll(append("find", expression));
2347+
}
2348+
2349+
@Override
2350+
protected String getMongoMethod() {
2351+
return "$replaceAll";
2352+
}
2353+
}
2354+
20812355
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java

+2
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ public class MethodReferenceNode extends ExpressionNode {
126126
map.put("regexFind", mapArgRef().forOperator("$regexFind").mappingParametersTo("input", "regex" , "options"));
127127
map.put("regexFindAll", mapArgRef().forOperator("$regexFindAll").mappingParametersTo("input", "regex" , "options"));
128128
map.put("regexMatch", mapArgRef().forOperator("$regexMatch").mappingParametersTo("input", "regex" , "options"));
129+
map.put("replaceOne", mapArgRef().forOperator("$replaceOne").mappingParametersTo("input", "find" , "replacement"));
130+
map.put("replaceAll", mapArgRef().forOperator("$replaceAll").mappingParametersTo("input", "find" , "replacement"));
129131

130132
// TEXT SEARCH OPERATORS
131133
map.put("meta", singleArgRef().forOperator("$meta"));

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java

+14
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,20 @@ void shouldRenderRegexMatchWithOptionsFromFieldReference() {
864864
assertThat(transform("regexMatch(field1,'e',field2)"))
865865
.isEqualTo("{ \"$regexMatch\" : {\"input\" : \"$field1\" , \"regex\" : \"e\" , \"options\" : \"$field2\"}}");
866866
}
867+
868+
@Test
869+
void shouldRenderReplaceOne() {
870+
871+
assertThat(transform("replaceOne(field,'bar','baz')"))
872+
.isEqualTo("{ \"$replaceOne\" : {\"input\" : \"$field\" , \"find\" : \"bar\" , \"replacement\" : \"baz\"}}");
873+
}
874+
875+
@Test
876+
void shouldRenderReplaceAll() {
877+
878+
assertThat(transform("replaceAll(field,'bar','baz')"))
879+
.isEqualTo("{ \"$replaceAll\" : {\"input\" : \"$field\" , \"find\" : \"bar\" , \"replacement\" : \"baz\"}}");
880+
}
867881

868882

869883
@Test // DATAMONGO-2077

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/StringOperatorsUnitTests.java

+31
Original file line numberDiff line numberDiff line change
@@ -281,4 +281,35 @@ void shouldRenderRegexFindWithOptionsExpression() {
281281
.isEqualTo("{ $regexFind: { \"input\" : \"$shrewd\", \"regex\" : \"e\" , \"options\" : " + EXPRESSION_STRING
282282
+ " } } ");
283283
}
284+
285+
@Test
286+
void shouldRenderReplaceOne() {
287+
288+
assertThat(StringOperators.valueOf("bar").replaceOne("foobar","baz").toDocument(Aggregation.DEFAULT_CONTEXT))
289+
.isEqualTo("{ $replaceOne : {\"find\" : \"foobar\", \"input\" : \"$bar\", \"replacement\" : \"baz\"}}");
290+
}
291+
292+
@Test
293+
void shouldRenderReplaceOneForExpression() {
294+
295+
assertThat(StringOperators.valueOf(EXPRESSION).replaceOne("a","s").toDocument(Aggregation.DEFAULT_CONTEXT))
296+
.isEqualTo("{ $replaceOne : {\"find\" : \"a\", \"input\" : " + EXPRESSION_STRING + ", \"replacement\" : \"s\"}}");
297+
}
298+
299+
@Test
300+
void shouldRenderReplaceAll() {
301+
302+
assertThat(StringOperators.valueOf("bar").replaceAll("foobar","baz").toDocument(Aggregation.DEFAULT_CONTEXT))
303+
.isEqualTo("{ $replaceAll : {\"find\" : \"foobar\", \"input\" : \"$bar\", \"replacement\" : \"baz\"}}");
304+
}
305+
306+
@Test
307+
void shouldRenderReplaceAllForExpression() {
308+
309+
assertThat(StringOperators.valueOf(EXPRESSION).replaceAll("a","s").toDocument(Aggregation.DEFAULT_CONTEXT))
310+
.isEqualTo("{ $replaceAll : {\"find\" : \"a\", \"input\" : " + EXPRESSION_STRING + ", \"replacement\" : \"s\"}}");
311+
}
312+
313+
314+
284315
}

0 commit comments

Comments
 (0)