Skip to content

DATAMONGO-2370 - Add support for $round aggregation operator. #803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.3.0.BUILD-SNAPSHOT</version>
<version>3.0.0.DATAMONGO-2370-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.3.0.BUILD-SNAPSHOT</version>
<version>3.0.0.DATAMONGO-2370-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.3.0.BUILD-SNAPSHOT</version>
<version>3.0.0.DATAMONGO-2370-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.3.0.BUILD-SNAPSHOT</version>
<version>3.0.0.DATAMONGO-2370-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,27 @@ public StdDevSamp stdDevSamp() {
: AccumulatorOperators.StdDevSamp.stdDevSampOf(expression);
}

/**
* Creates new {@link AggregationExpression} that rounds a number to a whole integer or to a specified decimal
* place.
*
* @return new instance of {@link Round}.
* @since 3.0
*/
public Round round() {
return usesFieldRef() ? Round.roundValueOf(fieldReference) : Round.roundValueOf(expression);
}

/**
* Creates new {@link AggregationExpression} that rounds a number to a specified decimal place.
*
* @return new instance of {@link Round}.
* @since 3.0
*/
public Round roundToPlace(int place) {
return round().place(place);
}

private boolean usesFieldRef() {
return fieldReference != null;
}
Expand Down Expand Up @@ -1422,4 +1443,100 @@ public static Trunc truncValueOf(Number value) {
return new Trunc(value);
}
}

