From 9348794fcc3fc56e69ce89964a149c8fd73dba9f Mon Sep 17 00:00:00 2001
From: Julia <5765049+sxhinzvc@users.noreply.github.com>
Date: Mon, 25 Sep 2023 12:41:13 -0400
Subject: [PATCH 1/3] 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 366786fc6d..3bd4d68c6a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.2.0-SNAPSHOT
+ 4.2.x-4472-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml
index 2de4b6b635..636e20872e 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
- 4.2.0-SNAPSHOT
+ 4.2.x-4472-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 41b81f9aa6..d28a8e16c5 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -15,7 +15,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.2.0-SNAPSHOT
+ 4.2.x-4472-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index dc07f13ccc..91afc435ca 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 4.2.0-SNAPSHOT
+ 4.2.x-4472-SNAPSHOT
../pom.xml
From 8c123844cd1fb2b5f9bb0f7e8986fa2f69caa4d6 Mon Sep 17 00:00:00 2001
From: Julia <5765049+sxhinzvc@users.noreply.github.com>
Date: Tue, 26 Sep 2023 10:00:52 -0400
Subject: [PATCH 2/3] Add support for $median aggregation operator.
Closes #4472
---
.../aggregation/AccumulatorOperators.java | 84 +++++++++++++++++++
.../core/aggregation/ArithmeticOperators.java | 15 ++++
.../AccumulatorOperatorsUnitTests.java | 20 +++++
.../core/aggregation/AggregationTests.java | 43 ++++++++--
.../ArithmeticOperatorsUnitTests.java | 2 +-
.../ProjectionOperationUnitTests.java | 16 ++++
6 files changed, 173 insertions(+), 7 deletions(-)
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java
index a69555c4da..82375d27cb 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperators.java
@@ -265,6 +265,16 @@ public Percentile percentile(Double... percentages) {
return percentile.percentages(percentages);
}
+ /**
+ * Creates new {@link AggregationExpression} that calculates the median of the associated numeric value expression.
+ *
+ * @return new instance of {@link Median}.
+ * @since 4.2
+ */
+ public Median median() {
+ return usesFieldRef() ? Median.medianOf(fieldReference) : Median.medianOf(expression);
+ }
+
private boolean usesFieldRef() {
return fieldReference != null;
}
@@ -1082,4 +1092,78 @@ protected String getMongoMethod() {
return "$percentile";
}
}
+
+ /**
+ * {@link AggregationExpression} for {@code $median}.
+ *
+ * @author Julia Lee
+ * @since 4.2
+ */
+ public static class Median extends AbstractAggregationExpression {
+
+ private Median(Object value) {
+ super(value);
+ }
+
+ /**
+ * Creates new {@link Median}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return new instance of {@link Median}.
+ */
+ public static Median medianOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null");
+ Map fields = new HashMap<>();
+ fields.put("input", Fields.field(fieldReference));
+ fields.put("method", "approximate");
+ return new Median(fields);
+ }
+
+ /**
+ * Creates new {@link Median}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link Median}.
+ */
+ public static Median medianOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null");
+ Map fields = new HashMap<>();
+ fields.put("input", expression);
+ fields.put("method", "approximate");
+ return new Median(fields);
+ }
+
+ /**
+ * Creates new {@link Median} with all previously added inputs appending the given one.
+ * NOTE: Only possible in {@code $project} stage.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return new instance of {@link Median}.
+ */
+ public Median and(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null");
+ return new Median(appendTo("input", Fields.field(fieldReference)));
+ }
+
+ /**
+ * Creates new {@link Median} with all previously added inputs appending the given one.
+ * NOTE: Only possible in {@code $project} stage.
+ *
+ * @param expression must not be {@literal null}.
+ * @return new instance of {@link Median}.
+ */
+ public Median and(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null");
+ return new Median(appendTo("input", expression));
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$median";
+ }
+ }
}
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 d985e3b7b4..8d665dade3 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
@@ -24,6 +24,7 @@
import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.CovariancePop;
import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.CovarianceSamp;
import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Max;
+import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Median;
import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Min;
import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Percentile;
import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.StdDevPop;
@@ -31,6 +32,8 @@
import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators.Sum;
import org.springframework.data.mongodb.core.aggregation.SetWindowFieldsOperation.WindowUnit;
import org.springframework.data.mongodb.core.aggregation.SetWindowFieldsOperation.WindowUnits;
+import org.springframework.data.mongodb.core.aggregation.SetWindowFieldsOperation.WindowUnit;
+import org.springframework.data.mongodb.core.aggregation.SetWindowFieldsOperation.WindowUnits;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -948,6 +951,18 @@ public Percentile percentile(Double... percentages) {
return percentile.percentages(percentages);
}
+ /**
+ * Creates new {@link AggregationExpression} that calculates the requested percentile(s) of the
+ * numeric value.
+ *
+ * @return new instance of {@link Median}.
+ * @since 4.2
+ */
+ public Median median() {
+ return usesFieldRef() ? AccumulatorOperators.Median.medianOf(fieldReference)
+ : AccumulatorOperators.Median.medianOf(expression);
+ }
+
private boolean usesFieldRef() {
return fieldReference != null;
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperatorsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperatorsUnitTests.java
index a43b0f8620..3d7f770808 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperatorsUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AccumulatorOperatorsUnitTests.java
@@ -132,6 +132,26 @@ void rendersPercentileWithExpression() {
.isEqualTo(Document.parse("{ $percentile: { input: [\"$scoreOne\", {\"$sum\": \"$scoreTwo\"}], method: \"approximate\", p: [0.1, 0.2] } }"));
}
+ @Test // GH-4472
+ void rendersMedianWithFieldReference() {
+
+ assertThat(valueOf("score").median().toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo(Document.parse("{ $median: { input: \"$score\", method: \"approximate\" } }"));
+
+ assertThat(valueOf("score").median().and("scoreTwo").toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo(Document.parse("{ $median: { input: [\"$score\", \"$scoreTwo\"], method: \"approximate\" } }"));
+ }
+
+ @Test // GH-4472
+ void rendersMedianWithExpression() {
+
+ assertThat(valueOf(Sum.sumOf("score")).median().toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo(Document.parse("{ $median: { input: {\"$sum\": \"$score\"}, method: \"approximate\" } }"));
+
+ assertThat(valueOf("scoreOne").median().and(Sum.sumOf("scoreTwo")).toDocument(Aggregation.DEFAULT_CONTEXT))
+ .isEqualTo(Document.parse("{ $median: { input: [\"$scoreOne\", {\"$sum\": \"$scoreTwo\"}], method: \"approximate\" } }"));
+ }
+
static class Jedi {
String name;
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
index 5025d7fdce..ea4b218c45 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
@@ -1897,19 +1897,44 @@ void facetShouldCreateFacets() {
@EnableIfMongoServerVersion(isGreaterThanEqual = "7.0")
void percentileShouldBeAppliedCorrectly() {
- mongoTemplate.insert(new DATAMONGO788(15, 16));
- mongoTemplate.insert(new DATAMONGO788(17, 18));
+ DATAMONGO788 objectToSave = new DATAMONGO788(62, 81, 80);
+ DATAMONGO788 objectToSave2 = new DATAMONGO788(60, 83, 79);
+
+ mongoTemplate.insert(objectToSave);
+ mongoTemplate.insert(objectToSave2);
Aggregation agg = Aggregation.newAggregation(
- project().and(ArithmeticOperators.valueOf("x").percentile(0.9).and("y"))
- .as("ninetiethPercentile"));
+ project().and(ArithmeticOperators.valueOf("x").percentile(0.9, 0.4).and("y").and("xField"))
+ .as("percentileValues"));
AggregationResults result = mongoTemplate.aggregate(agg, DATAMONGO788.class, Document.class);
// MongoDB server returns $percentile as an array of doubles
List rawResults = (List) result.getRawResults().get("results");
- assertThat((List