From 5bbe481e98bbd181681dda3c52a2e386d4b758f4 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 23 Sep 2022 09:50:12 +0200 Subject: [PATCH] Add support for $minN aggregation operator. See #4139 Original pull request: #4182. --- .../aggregation/AccumulatorOperators.java | 41 ++++++++++++++++--- .../core/spel/MethodReferenceNode.java | 2 + .../AccumulatorOperatorsUnitTests.java | 14 +++++++ .../SpelExpressionTransformerUnitTests.java | 5 +++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java index 042ff9032..1a47b7759 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java @@ -133,6 +133,17 @@ public class AccumulatorOperators { return usesFieldRef() ? Min.minOf(fieldReference) : Min.minOf(expression); } + /** + * Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the + * requested number of maximum values. + * + * @return new instance of {@link Max}. + * @since 4.0 + */ + public Min min(int numberOfResults) { + return min().limit(numberOfResults); + } + /** * Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates the * population standard deviation of the input values. @@ -551,7 +562,7 @@ public class AccumulatorOperators { @Override protected String getMongoMethod() { - return "$min"; + return contains("n") ? "$minN" : "$min"; } /** @@ -563,7 +574,7 @@ public class AccumulatorOperators { public static Min minOf(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null"); - return new Min(asFields(fieldReference)); + return new Min(Collections.singletonMap("input", Fields.field(fieldReference))); } /** @@ -575,7 +586,7 @@ public class AccumulatorOperators { public static Min minOf(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null"); - return new Min(Collections.singletonList(expression)); + return new Min(Collections.singletonMap("input", expression)); } /** @@ -588,7 +599,7 @@ public class AccumulatorOperators { public Min and(String fieldReference) { Assert.notNull(fieldReference, "FieldReference must not be null"); - return new Min(append(Fields.field(fieldReference))); + return new Min(appendTo("input", Fields.field(fieldReference))); } /** @@ -601,7 +612,27 @@ public class AccumulatorOperators { public Min and(AggregationExpression expression) { Assert.notNull(expression, "Expression must not be null"); - return new Min(append(expression)); + return new Min(appendTo("input", expression)); + } + + /** + * Creates new {@link Min} that returns the given number of minimum values ({@literal $minN}). + * NOTE: Cannot be used with more than one {@literal input} value. + * + * @param numberOfResults + * @return new instance of {@link Min}. + */ + public Min limit(int numberOfResults) { + return new Min(append("n", numberOfResults)); + } + + @Override + public Document toDocument(AggregationOperationContext context) { + + if (get("n") == null) { + return toDocument(get("input"), context); + } + return super.toDocument(context); } @Override 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 1f33d18cd..2925e87e0 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 @@ -226,6 +226,8 @@ public class MethodReferenceNode extends ExpressionNode { .mappingParametersTo("n", "output", "sortBy")); map.put("maxN", mapArgRef().forOperator("$maxN") // .mappingParametersTo("n", "input")); + map.put("minN", mapArgRef().forOperator("$minN") // + .mappingParametersTo("n", "input")); // CONVERT OPERATORS map.put("convert", mapArgRef().forOperator("$convert") // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperatorsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperatorsUnitTests.java index 871f60db4..ea0710fcb 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperatorsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperatorsUnitTests.java @@ -94,6 +94,20 @@ class AccumulatorOperatorsUnitTests { .isEqualTo(Document.parse("{ $maxN: { n: 3, input : \"$price\" } }")); } + @Test // GH-4139 + void rendersMin() { + + assertThat(valueOf("price").min().toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $min: \"$price\" }")); + } + + @Test // GH-4139 + void rendersMinN() { + + assertThat(valueOf("price").min(3).toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $minN: { n: 3, input : \"$price\" } }")); + } + static class Jedi { String name; 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 547461697..06f31b1c1 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 @@ -1209,6 +1209,11 @@ public class SpelExpressionTransformerUnitTests { assertThat(transform("maxN(3, \"$score\")")).isEqualTo("{ $maxN : { n : 3, input : \"$score\" }}"); } + @Test // GH-4139 + void shouldRenderMinN() { + assertThat(transform("minN(3, \"$score\")")).isEqualTo("{ $minN : { n : 3, input : \"$score\" }}"); + } + private Document transform(String expression, Object... params) { return (Document) transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params); }