Skip to content

Add missing aggregation operators and stages #4182

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
55ae80c
Prepare issue branch.
christophstrobl Sep 20, 2022
0c3d840
Add support for $bottom aggregation operator
christophstrobl Sep 22, 2022
10aedad
Add support for $bottomN aggregation operator
christophstrobl Sep 22, 2022
6994b86
Add support for $firstN aggregation operator
christophstrobl Sep 22, 2022
563811b
Add support for $lastN aggregation operator
christophstrobl Sep 22, 2022
332d4b9
Add support for $top & $topN aggregation operators
christophstrobl Sep 22, 2022
5e677eb
Polishing.
christophstrobl Sep 22, 2022
ae18218
Add support for $maxN aggregation operator
christophstrobl Sep 23, 2022
17ecc40
Add support for $minN aggregation operator
christophstrobl Sep 23, 2022
3d2c66d
Add support for $dateSubtract aggregation operator
christophstrobl Sep 23, 2022
0726c45
Add support for $dateTrunc aggregation operator
christophstrobl Sep 23, 2022
c3ec450
Add support for $getField aggregation operator
christophstrobl Sep 23, 2022
f29dc44
Add support for $setField aggregation operator
christophstrobl Sep 23, 2022
a790e3b
Fix errors after rebase
christophstrobl Sep 23, 2022
400db28
Add support for $sortArray aggregation operator
christophstrobl Sep 26, 2022
692bb76
Add support for $tsIncrement aggregation operator
christophstrobl Sep 26, 2022
ccac82b
Add support for $tsSecond aggregation operator
christophstrobl Sep 26, 2022
02337a9
Add support for $locf aggregation operator
christophstrobl Sep 26, 2022
df51f6b
Move Expr operator one level up
christophstrobl Sep 26, 2022
c62b965
Add support for $densify aggregation stage
christophstrobl Sep 26, 2022
173055a
Update aggregation reference documentation
christophstrobl Sep 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.x-GH-4139-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.x-GH-4139-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.x-GH-4139-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.x-GH-4139-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.bson.Document;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

Expand Down Expand Up @@ -68,8 +73,24 @@ private Object unpack(Object value, AggregationOperationContext context) {
return ((AggregationExpression) value).toDocument(context);
}

