From 82b33331fcc998cbd29421e9a6f1d59f3a7fd0a2 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 22 Jul 2021 14:08:06 +0200 Subject: [PATCH] Add support for `$derivative` aggregation operator. Closes: #3716 Original pull request: #3742. --- .../core/aggregation/ArithmeticOperators.java | 55 +++++++++++++++++++ .../core/spel/MethodReferenceNode.java | 1 + .../ArithmeticOperatorsUnitTests.java | 7 +++ .../SpelExpressionTransformerUnitTests.java | 5 ++ 4 files changed, 68 insertions(+) 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 b27e54d29..fe54f2434 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 @@ -26,7 +26,9 @@ import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Mi import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.StdDevPop; import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.StdDevSamp; import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Sum; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Gateway to {@literal Arithmetic} aggregation operations that perform math operations on numbers. @@ -591,6 +593,31 @@ 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(null); + } + + /** + * 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; } @@ -1724,4 +1751,32 @@ public class ArithmeticOperators { return "$round"; } } + + public static class Derivative extends AbstractAggregationExpression { + + private Derivative(Object value) { + super(value); + } + + public static Derivative derivativeOf(String fieldReference) { + return new Derivative(Collections.singletonMap("input", Fields.field(fieldReference))); + } + + public static Derivative derivativeOf(AggregationExpression expression) { + return new Derivative(Collections.singletonMap("input", expression)); + } + + public static Derivative derivativeOfValue(Number value) { + return new Derivative(Collections.singletonMap("input", value)); + } + + public Derivative unit(String unit) { + return new Derivative(append("unit", unit)); + } + + @Override + protected String getMongoMethod() { + return "$derivative"; + } + } } 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 4052b2cba..928869e93 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 @@ -91,6 +91,7 @@ public class MethodReferenceNode extends ExpressionNode { map.put("subtract", arrayArgRef().forOperator("$subtract")); map.put("trunc", singleArgRef().forOperator("$trunc")); map.put("round", arrayArgRef().forOperator("$round")); + map.put("derivative", mapArgRef().forOperator("$derivative").mappingParametersTo("input", "unit")); // 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 index 940a31523..b4f3cdadb 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 @@ -59,4 +59,11 @@ public class ArithmeticOperatorsUnitTests { .toDocument(Aggregation.DEFAULT_CONTEXT)) .isEqualTo(new Document("$round", Arrays.asList("$field", new Document("$first", "$source")))); } + + @Test // GH-3716 + void rendersDerivativeCorrectly() { + + assertThat(valueOf("miles").derivative("hour").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo(Document.parse("{ $derivative: { input: \"$miles\", 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 ee5581801..2653c52f2 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 @@ -989,6 +989,11 @@ public class SpelExpressionTransformerUnitTests { } @Nullable + @Test // GH-3716 + void shouldRenderDerivative() { + assertThat(transform("derivative(miles, 'hour')")).isEqualTo(Document.parse("{ \"$derivative\" : { input : '$miles', unit : 'hour'} }")); + } + 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);