Skip to content

Commit f3e067f

Browse files
christophstroblmp911de
authored andcommitted
Add support for $expMovingAvg aggregation operator.
The SpEL support for this one is missing due to the differing argument map (N, alpha). Closes: #3718 Original pull request: #3744.
1 parent dbfd4e5 commit f3e067f

File tree

3 files changed

+131
-6
lines changed

3 files changed

+131
-6
lines changed

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

+111
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,61 @@ private CovarianceSamp covarianceSamp() {
199199
: CovarianceSamp.covarianceSampOf(expression);
200200
}
201201

202+
/**
203+
* Creates new {@link ExpMovingAvgBuilder} that to build {@link AggregationExpression expMovingAvg} that calculates
204+
* the exponential moving average of numeric values
205+
*
206+
* @return new instance of {@link ExpMovingAvg}.
207+
* @since 3.3
208+
*/
209+
public ExpMovingAvgBuilder expMovingAvg() {
210+
211+
ExpMovingAvg expMovingAvg = usesFieldRef() ? ExpMovingAvg.expMovingAvgOf(fieldReference)
212+
: ExpMovingAvg.expMovingAvgOf(expression);
213+
return new ExpMovingAvgBuilder() {
214+
215+
@Override
216+
public ExpMovingAvg historicalDocuments(int numberOfHistoricalDocuments) {
217+
return expMovingAvg.n(numberOfHistoricalDocuments);
218+
}
219+
220+
@Override
221+
public ExpMovingAvg alpha(double exponentialDecayValue) {
222+
return expMovingAvg.alpha(exponentialDecayValue);
223+
}
224+
};
225+
}
226+
202227
private boolean usesFieldRef() {
203228
return fieldReference != null;
204229
}
205230
}
206231

232+
/**
233+
* Builder for {@link ExpMovingAvg}.
234+
*
235+
* @since 3.3
236+
*/
237+
public interface ExpMovingAvgBuilder {
238+
239+
/**
240+
* Define the number of historical documents with significant mathematical weight.
241+
*
242+
* @param numberOfHistoricalDocuments
243+
* @return new instance of {@link ExpMovingAvg}.
244+
*/
245+
ExpMovingAvg historicalDocuments(int numberOfHistoricalDocuments);
246+
247+
/**
248+
* Define the exponential decay value.
249+
*
250+
* @param exponentialDecayValue
251+
* @return new instance of {@link ExpMovingAvg}.
252+
*/
253+
ExpMovingAvg alpha(double exponentialDecayValue);
254+
255+
}
256+
207257
/**
208258
* {@link AggregationExpression} for {@code $sum}.
209259
*
@@ -835,4 +885,65 @@ protected String getMongoMethod() {
835885
return "$covarianceSamp";
836886
}
837887
}
888+
889+
/**
890+
* {@link ExpMovingAvg} calculates the exponential moving average of numeric values.
891+
*
892+
* @author Christoph Strobl
893+
* @since 3.3
894+
*/
895+
public static class ExpMovingAvg extends AbstractAggregationExpression {
896+
897+
private ExpMovingAvg(Object value) {
898+
super(value);
899+
}
900+
901+
/**
902+
* Create a new {@link ExpMovingAvg} by defining the field holding the value to be used as input.
903+
*
904+
* @param fieldReference must not be {@literal null}.
905+
* @return new instance of {@link ExpMovingAvg}.
906+
*/
907+
public static ExpMovingAvg expMovingAvgOf(String fieldReference) {
908+
return new ExpMovingAvg(Collections.singletonMap("input", Fields.field(fieldReference)));
909+
}
910+
911+
/**
912+
* Create a new {@link ExpMovingAvg} by defining the {@link AggregationExpression expression} to compute the value
913+
* to be used as input.
914+
*
915+
* @param expression must not be {@literal null}.
916+
* @return new instance of {@link ExpMovingAvg}.
917+
*/
918+
public static ExpMovingAvg expMovingAvgOf(AggregationExpression expression) {
919+
return new ExpMovingAvg(Collections.singletonMap("input", expression));
920+
}
921+
922+
/**
923+
* Define the number of historical documents with significant mathematical weight. <br />
924+
* Specify either {@link #n(int) N} or {@link #alpha(double) aplha}. Not both!
925+
*
926+
* @param numberOfHistoricalDocuments
927+
* @return new instance of {@link ExpMovingAvg}.
928+
*/
929+
public ExpMovingAvg n/*umber of historical documents*/(int numberOfHistoricalDocuments) {
930+
return new ExpMovingAvg(append("N", numberOfHistoricalDocuments));
931+
}
932+
933+
/**
934+
* Define the exponential decay value. <br />
935+
* Specify either {@link #alpha(double) aplha} or {@link #n(int) N}. Not both!
936+
*
937+
* @param exponentialDecayValue
938+
* @return new instance of {@link ExpMovingAvg}.
939+
*/
940+
public ExpMovingAvg alpha(double exponentialDecayValue) {
941+
return new ExpMovingAvg(append("alpha", exponentialDecayValue));
942+
}
943+
944+
@Override
945+
protected String getMongoMethod() {
946+
return "$expMovingAvg";
947+
}
948+
}
838949
}

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