/**
* {@link Round} rounds a number to a whole integer or to a specified decimal place. <br />
* <ul>
* <li>If {@link Round#place(int)} resolves to a positive integer, {@code $round} rounds to the given decimal
* places.</li>
* <li>If {@link Round#place(int)} resolves to a negative integer, {@code $round} rounds to the left of the
* decimal.</li>
* <li>If {@link Round#place(int)} resolves to a zero, {@code $round} rounds using the first digit to the right of the
* decimal.</li>
* </ul>
*
* @since 3.0
*/
public static class Round extends AbstractAggregationExpression {

private Round(Object value) {
super(value);
}

/**
* Round the value of the field that resolves to an integer, double, decimal, or long.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link Round}.
*/
public static Round roundValueOf(String fieldReference) {

Assert.notNull(fieldReference, "FieldReference must not be null!");
return new Round(Collections.singletonList(Fields.field(fieldReference)));
}

/**
* Round the outcome of the given expression hat resolves to an integer, double, decimal, or long.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Round}.
*/
public static Round roundValueOf(AggregationExpression expression) {

Assert.notNull(expression, "Expression must not be null!");
return new Round(Collections.singletonList(expression));
}

/**
* Round the given numeric (integer, double, decimal, or long) value.
*
* @param value must not be {@literal null}.
* @return new instance of {@link Round}.
*/
public static Round round(Number value) {

Assert.notNull(value, "Value must not be null!");
return new Round(Collections.singletonList(value));
}

/**
* The place to round to. Can be between -20 and 100, exclusive.
*
* @param place
* @return new instance of {@link Round}.
*/
public Round place(int place) {
return new Round(append(place));
}

/**
* The place to round to defined by an expression that resolves to an integer between -20 and 100, exclusive.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Round}.
*/
public Round placeOf(AggregationExpression expression) {

Assert.notNull(expression, "Expression must not be null!");
return new Round(append(expression));
}

/**
* The place to round to defined by via a field reference that resolves to an integer between -20 and 100,
* exclusive.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link Round}.
*/
public Round placeOf(String fieldReference) {

Assert.notNull(fieldReference, "fieldReference must not be null!");
return new Round(append(Fields.field(fieldReference)));
}

@Override
protected String getMongoMethod() {
return "$round";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public class MethodReferenceNode extends ExpressionNode {
map.put("sqrt", singleArgRef().forOperator("$sqrt"));
map.put("subtract", arrayArgRef().forOperator("$subtract"));
map.put("trunc", singleArgRef().forOperator("$trunc"));
map.put("round", arrayArgRef().forOperator("$round"));

// STRING OPERATORS
map.put("concat", arrayArgRef().forOperator("$concat"));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;

import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.aggregation.ArithmeticOperators.*;

import java.util.Arrays;
import java.util.Collections;

import org.bson.Document;
import org.junit.jupiter.api.Test;

/**
* @author Christoph Strobl
*/

public class ArithmeticOperatorsUnitTests {

@Test // DATAMONGO-2370
void roundShouldWithoutPlace() {

assertThat(valueOf("field").round().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(new Document("$round", Collections.singletonList("$field")));
}

@Test // DATAMONGO-2370
void roundShouldWithPlace() {

assertThat(valueOf("field").roundToPlace(3).toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(new Document("$round", Arrays.asList("$field", Integer.valueOf(3))));
}

@Test // DATAMONGO-2370
void roundShouldWithPlaceFromField() {

assertThat(valueOf("field").round().placeOf("my-field").toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(new Document("$round", Arrays.asList("$field", "$my-field")));
}

@Test // DATAMONGO-2370
void roundShouldWithPlaceFromExpression() {

assertThat(valueOf("field").round().placeOf((ctx -> new Document("$first", "$source")))
.toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(new Document("$round", Arrays.asList("$field", new Document("$first", "$source"))));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@
import java.util.Arrays;

import org.bson.Document;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.data.mongodb.core.Person;

/**
Expand All @@ -39,8 +38,8 @@ public class SpelExpressionTransformerUnitTests {

Data data;

@Before
public void setup() {
@BeforeEach
public void beforeEach() {

this.data = new Data();
this.data.primitiveLongValue = 42;
Expand Down Expand Up @@ -109,7 +108,7 @@ public void shouldRenderNestedFieldReference() {
}

@Test // DATAMONGO-774
@Ignore
@Disabled
public void shouldRenderNestedIndexedFieldReference() {

// TODO add support for rendering nested indexed field references
Expand Down Expand Up @@ -162,23 +161,25 @@ public void shouldRenderParameterExpressionResults() {
}

@Test // DATAMONGO-774
@Ignore("TODO: mongo3 renders this a bit strange")
public void shouldRenderNestedParameterExpressionResults() {

assertThat(
((Document) transform("[0].primitiveLongValue + [0].primitiveDoubleValue + [0].doubleValue.longValue()", data))
.toJson()).isEqualTo(Document.parse("{ \"$add\" : [ 42 , 1.2345 , 23]}").toJson());
.toJson())
.isEqualTo(Document
.parse("{ \"$add\" : [ { $numberLong : \"42\"} , 1.2345 , { $numberLong : \"23\" } ]}").toJson());
}

@Test // DATAMONGO-774
@Ignore("TODO: mongo3 renders this a bit strange")
public void shouldRenderNestedParameterExpressionResultsInNestedExpressions() {

assertThat(
((Document) transform("((1 + [0].primitiveLongValue) + [0].primitiveDoubleValue) * [0].doubleValue.longValue()",
data)).toJson()).isEqualTo(
new Document("$multiply", Arrays.asList(new Document("$add", Arrays.asList(1, 42L, 1.2345D, 23L))))
.toJson());
data)).toJson())
.isEqualTo(new Document("$multiply",
Arrays.<Object> asList(
new Document("$add", Arrays.<Object> asList(1, new Document("$numberLong", "42"), 1.2345D)),
new Document("$numberLong", "23"))).toJson());
}

@Test // DATAMONGO-840
Expand Down Expand Up @@ -681,9 +682,8 @@ public void shouldRenderMethodReferenceReverseArray() {
}

@Test // DATAMONGO-1548
@Ignore("Document API cannot render String[]")
public void shouldRenderMethodReferenceReduce() {
assertThat(transform("reduce(field, '', {'$concat':new String[]{'$$value','$$this'}})")).isEqualTo(Document.parse(
assertThat(transform("reduce(field, '', {'$concat':{'$$value','$$this'}})")).isEqualTo(Document.parse(
"{ \"$reduce\" : { \"input\" : \"$field\" , \"initialValue\" : \"\" , \"in\" : { \"$concat\" : [ \"$$value\" , \"$$this\"]}}}"));
}

Expand Down Expand Up @@ -935,6 +935,16 @@ public void shouldRenderRange() {
assertThat(transform("range(0, 10, 2)")).isEqualTo(Document.parse("{ \"$range\" : [0, 10, 2 ]}"));
}

@Test // DATAMONGO-2370
public void shouldRenderRound() {
assertThat(transform("round(field)")).isEqualTo(Document.parse("{ \"$round\" : [\"$field\"]}"));
}

@Test // DATAMONGO-2370
public void shouldRenderRoundWithPlace() {
assertThat(transform("round(field, 2)")).isEqualTo(Document.parse("{ \"$round\" : [\"$field\", 2]}"));
}

private Object transform(String expression, Object... params) {
Object result = transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
return result == null ? null : (!(result instanceof org.bson.Document) ? result.toString() : result);
Expand Down
2 changes: 1 addition & 1 deletion src/main/asciidoc/reference/mongodb.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2460,7 +2460,7 @@ At the time of this writing, we provide support for the following Aggregation Op
| `addToSet`, `first`, `last`, `max`, `min`, `avg`, `push`, `sum`, `(*count)`, `stdDevPop`, `stdDevSamp`

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

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