Skip to content

Commit fb39c31

Browse files
christophstroblmp911de
authored andcommitted
Add support for $maxN aggregation operator.
See #4139 Original pull request: #4182.
1 parent dd44647 commit fb39c31

File tree

5 files changed

+95
-8
lines changed

5 files changed

+95
-8
lines changed

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AbstractAggregationExpression.java

+39-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Arrays;
2020
import java.util.Collection;
2121
import java.util.Collections;
22+
import java.util.HashMap;
2223
import java.util.LinkedHashMap;
2324
import java.util.List;
2425
import java.util.Map;
@@ -76,11 +77,11 @@ private Object unpack(Object value, AggregationOperationContext context) {
7677
return context.getReference(field).toString();
7778
}
7879

79-
if(value instanceof Fields fields) {
80+
if (value instanceof Fields fields) {
8081
return fields.asList().stream().map(it -> unpack(it, context)).collect(Collectors.toList());
8182
}
8283

83-
if(value instanceof Sort sort) {
84+
if (value instanceof Sort sort) {
8485

8586
Document sortDoc = new Document();
8687
for (Order order : sort) {
@@ -154,9 +155,40 @@ protected Map<String, Object> append(String key, Object value) {
154155

155156
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map");
156157

157-
Map<String, Object> clone = new LinkedHashMap<>((java.util.Map) this.value);
158+
return append((Map<String, Object>) this.value, key, value);
159+
}
160+
161+
private Map<String, Object> append(Map<String, Object> existing, String key, Object value) {
162+
163+
Map<String, Object> clone = new LinkedHashMap<>(existing);
158164
clone.put(key, value);
159165
return clone;
166+
}
167+
168+
protected Map<String, Object> appendTo(String key, Object value) {
169+
170+
Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map");
171+
172+
if (this.value instanceof Map map) {
173+
174+
Map<String, Object> target = new HashMap<>(map);
175+
if (!target.containsKey(key)) {
176+
target.put(key, value);
177+
return target;
178+
}
179+
target.computeIfPresent(key, (k, v) -> {
180+
181+
if (v instanceof List<?> list) {
182+
List<Object> targetList = new ArrayList<>(list);
183+
targetList.add(value);
184+
return targetList;
185+
}
186+
return Arrays.asList(v, value);
187+
});
188+
return target;
189+
}
190+
throw new IllegalStateException(
191+
String.format("Cannot append value to %s type", ObjectUtils.nullSafeClassName(this.value)));
160192

161193
}
162194

@@ -247,6 +279,10 @@ protected <T> T get(Object key) {
247279
return (T) ((Map<String, Object>) this.value).get(key);
248280
}
249281

282+
protected boolean isArgumentMap() {
283+
return this.value instanceof Map;
284+
}
285+
250286
/**
251287
* Get the argument map.
252288
*

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java

+35-5
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ public Max max() {
112112
return usesFieldRef() ? Max.maxOf(fieldReference) : Max.maxOf(expression);
113113
}
114114

115+
/**
116+
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the
117+
* requested number of maximum values.
118+
*
119+
* @return new instance of {@link Max}.
120+
* @since 4.0
121+
*/
122+
public Max max(int numberOfResults) {
123+
return max().limit(numberOfResults);
124+
}
125+
115126
/**
116127
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the
117128
* minimum value.
@@ -441,7 +452,7 @@ private Max(Object value) {
441452

442453
@Override
443454
protected String getMongoMethod() {
444-
return "$max";
455+
return contains("n") ? "$maxN" : "$max";
445456
}
446457

447458
/**
@@ -453,7 +464,7 @@ protected String getMongoMethod() {
453464
public static Max maxOf(String fieldReference) {
454465

455466
Assert.notNull(fieldReference, "FieldReference must not be null");
456-
return new Max(asFields(fieldReference));
467+
return new Max(Collections.singletonMap("input", Fields.field(fieldReference)));
457468
}
458469

459470
/**
@@ -465,7 +476,7 @@ public static Max maxOf(String fieldReference) {
465476
public static Max maxOf(AggregationExpression expression) {
466477

467478
Assert.notNull(expression, "Expression must not be null");
468-
return new Max(Collections.singletonList(expression));
479+
return new Max(Collections.singletonMap("input", expression));
469480
}
470481

471482
/**
@@ -478,7 +489,7 @@ public static Max maxOf(AggregationExpression expression) {
478489
public Max and(String fieldReference) {
479490

480491
Assert.notNull(fieldReference, "FieldReference must not be null");
481-
return new Max(append(Fields.field(fieldReference)));
492+
return new Max(appendTo("input", Fields.field(fieldReference)));
482493
}
483494

484495
/**
@@ -491,7 +502,26 @@ public Max and(String fieldReference) {
491502
public Max and(AggregationExpression expression) {
492503

493504
Assert.notNull(expression, "Expression must not be null");
494-
return new Max(append(expression));
505+
return new Max(appendTo("input", expression));
506+
}
507+
508+
/**
509+
* Creates new {@link Max} that returns the given number of maxmimum values ({@literal $maxN}).
510+
* <strong>NOTE</strong>: Cannot be used with more than one {@literal input} value.
511+
*
512+
* @param numberOfResults
513+
* @return new instance of {@link Max}.
514+
*/
515+
public Max limit(int numberOfResults) {
516+
return new Max(append("n", numberOfResults));
517+
}
518+
519+
@Override
520+
public Document toDocument(AggregationOperationContext context) {
521+
if (get("n") == null) {
522+
return toDocument(get("input"), context);
523+
}
524+
return super.toDocument(context);
495525
}
496526

497527
@Override

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

+2
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ public class MethodReferenceNode extends ExpressionNode {
224224
.mappingParametersTo("output", "sortBy"));
225225
map.put("topN", mapArgRef().forOperator("$topN") //
226226
.mappingParametersTo("n", "output", "sortBy"));
227+
map.put("maxN", mapArgRef().forOperator("$maxN") //
228+
.mappingParametersTo("n", "input"));
227229

228230
// CONVERT OPERATORS
229231
map.put("convert", mapArgRef().forOperator("$convert") //

Diff for: spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperatorsUnitTests.java

+14
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ void rendersExpMovingAvgWithAlpha() {
8080
.isEqualTo(Document.parse("{ $expMovingAvg: { input: \"$price\", alpha: 0.75 } }"));
8181
}
8282

83+
@Test // GH-4139
84+
void rendersMax() {
85+
86+
assertThat(valueOf("price").max().toDocument(Aggregation.DEFAULT_CONTEXT))
87+
.isEqualTo(Document.parse("{ $max: \"$price\" }"));
88+
}
89+
90+
@Test // GH-4139
91+
void rendersMaxN() {
92+
93+
assertThat(valueOf("price").max(3).toDocument(Aggregation.DEFAULT_CONTEXT))
94+
.isEqualTo(Document.parse("{ $maxN: { n: 3, input : \"$price\" } }"));
95+
}
96+
8397
static class Jedi {
8498

8599
String name;

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

+5
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,11 @@ void shouldRenderLastN() {
12041204
assertThat(transform("lastN(3, \"$score\")")).isEqualTo("{ $lastN : { n : 3, input : \"$score\" }}");
12051205
}
12061206

1207+
@Test // GH-4139
1208+
void shouldRenderMaxN() {
1209+
assertThat(transform("maxN(3, \"$score\")")).isEqualTo("{ $maxN : { n : 3, input : \"$score\" }}");
1210+
}
1211+
12071212
private Document transform(String expression, Object... params) {
12081213
return (Document) transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
12091214
}

0 commit comments

Comments
 (0)