Skip to content

Commit 7abf69e

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-2370 - Add support for $round aggregation operator.
Original pull request: #803.
1 parent 588ed2b commit 7abf69e

File tree

5 files changed

+190
-2
lines changed

5 files changed

+190
-2
lines changed

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

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,27 @@ public StdDevSamp stdDevSamp() {
511511
: AccumulatorOperators.StdDevSamp.stdDevSampOf(expression);
512512
}
513513

514+
/**
515+
* Creates new {@link AggregationExpression} that rounds a number to a whole integer or to a specified decimal
516+
* place.
517+
*
518+
* @return new instance of {@link Round}.
519+
* @since 3.0
520+
*/
521+
public Round round() {
522+
return usesFieldRef() ? Round.roundValueOf(fieldReference) : Round.roundValueOf(expression);
523+
}
524+
525+
/**
526+
* Creates new {@link AggregationExpression} that rounds a number to a specified decimal place.
527+
*
528+
* @return new instance of {@link Round}.
529+
* @since 3.0
530+
*/
531+
public Round roundToPlace(int place) {
532+
return round().place(place);
533+
}
534+
514535
private boolean usesFieldRef() {
515536
return fieldReference != null;
516537
}
@@ -1422,4 +1443,100 @@ public static Trunc truncValueOf(Number value) {
14221443
return new Trunc(value);
14231444
}
14241445
}
1446+
1447+
/**
1448+
* {@link Round} rounds a number to a whole integer or to a specified decimal place. <br />
1449+
* <ul>
1450+
* <li>If {@link Round#place(int)} resolves to a positive integer, {@code $round} rounds to the given decimal
1451+
* places.</li>
1452+
* <li>If {@link Round#place(int)} resolves to a negative integer, {@code $round} rounds to the left of the
1453+
* decimal.</li>
1454+
* <li>If {@link Round#place(int)} resolves to a zero, {@code $round} rounds using the first digit to the right of the
1455+
* decimal.</li>
1456+
* </ul>
1457+
*
1458+
* @since 3.0
1459+
*/
1460+
public static class Round extends AbstractAggregationExpression {
1461+
1462+
private Round(Object value) {
1463+
super(value);
1464+
}
1465+
1466+
/**
1467+
* Round the value of the field that resolves to an integer, double, decimal, or long.
1468+
*
1469+
* @param fieldReference must not be {@literal null}.
1470+
* @return new instance of {@link Round}.
1471+
*/
1472+
public static Round roundValueOf(String fieldReference) {
1473+
1474+
Assert.notNull(fieldReference, "FieldReference must not be null!");
1475+
return new Round(Collections.singletonList(Fields.field(fieldReference)));
1476+
}
1477+
1478+
/**
1479+
* Round the outcome of the given expression hat resolves to an integer, double, decimal, or long.
1480+
*
1481+
* @param expression must not be {@literal null}.
1482+
* @return new instance of {@link Round}.
1483+
*/
1484+
public static Round roundValueOf(AggregationExpression expression) {
1485+
1486+
Assert.notNull(expression, "Expression must not be null!");
1487+
return new Round(Collections.singletonList(expression));
1488+
}
1489+
1490+
/**
1491+
* Round the given numeric (integer, double, decimal, or long) value.
1492+
*
1493+
* @param value must not be {@literal null}.
1494+
* @return new instance of {@link Round}.
1495+
*/
1496+
public static Round round(Number value) {
1497+
1498+
Assert.notNull(value, "Value must not be null!");
1499+
return new Round(Collections.singletonList(value));
1500+
}
1501+
1502+
/**
1503+
* The place to round to. Can be between -20 and 100, exclusive.
1504+
*
1505+
* @param place
1506+
* @return new instance of {@link Round}.
1507+
*/
1508+
public Round place(int place) {
1509+
return new Round(append(place));
1510+
}
1511+
1512+
/**
1513+
* The place to round to defined by an expression that resolves to an integer between -20 and 100, exclusive.
1514+
*
1515+
* @param expression must not be {@literal null}.
1516+
* @return new instance of {@link Round}.
1517+
*/
1518+
public Round placeOf(AggregationExpression expression) {
1519+
1520+
Assert.notNull(expression, "Expression must not be null!");
1521+
return new Round(append(expression));
1522+
}
1523+
1524+
/**
1525+
* The place to round to defined by via a field reference that resolves to an integer between -20 and 100,
1526+
* exclusive.
1527+
*
1528+
* @param fieldReference must not be {@literal null}.
1529+
* @return new instance of {@link Round}.
1530+
*/
1531+
public Round placeOf(String fieldReference) {
1532+
1533+
Assert.notNull(fieldReference, "fieldReference must not be null!");
1534+
return new Round(append(Fields.field(fieldReference)));
1535+
}
1536+
1537+
@Override
1538+
protected String getMongoMethod() {
1539+
return "$round";
1540+
}
1541+
}
14251542
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ public class MethodReferenceNode extends ExpressionNode {
8484
map.put("sqrt", singleArgRef().forOperator("$sqrt"));
8585
map.put("subtract", arrayArgRef().forOperator("$subtract"));
8686
map.put("trunc", singleArgRef().forOperator("$trunc"));
87+
map.put("round", arrayArgRef().forOperator("$round"));
8788

8889
// STRING OPERATORS
8990
map.put("concat", arrayArgRef().forOperator("$concat"));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.aggregation;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.springframework.data.mongodb.core.aggregation.ArithmeticOperators.*;
20+
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
24+
import org.bson.Document;
25+
import org.junit.jupiter.api.Test;
26+
27+
/**
28+
* @author Christoph Strobl
29+
*/
30+
31+
public class ArithmeticOperatorsUnitTests {
32+
33+
@Test // DATAMONGO-2370
34+
void roundShouldWithoutPlace() {
35+
36+
assertThat(valueOf("field").round().toDocument(Aggregation.DEFAULT_CONTEXT))
37+
.isEqualTo(new Document("$round", Collections.singletonList("$field")));
38+
}
39+
40+
@Test // DATAMONGO-2370
41+
void roundShouldWithPlace() {
42+
43+
assertThat(valueOf("field").roundToPlace(3).toDocument(Aggregation.DEFAULT_CONTEXT))
44+
.isEqualTo(new Document("$round", Arrays.asList("$field", Integer.valueOf(3))));
45+
}
46+
47+
@Test // DATAMONGO-2370
48+
void roundShouldWithPlaceFromField() {
49+
50+
assertThat(valueOf("field").round().placeOf("my-field").toDocument(Aggregation.DEFAULT_CONTEXT))
51+
.isEqualTo(new Document("$round", Arrays.asList("$field", "$my-field")));
52+
}
53+
54+
@Test // DATAMONGO-2370
55+
void roundShouldWithPlaceFromExpression() {
56+
57+
assertThat(valueOf("field").round().placeOf((ctx -> new Document("$first", "$source")))
58+
.toDocument(Aggregation.DEFAULT_CONTEXT))
59+
.isEqualTo(new Document("$round", Arrays.asList("$field", new Document("$first", "$source"))));
60+
}
61+
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.junit.Before;
2424
import org.junit.Ignore;
2525
import org.junit.Test;
26-
2726
import org.springframework.data.mongodb.core.Person;
2827

2928
/**
@@ -935,6 +934,16 @@ public void shouldRenderRange() {
935934
assertThat(transform("range(0, 10, 2)")).isEqualTo(Document.parse("{ \"$range\" : [0, 10, 2 ]}"));
936935
}
937936

937+
@Test // DATAMONGO-2370
938+
public void shouldRenderRound() {
939+
assertThat(transform("round(field)")).isEqualTo(Document.parse("{ \"$round\" : [\"$field\"]}"));
940+
}
941+
942+
@Test // DATAMONGO-2370
943+
public void shouldRenderRoundWithPlace() {
944+
assertThat(transform("round(field, 2)")).isEqualTo(Document.parse("{ \"$round\" : [\"$field\", 2]}"));
945+
}
946+
938947
private Object transform(String expression, Object... params) {
939948
Object result = transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
940949
return result == null ? null : (!(result instanceof org.bson.Document) ? result.toString() : result);

src/main/asciidoc/reference/mongodb.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2516,7 +2516,7 @@ At the time of this writing, we provide support for the following Aggregation Op
25162516
| `addToSet`, `first`, `last`, `max`, `min`, `avg`, `push`, `sum`, `(*count)`, `stdDevPop`, `stdDevSamp`
25172517

25182518
| Arithmetic Aggregation Operators
2519-
| `abs`, `add` (*via `plus`), `ceil`, `divide`, `exp`, `floor`, `ln`, `log`, `log10`, `mod`, `multiply`, `pow`, `sqrt`, `subtract` (*via `minus`), `trunc`
2519+
| `abs`, `add` (*via `plus`), `ceil`, `divide`, `exp`, `floor`, `ln`, `log`, `log10`, `mod`, `multiply`, `pow`, `sqrt`, `subtract` (*via `minus`), `trunc`, `round`
25202520

25212521
| String Aggregation Operators
25222522
| `concat`, `substr`, `toLower`, `toUpper`, `stcasecmp`, `indexOfBytes`, `indexOfCP`, `split`, `strLenBytes`, `strLenCP`, `substrCP`, `trim`, `ltrim`, `rtim`

0 commit comments

Comments
 (0)