From 2dde2c725d69cd362ba83695db41496cca541e7f Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 5 Nov 2019 08:49:26 +0100 Subject: [PATCH 1/3] DATAMONGO-2370 - Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-benchmarks/pom.xml | 2 +- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 4556c2de22..333a75e424 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 2.3.0.BUILD-SNAPSHOT + 3.0.0.DATAMONGO-2370-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml index c4766040c1..720a116e23 100644 --- a/spring-data-mongodb-benchmarks/pom.xml +++ b/spring-data-mongodb-benchmarks/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-mongodb-parent - 2.3.0.BUILD-SNAPSHOT + 3.0.0.DATAMONGO-2370-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index ed39c63e76..2922c1e76d 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-mongodb-parent - 2.3.0.BUILD-SNAPSHOT + 3.0.0.DATAMONGO-2370-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 25cf02b5d5..c560eff01a 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 2.3.0.BUILD-SNAPSHOT + 3.0.0.DATAMONGO-2370-SNAPSHOT ../pom.xml From 5cfc5f3927627c9d17c238ae179dbb3df5fd5a2b Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 5 Nov 2019 10:46:29 +0100 Subject: [PATCH 2/3] DATAMONGO-2370 - Add support for $round aggregation operator. --- .../core/aggregation/ArithmeticOperators.java | 117 ++++++++++++++++++ .../core/spel/MethodReferenceNode.java | 1 + .../ArithmeticOperatorsUnitTests.java | 61 +++++++++ .../SpelExpressionTransformerUnitTests.java | 11 +- src/main/asciidoc/reference/mongodb.adoc | 2 +- 5 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperatorsUnitTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java index 10785623e3..40f28d94ce 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperators.java @@ -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; } @@ -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.
+ * + * + * @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"; + } + } } 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 fd00c39a4f..dcc351fa0f 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 @@ -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")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperatorsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperatorsUnitTests.java new file mode 100644 index 0000000000..4fd7a226a4 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ArithmeticOperatorsUnitTests.java @@ -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")))); + } +} 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 ac16820de3..28053b9508 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 @@ -23,7 +23,6 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; - import org.springframework.data.mongodb.core.Person; /** @@ -935,6 +934,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); diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index aa234b6f0d..4f789a5c29 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -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` From f963bbf4d12977fce5e7b7e787189f5d8d45e58a Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 5 Nov 2019 11:18:41 +0100 Subject: [PATCH 3/3] DATAMONGO-2370 - Polishing. Use JUnit Jupiter api for tests of touched classes and fix parsing errors in disabled ones. --- .../SpelExpressionTransformerUnitTests.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) 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 28053b9508..f0f1922525 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 @@ -20,9 +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; /** @@ -38,8 +38,8 @@ public class SpelExpressionTransformerUnitTests { Data data; - @Before - public void setup() { + @BeforeEach + public void beforeEach() { this.data = new Data(); this.data.primitiveLongValue = 42; @@ -108,7 +108,7 @@ public void shouldRenderNestedFieldReference() { } @Test // DATAMONGO-774 - @Ignore + @Disabled public void shouldRenderNestedIndexedFieldReference() { // TODO add support for rendering nested indexed field references @@ -161,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. asList( + new Document("$add", Arrays. asList(1, new Document("$numberLong", "42"), 1.2345D)), + new Document("$numberLong", "23"))).toJson()); } @Test // DATAMONGO-840 @@ -680,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\"]}}}")); }