Skip to content

Commit c3f9af0

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-1540 - Add support for $map (aggregation).
We now support $map operator in aggregation. Original pull request: #420.
1 parent 655a1e0 commit c3f9af0

File tree

3 files changed

+214
-9
lines changed

3 files changed

+214
-9
lines changed

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

Lines changed: 180 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
import java.util.Collections;
2121
import java.util.LinkedHashMap;
2222
import java.util.List;
23-
import java.util.Map;
2423

24+
import com.mongodb.BasicDBObject;
25+
import com.mongodb.DBObject;
2526
import org.bson.Document;
2627
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder;
28+
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Map.ArrayOfBuilder;
2729
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
2830
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
2931
import org.springframework.util.Assert;
@@ -1779,6 +1781,24 @@ private boolean usesFieldRef() {
17791781
}
17801782
}
17811783

1784+
/**
1785+
* Gateway to {@literal Date} aggregation operations.
1786+
*
1787+
* @author Christoph Strobl
1788+
*/
1789+
class VariableOperators {
1790+
1791+
/**
1792+
* Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array
1793+
* and returns an array with the applied results.
1794+
*
1795+
* @return
1796+
*/
1797+
public static ArrayOfBuilder map() {
1798+
return Map.map();
1799+
}
1800+
}
1801+
17821802
/**
17831803
* @author Christoph Strobl
17841804
*/
@@ -1807,10 +1827,10 @@ public Document toDocument(Object value, AggregationOperationContext context) {
18071827
args.add(unpack(val, context));
18081828
}
18091829
valueToUse = args;
1810-
} else if (value instanceof Map) {
1830+
} else if (value instanceof java.util.Map) {
18111831

18121832
Document dbo = new Document();
1813-
for (Map.Entry<String, Object> entry : ((Map<String, Object>) value).entrySet()) {
1833+
for (java.util.Map.Entry<String, Object> entry : ((java.util.Map<String, Object>) value).entrySet()) {
18141834
dbo.put(entry.getKey(), unpack(entry.getValue(), context));
18151835
}
18161836
valueToUse = dbo;
@@ -1864,10 +1884,10 @@ protected List<Object> append(Object value) {
18641884

18651885
protected Object append(String key, Object value) {
18661886

1867-
if (!(value instanceof Map)) {
1887+
if (!(value instanceof java.util.Map)) {
18681888
throw new IllegalArgumentException("o_O");
18691889
}
1870-
Map<String, Object> clone = new LinkedHashMap<String, Object>((Map<String, Object>) value);
1890+
java.util.Map<String, Object> clone = new LinkedHashMap<String, Object>((java.util.Map<String, Object>) value);
18711891
clone.put(key, value);
18721892
return clone;
18731893

@@ -2342,6 +2362,7 @@ public static Abs absoluteValueOf(AggregationExpression expression) {
23422362
Assert.notNull(expression, "Expression must not be null!");
23432363
return new Abs(expression);
23442364
}
2365+
23452366
/**
23462367
* Creates new {@link Abs}.
23472368
*
@@ -2493,7 +2514,6 @@ protected String getMongoMethod() {
24932514
return "$divide";
24942515
}
24952516

2496-
24972517
/**
24982518
* Creates new {@link Divide}.
24992519
*
@@ -4507,9 +4527,9 @@ public DateToString toString(String format) {
45074527
};
45084528
}
45094529

4510-
private static Map<String, Object> argumentMap(Object date, String format) {
4530+
private static java.util.Map<String, Object> argumentMap(Object date, String format) {
45114531

4512-
Map<String, Object> args = new LinkedHashMap<String, Object>(2);
4532+
java.util.Map<String, Object> args = new LinkedHashMap<String, Object>(2);
45134533
args.put("format", format);
45144534
args.put("date", date);
45154535
return args;
@@ -5713,4 +5733,156 @@ public static Not not(AggregationExpression expression) {
57135733
}
57145734
}
57155735

5736+
/**
5737+
* {@link AggregationExpression} for {@code $map}.
5738+
*/
5739+
class Map implements AggregationExpression {
5740+
5741+
private Object sourceArray;
5742+
private String itemVariableName;
5743+
private AggregationExpression functionToApply;
5744+
5745+
private Map(Object sourceArray, String itemVariableName, AggregationExpression functionToApply) {
5746+
5747+
Assert.notNull(sourceArray, "SourceArray must not be null!");
5748+
Assert.notNull(itemVariableName, "ItemVariableName must not be null!");
5749+
Assert.notNull(functionToApply, "FunctionToApply must not be null!");
5750+
5751+
this.sourceArray = sourceArray;
5752+
this.itemVariableName = itemVariableName;
5753+
this.functionToApply = functionToApply;
5754+
}
5755+
5756+
/**
5757+
* Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array
5758+
* and returns an array with the applied results.
5759+
*
5760+
* @return
5761+
*/
5762+
static ArrayOfBuilder map() {
5763+
5764+
return new ArrayOfBuilder() {
5765+
5766+
@Override
5767+
public AsBuilder itemsOf(final String fieldReference) {
5768+
5769+
return new AsBuilder() {
5770+
5771+
@Override
5772+
public FunctionBuilder as(final String variableName) {
5773+
5774+
return new FunctionBuilder() {
5775+
5776+
@Override
5777+
public Map andApply(final AggregationExpression expression) {
5778+
return new Map(Fields.field(fieldReference), variableName, expression);
5779+
}
5780+
};
5781+
}
5782+
};
5783+
}
5784+
5785+
@Override
5786+
public AsBuilder itemsOf(final AggregationExpression source) {
5787+
5788+
return new AsBuilder() {
5789+
5790+
@Override
5791+
public FunctionBuilder as(final String variableName) {
5792+
5793+
return new FunctionBuilder() {
5794+
5795+
@Override
5796+
public Map andApply(final AggregationExpression expression) {
5797+
return new Map(source, variableName, expression);
5798+
}
5799+
};
5800+
}
5801+
};
5802+
}
5803+
};
5804+
};
5805+
5806+
@Override
5807+
public Document toDocument(final AggregationOperationContext context) {
5808+
5809+
return toMap(new ExposedFieldsAggregationOperationContext(
5810+
ExposedFields.synthetic(Fields.fields(itemVariableName)), context) {
5811+
5812+
@Override
5813+
public FieldReference getReference(Field field) {
5814+
5815+
FieldReference ref = null;
5816+
try {
5817+
ref = context.getReference(field);
5818+
} catch (Exception e) {
5819+
// just ignore that one.
5820+
}
5821+
return ref != null ? ref : super.getReference(field);
5822+
}
5823+
});
5824+
}
5825+
5826+
private Document toMap(AggregationOperationContext context) {
5827+
5828+
Document map = new Document();
5829+
5830+
Document input;
5831+
if (sourceArray instanceof Field) {
5832+
input = new Document("input", context.getReference((Field) sourceArray).toString());
5833+
} else {
5834+
input = new Document("input", ((AggregationExpression) sourceArray).toDocument(context));
5835+
}
5836+
5837+
map.putAll(context.getMappedObject(input));
5838+
map.put("as", itemVariableName);
5839+
map.put("in", functionToApply.toDocument(new NestedDelegatingExpressionAggregationOperationContext(context)));
5840+
5841+
return new Document("$map", map);
5842+
}
5843+
5844+
interface ArrayOfBuilder {
5845+
5846+
/**
5847+
* Set the field that resolves to an array on which to apply the {@link AggregationExpression}.
5848+
*
5849+
* @param fieldReference must not be {@literal null}.
5850+
* @return
5851+
*/
5852+
AsBuilder itemsOf(String fieldReference);
5853+
5854+
/**
5855+
* Set the {@link AggregationExpression} that results in an array on which to apply the
5856+
* {@link AggregationExpression}.
5857+
*
5858+
* @param expression must not be {@literal null}.
5859+
* @return
5860+
*/
5861+
AsBuilder itemsOf(AggregationExpression expression);
5862+
}
5863+
5864+
interface AsBuilder {
5865+
5866+
/**
5867+
* Define the {@literal variableName} for addressing items within the array.
5868+
*
5869+
* @param variableName must not be {@literal null}.
5870+
* @return
5871+
*/
5872+
FunctionBuilder as(String variableName);
5873+
}
5874+
5875+
interface FunctionBuilder {
5876+
5877+
/**
5878+
* Creates new {@link Map} that applies the given {@link AggregationExpression} to each item of the referenced
5879+
* array and returns an array with the applied results.
5880+
*
5881+
* @param expression must not be {@literal null}.
5882+
* @return
5883+
*/
5884+
Map andApply(AggregationExpression expression);
5885+
}
5886+
}
5887+
57165888
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
@Deprecated
3636
public enum AggregationFunctionExpressions {
3737

38-
SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT;
38+
SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT, ADD;
3939

4040
/**
4141
* Returns an {@link AggregationExpression} build from the current {@link Enum} name and the given parameters.

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.util.Arrays;
2626
import java.util.List;
2727

28+
import com.mongodb.DBObject;
29+
import com.mongodb.util.JSON;
2830
import org.bson.Document;
2931
import org.junit.Test;
3032
import org.springframework.data.mongodb.core.DocumentTestUtils;
@@ -36,6 +38,7 @@
3638
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators;
3739
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.SetOperators;
3840
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.StringOperators;
41+
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.VariableOperators;
3942
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder;
4043

4144
/**
@@ -1670,6 +1673,36 @@ public void shouldRenderNotAggregationExpression() {
16701673
assertThat(agg, is(Document.parse("{ $project: { result: { $not: [ { $gt: [ \"$qty\", 250 ] } ] } } }")));
16711674
}
16721675

1676+
/**
1677+
* @see DATAMONGO-784
1678+
*/
1679+
@Test
1680+
public void shouldRenderMapAggregationExpression() {
1681+
1682+
Document agg = Aggregation.project()
1683+
.and(VariableOperators.map().itemsOf("quizzes").as("grade")
1684+
.andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2)))
1685+
.as("adjustedGrades").toDocument(Aggregation.DEFAULT_CONTEXT);
1686+
1687+
assertThat(agg, is(Document.parse(
1688+
"{ $project:{ adjustedGrades:{ $map: { input: \"$quizzes\", as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}")));
1689+
}
1690+
1691+
/**
1692+
* @see DATAMONGO-784
1693+
*/
1694+
@Test
1695+
public void shouldRenderMapAggregationExpressionOnExpression() {
1696+
1697+
Document agg = Aggregation.project()
1698+
.and(VariableOperators.map().itemsOf(AggregationFunctionExpressions.SIZE.of("foo")).as("grade")
1699+
.andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2)))
1700+
.as("adjustedGrades").toDocument(Aggregation.DEFAULT_CONTEXT);
1701+
1702+
assertThat(agg, is(Document.parse(
1703+
"{ $project:{ adjustedGrades:{ $map: { input: { $size : [\"foo\"]}, as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}")));
1704+
}
1705+
16731706
private static Document exctractOperation(String field, Document fromProjectClause) {
16741707
return (Document) fromProjectClause.get(field);
16751708
}

0 commit comments

Comments
 (0)