+19-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.mongodb.core.aggregation;
1717

1818
import static org.assertj.core.api.Assertions.*;
19+
import static org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.*;
1920

2021
import java.util.Arrays;
2122
import java.util.Date;
@@ -46,23 +47,37 @@ void rendersCovariancePopWithExpression() {
4647

4748
assertThat(AccumulatorOperators.valueOf(Year.yearOf("birthdate")).covariancePop("midichlorianCount")
4849
.toDocument(TestAggregationContext.contextFor(Jedi.class)))
49-
.isEqualTo(new Document("$covariancePop", Arrays.asList(new Document("$year", "$birthdate"), "$force")));
50+
.isEqualTo(new Document("$covariancePop", Arrays.asList(new Document("$year", "$birthdate"), "$force")));
5051
}
5152

5253
@Test // GH-3712
5354
void rendersCovarianceSampWithFieldReference() {
5455

5556
assertThat(AccumulatorOperators.valueOf("balance").covarianceSamp("midichlorianCount")
5657
.toDocument(TestAggregationContext.contextFor(Jedi.class)))
57-
.isEqualTo(new Document("$covarianceSamp", Arrays.asList("$balance", "$force")));
58+
.isEqualTo(new Document("$covarianceSamp", Arrays.asList("$balance", "$force")));
5859
}
5960

6061
@Test // GH-3712
6162
void rendersCovarianceSampWithExpression() {
6263

6364
assertThat(AccumulatorOperators.valueOf(Year.yearOf("birthdate")).covarianceSamp("midichlorianCount")
6465
.toDocument(TestAggregationContext.contextFor(Jedi.class)))
65-
.isEqualTo(new Document("$covarianceSamp", Arrays.asList(new Document("$year", "$birthdate"), "$force")));
66+
.isEqualTo(new Document("$covarianceSamp", Arrays.asList(new Document("$year", "$birthdate"), "$force")));
67+
}
68+
69+
@Test // GH-3718
70+
void rendersExpMovingAvgWithNumberOfHistoricDocuments() {
71+
72+
assertThat(valueOf("price").expMovingAvg().historicalDocuments(2).toDocument(Aggregation.DEFAULT_CONTEXT))
73+
.isEqualTo(Document.parse("{ $expMovingAvg: { input: \"$price\", N: 2 } }"));
74+
}
75+
76+
@Test // GH-3718
77+
void rendersExpMovingAvgWithAlpha() {
78+
79+
assertThat(valueOf("price").expMovingAvg().alpha(0.75).toDocument(Aggregation.DEFAULT_CONTEXT))
80+
.isEqualTo(Document.parse("{ $expMovingAvg: { input: \"$price\", alpha: 0.75 } }"));
6681
}
6782

6883
static class Jedi {
@@ -71,8 +86,7 @@ static class Jedi {
7186

7287
Date birthdate;
7388

74-
@Field("force")
75-
Integer midichlorianCount;
89+
@Field("force") Integer midichlorianCount;
7690

7791
Integer balance;
7892
}

src/main/asciidoc/reference/mongodb.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -2503,7 +2503,7 @@ At the time of this writing, we provide support for the following Aggregation Op
25032503
| `setEquals`, `setIntersection`, `setUnion`, `setDifference`, `setIsSubset`, `anyElementTrue`, `allElementsTrue`
25042504

25052505
| Group/Accumulator Aggregation Operators
2506-
| `addToSet`, `covariancePop`, `covarianceSamp`, `first`, `last`, `max`, `min`, `avg`, `push`, `sum`, `(*count)`, `stdDevPop`, `stdDevSamp`
2506+
| `addToSet`, `covariancePop`, `covarianceSamp`, `expMovingAvg`, `first`, `last`, `max`, `min`, `avg`, `push`, `sum`, `(*count)`, `stdDevPop`, `stdDevSamp`
25072507

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

0 commit comments

Comments
 (0)