if (value instanceof Field) {
return context.getReference((Field) value).toString();
if (value instanceof Field field) {
return context.getReference(field).toString();
}

if (value instanceof Fields fields) {
return fields.asList().stream().map(it -> unpack(it, context)).collect(Collectors.toList());
}

if (value instanceof Sort sort) {

Document sortDoc = new Document();
for (Order order : sort) {

// Check reference
FieldReference reference = context.getReference(order.getProperty());
sortDoc.put(reference.getRaw(), order.isAscending() ? 1 : -1);
}
return sortDoc;
}

if (value instanceof List) {
Expand Down Expand Up @@ -134,9 +155,40 @@ protected Map<String, Object> append(String key, Object value) {

Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map");

Map<String, Object> clone = new LinkedHashMap<>((java.util.Map) this.value);
return append((Map<String, Object>) this.value, key, value);
}

private Map<String, Object> append(Map<String, Object> existing, String key, Object value) {

Map<String, Object> clone = new LinkedHashMap<>(existing);
clone.put(key, value);
return clone;
}

protected Map<String, Object> appendTo(String key, Object value) {

Assert.isInstanceOf(Map.class, this.value, "Value must be a type of Map");

if (this.value instanceof Map map) {

Map<String, Object> target = new HashMap<>(map);
if (!target.containsKey(key)) {
target.put(key, value);
return target;
}
target.computeIfPresent(key, (k, v) -> {

if (v instanceof List<?> list) {
List<Object> targetList = new ArrayList<>(list);
targetList.add(value);
return targetList;
}
return Arrays.asList(v, value);
});
return target;
}
throw new IllegalStateException(
String.format("Cannot append value to %s type", ObjectUtils.nullSafeClassName(this.value)));

}

Expand Down Expand Up @@ -227,6 +279,10 @@ protected <T> T get(Object key) {
return (T) ((Map<String, Object>) this.value).get(key);
}

protected boolean isArgumentMap() {
return this.value instanceof Map;
}

/**
* Get the argument map.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ public Max max() {
return usesFieldRef() ? Max.maxOf(fieldReference) : Max.maxOf(expression);
}

/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the
* requested number of maximum values.
*
* @return new instance of {@link Max}.
* @since 4.0
*/
public Max max(int numberOfResults) {
return max().limit(numberOfResults);
}

/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the
* minimum value.
Expand All @@ -122,6 +133,17 @@ public Min min() {
return usesFieldRef() ? Min.minOf(fieldReference) : Min.minOf(expression);
}

/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and returns the
* requested number of maximum values.
*
* @return new instance of {@link Max}.
* @since 4.0
*/
public Min min(int numberOfResults) {
return min().limit(numberOfResults);
}

/**
* Creates new {@link AggregationExpression} that takes the associated numeric value expression and calculates the
* population standard deviation of the input values.
Expand Down Expand Up @@ -441,7 +463,7 @@ private Max(Object value) {

@Override
protected String getMongoMethod() {
return "$max";
return contains("n") ? "$maxN" : "$max";
}

/**
Expand All @@ -453,7 +475,7 @@ protected String getMongoMethod() {
public static Max maxOf(String fieldReference) {

Assert.notNull(fieldReference, "FieldReference must not be null");
return new Max(asFields(fieldReference));
return new Max(Collections.singletonMap("input", Fields.field(fieldReference)));
}

/**
Expand All @@ -465,7 +487,7 @@ public static Max maxOf(String fieldReference) {
public static Max maxOf(AggregationExpression expression) {

Assert.notNull(expression, "Expression must not be null");
return new Max(Collections.singletonList(expression));
return new Max(Collections.singletonMap("input", expression));
}

/**
Expand All @@ -478,7 +500,7 @@ public static Max maxOf(AggregationExpression expression) {
public Max and(String fieldReference) {

Assert.notNull(fieldReference, "FieldReference must not be null");
return new Max(append(Fields.field(fieldReference)));
return new Max(appendTo("input", Fields.field(fieldReference)));
}

/**
Expand All @@ -491,7 +513,26 @@ public Max and(String fieldReference) {
public Max and(AggregationExpression expression) {

Assert.notNull(expression, "Expression must not be null");
return new Max(append(expression));
return new Max(appendTo("input", expression));
}

/**
* Creates new {@link Max} that returns the given number of maxmimum values ({@literal $maxN}).
* <strong>NOTE</strong>: Cannot be used with more than one {@literal input} value.
*
* @param numberOfResults
* @return new instance of {@link Max}.
*/
public Max limit(int numberOfResults) {
return new Max(append("n", numberOfResults));
}

@Override
public Document toDocument(AggregationOperationContext context) {
if (get("n") == null) {
return toDocument(get("input"), context);
}
return super.toDocument(context);
}

@Override
Expand Down Expand Up @@ -521,7 +562,7 @@ private Min(Object value) {

@Override
protected String getMongoMethod() {
return "$min";
return contains("n") ? "$minN" : "$min";
}

/**
Expand All @@ -533,7 +574,7 @@ protected String getMongoMethod() {
public static Min minOf(String fieldReference) {

Assert.notNull(fieldReference, "FieldReference must not be null");
return new Min(asFields(fieldReference));
return new Min(Collections.singletonMap("input", Fields.field(fieldReference)));
}

/**
Expand All @@ -545,7 +586,7 @@ public static Min minOf(String fieldReference) {
public static Min minOf(AggregationExpression expression) {

Assert.notNull(expression, "Expression must not be null");
return new Min(Collections.singletonList(expression));
return new Min(Collections.singletonMap("input", expression));
}

/**
Expand All @@ -558,7 +599,7 @@ public static Min minOf(AggregationExpression expression) {
public Min and(String fieldReference) {

Assert.notNull(fieldReference, "FieldReference must not be null");
return new Min(append(Fields.field(fieldReference)));
return new Min(appendTo("input", Fields.field(fieldReference)));
}

/**
Expand All @@ -571,7 +612,27 @@ public Min and(String fieldReference) {
public Min and(AggregationExpression expression) {

Assert.notNull(expression, "Expression must not be null");
return new Min(append(expression));
return new Min(appendTo("input", expression));
}

/**
* Creates new {@link Min} that returns the given number of minimum values ({@literal $minN}).
* <strong>NOTE</strong>: Cannot be used with more than one {@literal input} value.
*
* @param numberOfResults
* @return new instance of {@link Min}.
*/
public Min limit(int numberOfResults) {
return new Min(append("n", numberOfResults));
}

@Override
public Document toDocument(AggregationOperationContext context) {

if (get("n") == null) {
return toDocument(get("input"), context);
}
return super.toDocument(context);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import org.bson.Document;
import org.springframework.data.domain.Range;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Filter.AsBuilder;
import org.springframework.data.mongodb.core.aggregation.ArrayOperators.Reduce.PropertyExpression;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
Expand Down Expand Up @@ -315,6 +316,21 @@ public ArrayOperatorFactory.ReduceInitialValueBuilder reduce(PropertyExpression.
.withInitialValue(initialValue).reduce(expressions);
}

/**
* Creates new {@link AggregationExpression} that takes the associated array and sorts it by the given {@link Sort order}.
*
* @return new instance of {@link SortArray}.
* @since 4.0
*/
public SortArray sort(Sort sort) {

if (usesFieldRef()) {
return SortArray.sortArrayOf(fieldReference).by(sort);
}

return (usesExpression() ? SortArray.sortArrayOf(expression) : SortArray.sortArray(values)).by(sort);
}

/**
* Creates new {@link AggregationExpression} that transposes an array of input arrays so that the first element of
* the output array would be an array containing, the first element of the first input array, the first element of
Expand Down Expand Up @@ -1915,4 +1931,66 @@ protected String getMongoMethod() {
return "$last";
}
}

/**
* {@link AggregationExpression} for {@code $sortArray} that sorts elements in an array. <br />
*
* @author Christoph Strobl
* @since 4.0
*/
public static class SortArray extends AbstractAggregationExpression {

private SortArray(Object value) {
super(value);
}

/**
* Returns the given array.
*
* @param array must not be {@literal null}.
* @return new instance of {@link SortArray}.
*/
public static SortArray sortArray(Object array) {
return new SortArray(Collections.singletonMap("input", array));
}

/**
* Sorts the elements in the array pointed to by the given {@link Field field reference}.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link SortArray}.
*/
public static SortArray sortArrayOf(String fieldReference) {
return sortArray(Fields.field(fieldReference));
}

/**
* Sorts the elements of the array computed buy the given {@link AggregationExpression expression}.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link SortArray}.
*/
public static SortArray sortArrayOf(AggregationExpression expression) {
return sortArray(expression);
}

/**
* Set the order to put elements in.
*
* @param sort must not be {@literal null}.
* @return new instance of {@link SortArray}.
*/
public SortArray by(Sort sort) {
return new SortArray(append("sortBy", sort));
}

/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.AbstractAggregationExpression#getMongoMethod()
*/
@Override
protected String getMongoMethod() {
return "$sortArray";
}
}
}
Loading