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 4d86bac98..159c6bbea 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 @@ -155,6 +155,46 @@ public class ArithmeticOperators { return usesFieldRef() ? Ceil.ceilValueOf(fieldReference) : Ceil.ceilValueOf(expression); } + /** + * Creates new {@link AggregationExpression} that calculates the mathematical derivative value. + * + * @return new instance of {@link Derivative}. + * @since 3.3 + */ + public Derivative derivative() { + return derivative((String) null); + } + + /** + * Creates new {@link AggregationExpression} that calculates the mathematical derivative value. + * + * @param unit The time unit ({@link WindowUnits#WEEK}, {@link WindowUnits#DAY}, {@link WindowUnits#HOUR}, + * {@link WindowUnits#MINUTE}, {@link WindowUnits#SECOND}, {@link WindowUnits#MILLISECOND}) to apply. + * @return new instance of {@link Derivative}. + * @since 3.3 + */ + public Derivative derivative(WindowUnit unit) { + + Assert.notNull(unit, "Window unit must not be null"); + + return derivative(unit.name().toLowerCase(Locale.ROOT)); + } + + /** + * Creates new {@link AggregationExpression} that calculates the mathematical derivative value. + * + * @param unit The time unit ({@literal week, day, hour, minute, second, millisecond}) to apply can be + * {@literal null}. + * @return new instance of {@link Derivative}. + * @since 3.3 + */ + public Derivative derivative(@Nullable String unit) { + + Derivative derivative = usesFieldRef() ? Derivative.derivativeOf(fieldReference) + : Derivative.derivativeOf(expression); + return StringUtils.hasText(unit) ? derivative.unit(unit) : derivative; + } + /** * Creates new {@link AggregationExpression} that ivides the associated number by number referenced via * {@literal fieldReference}. @@ -226,6 +266,21 @@ public class ArithmeticOperators { return usesFieldRef() ? Integral.integralOf(fieldReference) : Integral.integralOf(expression); } + /** + * Creates new {@link AggregationExpression} that calculates the approximation for the mathematical integral value. + * + * @param unit The time unit ({@link WindowUnits#WEEK}, {@link WindowUnits#DAY}, {@link WindowUnits#HOUR}, + * {@link WindowUnits#MINUTE}, {@link WindowUnits#SECOND}, {@link WindowUnits#MILLISECOND}) to apply. + * @return new instance of {@link Derivative}. + * @since 3.3 + */ + public Integral integral(WindowUnit unit) { + + Assert.notNull(unit, "Window unit must not be null"); + + return integral(unit.name().toLowerCase(Locale.ROOT)); + } + /** * Creates new {@link AggregationExpression} that calculates the approximation for the mathematical integral value. * @@ -234,6 +289,9 @@ public class ArithmeticOperators { * @since 3.3 */ public Integral integral(String unit) { + + Assert.hasText(unit, "Unit must not be empty!"); + return integral().unit(unit); } @@ -618,46 +676,6 @@ public class ArithmeticOperators { return round().place(place); } - /** - * Creates new {@link AggregationExpression} that calculates the mathematical derivative value. - * - * @return new instance of {@link Derivative}. - * @since 3.3 - */ - public Derivative derivative() { - return derivative((String) null); - } - - /** - * Creates new {@link AggregationExpression} that calculates the mathematical derivative value. - * - * @param unit The time unit ({@link WindowUnits#WEEK}, {@link WindowUnits#DAY}, {@link WindowUnits#HOUR}, - * {@link WindowUnits#MINUTE}, {@link WindowUnits#SECOND}, {@link WindowUnits#MILLISECOND}) to apply. - * @return new instance of {@link Derivative}. - * @since 3.3 - */ - public Derivative derivative(WindowUnit unit) { - - Assert.notNull(unit, "Window unit must not be null"); - - return derivative(unit.name().toLowerCase(Locale.ROOT)); - } - - /** - * Creates new {@link AggregationExpression} that calculates the mathematical derivative value. - * - * @param unit The time unit ({@literal week, day, hour, minute, second, millisecond}) to apply can be - * {@literal null}. - * @return new instance of {@link Derivative}. - * @since 3.3 - */ - public Derivative derivative(@Nullable String unit) { - - Derivative derivative = usesFieldRef() ? Derivative.derivativeOf(fieldReference) - : Derivative.derivativeOf(expression); - return StringUtils.hasText(unit) ? derivative.unit(unit) : derivative; - } - private boolean usesFieldRef() { return fieldReference != null; } @@ -1792,16 +1810,36 @@ public class ArithmeticOperators { } } + /** + * Value object to represent an {@link AggregationExpression expression} that calculates the average rate of change + * within the specified window. + * + * @author Christoph Strobl + * @since 3.3 + */ public static class Derivative extends AbstractAggregationExpression { private Derivative(Object value) { super(value); } + /** + * Create a new instance of {@link Derivative} for the value stored at the given field holding a numeric value. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link Derivative}. + */ public static Derivative derivativeOf(String fieldReference) { return new Derivative(Collections.singletonMap("input", Fields.field(fieldReference))); } + /** + * Create a new instance of {@link Derivative} for the value provided by the given expression that resolves to a + * numeric value. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link Derivative}. + */ public static Derivative derivativeOf(AggregationExpression expression) { return new Derivative(Collections.singletonMap("input", expression)); } 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 index 1aab826a2..d57363d91 100644 --- 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 @@ -15,8 +15,8 @@ */ package org.springframework.data.mongodb.core.aggregation; -import static org.assertj.core.api.Assertions.*; import static org.springframework.data.mongodb.core.aggregation.ArithmeticOperators.*; +import static org.springframework.data.mongodb.test.util.Assertions.*; import java.util.Arrays; import java.util.Collections; @@ -66,16 +66,19 @@ class ArithmeticOperatorsUnitTests { assertThat( valueOf("miles").derivative(SetWindowFieldsOperation.WindowUnits.HOUR).toDocument(Aggregation.DEFAULT_CONTEXT)) - .isEqualTo(Document.parse("{ $derivative: { input: \"$miles\", unit: \"hour\" } }")); + .isEqualTo("{ $derivative: { input: \"$miles\", unit: \"hour\" } }"); } @Test // GH-3721 void rendersIntegral() { - assertThat(valueOf("kilowatts").integral().toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(Document.parse("{ $integral : { input : \"$kilowatts\" } }")); + assertThat(valueOf("kilowatts").integral().toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo("{ $integral : { input : \"$kilowatts\" } }"); } @Test // GH-3721 void rendersIntegralWithUnit() { - assertThat(valueOf("kilowatts").integral("hour").toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(Document.parse("{ $integral : { input : \"$kilowatts\", unit : \"hour\" } }")); + assertThat(valueOf("kilowatts").integral(SetWindowFieldsOperation.WindowUnits.HOUR) + .toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo("{ $integral : { input : \"$kilowatts\", unit : \"hour\" } }"); } } 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 a0fad05a3..0450e556c 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,8 +23,8 @@ import org.bson.Document; 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; -import org.springframework.lang.Nullable; /** * Unit tests for {@link SpelExpressionTransformer}. @@ -152,8 +152,8 @@ public class SpelExpressionTransformerUnitTests { @Test // DATAMONGO-774 void shouldRenderConsecutiveOperationsInComplexExpression() { - assertThat(transform("1 + 1 + (1 + 1 + 1) / q")).isEqualTo( - Document.parse("{ \"$add\" : [ 1 , 1 , { \"$divide\" : [ { \"$add\" : [ 1 , 1 , 1]} , \"$q\"]}]}")); + assertThat(transform("1 + 1 + (1 + 1 + 1) / q")) + .isEqualTo(Document.parse("{ \"$add\" : [ 1 , 1 , { \"$divide\" : [ { \"$add\" : [ 1 , 1 , 1]} , \"$q\"]}]}")); } @Test // DATAMONGO-774 @@ -189,8 +189,7 @@ public class SpelExpressionTransformerUnitTests { Person person = new Person(); person.setAge(10); - assertThat(transform("[0].age + a.c", person)) - .isEqualTo(Document.parse("{ \"$add\" : [ 10 , \"$a.c\"] }")); + assertThat(transform("[0].age + a.c", person)).isEqualTo(Document.parse("{ \"$add\" : [ 10 , \"$a.c\"] }")); } @Test // DATAMONGO-840 @@ -216,8 +215,7 @@ public class SpelExpressionTransformerUnitTests { @Test // DATAMONGO-1530 void shouldRenderMethodReferenceNodeSetEquals() { - assertThat(transform("setEquals(a, b)")) - .isEqualTo(Document.parse("{ \"$setEquals\" : [ \"$a\" , \"$b\"]}")); + assertThat(transform("setEquals(a, b)")).isEqualTo(Document.parse("{ \"$setEquals\" : [ \"$a\" , \"$b\"]}")); } @Test // DATAMONGO-1530 @@ -379,8 +377,7 @@ public class SpelExpressionTransformerUnitTests { @Test // DATAMONGO-1530 void shouldRenderMethodReferenceNodeConcat() { - assertThat(transform("concat(a, b, 'c')")) - .isEqualTo(Document.parse("{ \"$concat\" : [ \"$a\" , \"$b\" , \"c\"]}")); + assertThat(transform("concat(a, b, 'c')")).isEqualTo(Document.parse("{ \"$concat\" : [ \"$a\" , \"$b\" , \"c\"]}")); } @Test // DATAMONGO-1530 @@ -400,8 +397,7 @@ public class SpelExpressionTransformerUnitTests { @Test // DATAMONGO-1530 void shouldRenderMethodReferenceNodeStrCaseCmp() { - assertThat(transform("strcasecmp(a, b)")) - .isEqualTo(Document.parse("{ \"$strcasecmp\" : [ \"$a\" , \"$b\"]}")); + assertThat(transform("strcasecmp(a, b)")).isEqualTo(Document.parse("{ \"$strcasecmp\" : [ \"$a\" , \"$b\"]}")); } @Test // DATAMONGO-1530 @@ -411,8 +407,7 @@ public class SpelExpressionTransformerUnitTests { @Test // DATAMONGO-1530 void shouldRenderMethodReferenceNodeArrayElemAt() { - assertThat(transform("arrayElemAt(a, 10)")) - .isEqualTo(Document.parse("{ \"$arrayElemAt\" : [ \"$a\" , 10]}")); + assertThat(transform("arrayElemAt(a, 10)")).isEqualTo(Document.parse("{ \"$arrayElemAt\" : [ \"$a\" , 10]}")); } @Test // DATAMONGO-1530 @@ -511,15 +506,14 @@ public class SpelExpressionTransformerUnitTests { @Test // DATAMONGO-1530 void shouldRenderMethodReferenceDateToString() { - assertThat(transform("dateToString('%Y-%m-%d', $date)")).isEqualTo( - Document.parse("{ \"$dateToString\" : { \"format\" : \"%Y-%m-%d\" , \"date\" : \"$date\"}}")); + assertThat(transform("dateToString('%Y-%m-%d', $date)")) + .isEqualTo(Document.parse("{ \"$dateToString\" : { \"format\" : \"%Y-%m-%d\" , \"date\" : \"$date\"}}")); } @Test // DATAMONGO-1530 void shouldRenderMethodReferenceCond() { assertThat(transform("cond(qty > 250, 30, 20)")).isEqualTo( - Document - .parse("{ \"$cond\" : { \"if\" : { \"$gt\" : [ \"$qty\" , 250]} , \"then\" : 30 , \"else\" : 20}}")); + Document.parse("{ \"$cond\" : { \"if\" : { \"$gt\" : [ \"$qty\" , 250]} , \"then\" : 30 , \"else\" : 20}}")); } @Test // DATAMONGO-1530 @@ -633,8 +627,7 @@ public class SpelExpressionTransformerUnitTests { @Test // DATAMONGO-1530 void shouldRenderComplexOperationNodeAnd() { assertThat(transform("1+2 && concat(a, b) && true")).isEqualTo( - Document - .parse("{ \"$and\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); + Document.parse("{ \"$and\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); } @Test // DATAMONGO-1530 @@ -644,8 +637,7 @@ public class SpelExpressionTransformerUnitTests { @Test // DATAMONGO-1530 void shouldRenderComplexNotCorrectly() { - assertThat(transform("!(foo > 10)")) - .isEqualTo(Document.parse("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}")); + assertThat(transform("!(foo > 10)")).isEqualTo(Document.parse("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}")); } @Test // DATAMONGO-1548 @@ -951,12 +943,14 @@ public class SpelExpressionTransformerUnitTests { @Test // GH-3712 void shouldRenderCovariancePop() { - assertThat(transform("covariancePop(field1, field2)")).isEqualTo(Document.parse("{ \"$covariancePop\" : [\"$field1\", \"$field2\"]}")); + assertThat(transform("covariancePop(field1, field2)")) + .isEqualTo(Document.parse("{ \"$covariancePop\" : [\"$field1\", \"$field2\"]}")); } @Test // GH-3712 void shouldRenderCovarianceSamp() { - assertThat(transform("covarianceSamp(field1, field2)")).isEqualTo(Document.parse("{ \"$covarianceSamp\" : [\"$field1\", \"$field2\"]}")); + assertThat(transform("covarianceSamp(field1, field2)")) + .isEqualTo(Document.parse("{ \"$covarianceSamp\" : [\"$field1\", \"$field2\"]}")); } @Test // GH-3715 @@ -988,20 +982,21 @@ public class SpelExpressionTransformerUnitTests { .isEqualTo(Document.parse("{ $shift: { output: \"$quantity\", by: 1, default: \"Not available\" } }")); } - @Nullable @Test // GH-3716 void shouldRenderDerivative() { - assertThat(transform("derivative(miles, 'hour')")).isEqualTo(Document.parse("{ \"$derivative\" : { input : '$miles', unit : 'hour'} }")); + assertThat(transform("derivative(miles, 'hour')")) + .isEqualTo(Document.parse("{ \"$derivative\" : { input : '$miles', unit : 'hour'} }")); } @Test // GH-3721 - public void shouldRenderIntegral() { + void shouldRenderIntegral() { assertThat(transform("integral(field)")).isEqualTo(Document.parse("{ \"$integral\" : { \"input\" : \"$field\" }}")); } @Test // GH-3721 - public void shouldIntegralWithUnit() { - assertThat(transform("integral(field, 'hour')")).isEqualTo(Document.parse("{ \"$integral\" : { \"input\" : \"$field\", \"unit\" : \"hour\" }}")); + void shouldRenderIntegralWithUnit() { + assertThat(transform("integral(field, 'hour')")) + .isEqualTo(Document.parse("{ \"$integral\" : { \"input\" : \"$field\", \"unit\" : \"hour\" }}")); } private Object transform(String expression, Object... params) { diff --git a/src/main/asciidoc/reference/aggregation-framework.adoc b/src/main/asciidoc/reference/aggregation-framework.adoc index 2624e6c27..9b00811a7 100644 --- a/src/main/asciidoc/reference/aggregation-framework.adoc +++ b/src/main/asciidoc/reference/aggregation-framework.adoc @@ -85,7 +85,7 @@ At the time of this writing, we provide support for the following Aggregation Op | `addToSet`, `covariancePop`, `covarianceSamp`, `expMovingAvg`, `first`, `last`, `max`, `min`, `avg`, `push`, `sum`, `count` (+++*+++), `stdDevPop`, `stdDevSamp` | Arithmetic Aggregation Operators -| `abs`, `add` (+++*+++ via `plus`), `ceil`, `derivative`, `divide`, `exp`, `floor`, `ln`, `log`, `log10`, `mod`, `multiply`, `pow`, `round`, `sqrt`, `subtract` (+++*+++ via `minus`), `trunc` +| `abs`, `add` (+++*+++ via `plus`), `ceil`, `derivative`, `divide`, `exp`, `floor`, `integral`, `ln`, `log`, `log10`, `mod`, `multiply`, `pow`, `round`, `sqrt`, `subtract` (+++*+++ via `minus`), `trunc` | String Aggregation Operators | `concat`, `substr`, `toLower`, `toUpper`, `stcasecmp`, `indexOfBytes`, `indexOfCP`, `split`, `strLenBytes`, `strLenCP`, `substrCP`, `trim`, `ltrim`, `rtim`