From cdfe2a0b59a3cbf24431872d06911eb74b997784 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 22 Sep 2022 12:02:04 +0200 Subject: [PATCH] Add support for $lastN aggregation operator. Closes #4139 Original pull request: #4182. --- .../core/aggregation/SelectionOperators.java | 94 +++++++++++++++++++ .../core/spel/MethodReferenceNode.java | 2 + .../SelectionOperatorUnitTests.java | 21 +++++ .../SpelExpressionTransformerUnitTests.java | 5 + 4 files changed, 122 insertions(+) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SelectionOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SelectionOperators.java index 38e0e5f72..ac1a04159 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SelectionOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SelectionOperators.java @@ -216,4 +216,98 @@ public class SelectionOperators { return "$firstN"; } } + + /** + * {@link AbstractAggregationExpression} to return the {@literal $lastN} elements. + */ + public static class Last extends AbstractAggregationExpression { + + protected Last(Object value) { + super(value); + } + + /** + * @return new instance of {@link Last}. + */ + public static Last last() { + return new Last(Collections.emptyMap()).limit(1); + } + + /** + * @return new instance of {@link Last}. + */ + public static Last last(int numberOfResults) { + return new Last(Collections.emptyMap()).limit(numberOfResults); + } + + /** + * Limits the number of returned elements to the given value. + * + * @param numberOfResults + * @return new instance of {@link Bottom}. + */ + public Last limit(int numberOfResults) { + return limit((Object) numberOfResults); + } + + /** + * Limits the number of returned elements to the value defined by the given {@link AggregationExpression + * expression}. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link Bottom}. + */ + public Last limit(AggregationExpression expression) { + return limit((Object) expression); + } + + private Last limit(Object value) { + return new Last(append("n", value)); + } + + /** + * Define the field to serve as source. + * + * @param fieldName must not be {@literal null}. + * @return new instance of {@link Bottom}. + */ + public Last of(String fieldName) { + return input(fieldName); + } + + /** + * Define the expression building the value to serve as source. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link Bottom}. + */ + public Last of(AggregationExpression expression) { + return input(expression); + } + + /** + * Define the field to serve as source. + * + * @param fieldName must not be {@literal null}. + * @return new instance of {@link Bottom}. + */ + public Last input(String fieldName) { + return new Last(append("input", Fields.field(fieldName))); + } + + /** + * Define the expression building the value to serve as source. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link Bottom}. + */ + public Last input(AggregationExpression expression) { + return new Last(append("input", expression)); + } + + @Override + protected String getMongoMethod() { + return "$lastN"; + } + } } 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 b8b376110..62f1a606c 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 @@ -218,6 +218,8 @@ public class MethodReferenceNode extends ExpressionNode { .mappingParametersTo("n", "output", "sortBy")); map.put("firstN", mapArgRef().forOperator("$firstN") // .mappingParametersTo("n", "input")); + map.put("lastN", mapArgRef().forOperator("$lastN") // + .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/SelectionOperatorUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SelectionOperatorUnitTests.java index cc460eced..044b0adab 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SelectionOperatorUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SelectionOperatorUnitTests.java @@ -110,6 +110,27 @@ class SelectionOperatorUnitTests { """)); } + @Test // GH-4139 + void lastNMapsFieldNamesCorrectly() { + + MongoMappingContext mappingContext = new MongoMappingContext(); + RelaxedTypeBasedAggregationOperationContext aggregationContext = new RelaxedTypeBasedAggregationOperationContext( + Player.class, mappingContext, + new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext))); + + Document document = SelectionOperators.Last.last(3).of("score").toDocument(aggregationContext); + + assertThat(document).isEqualTo(Document.parse(""" + { + $lastN: + { + n: 3, + input: "$s_cor_e" + } + } + """)); + } + static class Player { @Field("player_id") String playerId; 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 1c1e91d3a..b343d68ea 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 @@ -1189,6 +1189,11 @@ public class SpelExpressionTransformerUnitTests { assertThat(transform("firstN(3, \"$score\")")).isEqualTo("{ $firstN : { n : 3, input : \"$score\" }}"); } + @Test // GH-4139 + void shouldRenderLastN() { + assertThat(transform("lastN(3, \"$score\")")).isEqualTo("{ $lastN : { n : 3, input : \"$score\" }}"); + } + private Document transform(String expression, Object... params) { return (Document) transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params); }