Skip to content

Commit 5525a4f

Browse files
christophstroblmp911de
authored andcommitted
Add support for $firstN aggregation operator.
Closes #4139 Original pull request: #4182.
1 parent 59464f3 commit 5525a4f

File tree

4 files changed

+122
-1
lines changed

4 files changed

+122
-1
lines changed

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

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,98 @@ public Bottom output(AggregationExpression... out) {
122122
return new Bottom(append("output", Arrays.asList(out)));
123123
}
124124
}
125+
126+
/**
127+
* {@link AbstractAggregationExpression} to return the {@literal $firstN} elements.
128+
*/
129+
public static class First extends AbstractAggregationExpression {
130+
131+
protected First(Object value) {
132+
super(value);
133+
}
134+
135+
/**
136+
* @return new instance of {@link First}.
137+
*/
138+
public static First first() {
139+
return new First(Collections.emptyMap()).limit(1);
140+
}
141+
142+
/**
143+
* @return new instance of {@link First}.
144+
*/
145+
public static First first(int numberOfResults) {
146+
return new First(Collections.emptyMap()).limit(numberOfResults);
147+
}
148+
149+
/**
150+
* Limits the number of returned elements to the given value.
151+
*
152+
* @param numberOfResults
153+
* @return new instance of {@link Bottom}.
154+
*/
155+
public First limit(int numberOfResults) {
156+
return limit((Object) numberOfResults);
157+
}
158+
159+
/**
160+
* Limits the number of returned elements to the value defined by the given {@link AggregationExpression
161+
* expression}.
162+
*
163+
* @param expression must not be {@literal null}.
164+
* @return new instance of {@link Bottom}.
165+
*/
166+
public First limit(AggregationExpression expression) {
167+
return limit((Object) expression);
168+
}
169+
170+
private First limit(Object value) {
171+
return new First(append("n", value));
172+
}
173+
174+
/**
175+
* Define the field to serve as source.
176+
*
177+
* @param fieldName must not be {@literal null}.
178+
* @return new instance of {@link Bottom}.
179+
*/
180+
public First of(String fieldName) {
181+
return input(fieldName);
182+
}
183+
184+
/**
185+
* Define the expression building the value to serve as source.
186+
*
187+
* @param expression must not be {@literal null}.
188+
* @return new instance of {@link Bottom}.
189+
*/
190+
public First of(AggregationExpression expression) {
191+
return input(expression);
192+
}
193+
194+
/**
195+
* Define the field to serve as source.
196+
*
197+
* @param fieldName must not be {@literal null}.
198+
* @return new instance of {@link Bottom}.
199+
*/
200+
public First input(String fieldName) {
201+
return new First(append("input", Fields.field(fieldName)));
202+
}
203+
204+
/**
205+
* Define the expression building the value to serve as source.
206+
*
207+
* @param expression must not be {@literal null}.
208+
* @return new instance of {@link Bottom}.
209+
*/
210+
public First input(AggregationExpression expression) {
211+
return new First(append("input", expression));
212+
}
213+
214+
@Override
215+
protected String getMongoMethod() {
216+
return "$firstN";
217+
}
218+
}
125219
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ public class MethodReferenceNode extends ExpressionNode {
216216
.mappingParametersTo("output", "sortBy"));
217217
map.put("bottomN", mapArgRef().forOperator("$bottomN") //
218218
.mappingParametersTo("n", "output", "sortBy"));
219+
map.put("firstN", mapArgRef().forOperator("$firstN") //
220+
.mappingParametersTo("n", "input"));
219221

220222
// CONVERT OPERATORS
221223
map.put("convert", mapArgRef().forOperator("$convert") //
@@ -230,7 +232,6 @@ public class MethodReferenceNode extends ExpressionNode {
230232
map.put("toString", singleArgRef().forOperator("$toString"));
231233
map.put("degreesToRadians", singleArgRef().forOperator("$degreesToRadians"));
232234

233-
234235
FUNCTIONS = Collections.unmodifiableMap(map);
235236
}
236237

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,27 @@ void bottomNRenderedCorrectly() {
8989
"""));
9090
}
9191

92+
@Test // GH-4139
93+
void firstNMapsFieldNamesCorrectly() {
94+
95+
MongoMappingContext mappingContext = new MongoMappingContext();
96+
RelaxedTypeBasedAggregationOperationContext aggregationContext = new RelaxedTypeBasedAggregationOperationContext(
97+
Player.class, mappingContext,
98+
new QueryMapper(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext)));
99+
100+
Document document = SelectionOperators.First.first(3).of("score").toDocument(aggregationContext);
101+
102+
assertThat(document).isEqualTo(Document.parse("""
103+
{
104+
$firstN:
105+
{
106+
n: 3,
107+
input: "$s_cor_e"
108+
}
109+
}
110+
"""));
111+
}
112+
92113
static class Player {
93114

94115
@Field("player_id") String playerId;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,11 @@ void shouldRenderBottomN() {
11841184
assertThat(transform("bottomN(3, new String[]{\"$playerId\", \"$score\" }, { \"score\" : -1 })")).isEqualTo("{ $bottomN : { n : 3, output: [ \"$playerId\", \"$score\" ], sortBy: { \"score\": -1 }}}");
11851185
}
11861186

1187+
@Test // GH-4139
1188+
void shouldRenderFirstN() {
1189+
assertThat(transform("firstN(3, \"$score\")")).isEqualTo("{ $firstN : { n : 3, input : \"$score\" }}");
1190+
}
1191+
11871192
private Document transform(String expression, Object... params) {
11881193
return (Document) transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
11891194
}

0 commit comments

Comments
 (0)