Skip to content

DATAMONGO-1540 - Add support for $map (aggregation). #420

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 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1540-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
4 changes: 2 additions & 2 deletions spring-data-mongodb-cross-store/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1540-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down Expand Up @@ -48,7 +48,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1540-SNAPSHOT</version>
</dependency>

<dependency>
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 @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1540-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-log4j/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1540-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 @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>1.10.0.BUILD-SNAPSHOT</version>
<version>1.10.0.DATAMONGO-1540-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
Expand Down Expand Up @@ -1781,6 +1780,37 @@ private boolean usesFieldRef() {
}
}

/**
* Gateway to {@literal variable} aggregation operations.
*
* @author Christoph Strobl
* @author Mark Paluch
*/
class VariableOperators {

/**
* Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array
* and returns an array with the applied results.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
public static Map.AsBuilder mapItemsOf(String fieldReference) {
return Map.itemsOf(fieldReference);
}

/**
* Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array
* and returns an array with the applied results.
*
* @param expression must not be {@literal null}.
* @return
*/
public static Map.AsBuilder mapItemsOf(AggregationExpression expression) {
return Map.itemsOf(expression);
}
}

/**
* @author Christoph Strobl
*/
Expand Down Expand Up @@ -1809,10 +1839,10 @@ public DBObject toDbObject(Object value, AggregationOperationContext context) {
args.add(unpack(val, context));
}
valueToUse = args;
} else if (value instanceof Map) {
} else if (value instanceof java.util.Map) {

DBObject dbo = new BasicDBObject();
for (Map.Entry<String, Object> entry : ((Map<String, Object>) value).entrySet()) {
for (java.util.Map.Entry<String, Object> entry : ((java.util.Map<String, Object>) value).entrySet()) {
dbo.put(entry.getKey(), unpack(entry.getValue(), context));
}
valueToUse = dbo;
Expand Down Expand Up @@ -1866,10 +1896,10 @@ protected List<Object> append(Object value) {

protected Object append(String key, Object value) {

if (!(value instanceof Map)) {
if (!(value instanceof java.util.Map)) {
throw new IllegalArgumentException("o_O");
}
Map<String, Object> clone = new LinkedHashMap<String, Object>((Map<String, Object>) value);
java.util.Map<String, Object> clone = new LinkedHashMap<String, Object>((java.util.Map<String, Object>) value);
clone.put(key, value);
return clone;

Expand Down Expand Up @@ -2344,6 +2374,7 @@ public static Abs absoluteValueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
return new Abs(expression);
}

/**
* Creates new {@link Abs}.
*
Expand Down Expand Up @@ -2495,7 +2526,6 @@ protected String getMongoMethod() {
return "$divide";
}


/**
* Creates new {@link Divide}.
*
Expand Down Expand Up @@ -4390,7 +4420,7 @@ protected String getMongoMethod() {

/**
* Creates new {@link Second}.
*
*
* @param fieldReference must not be {@literal null}.
* @return
*/
Expand Down Expand Up @@ -4509,9 +4539,9 @@ public DateToString toString(String format) {
};
}

private static Map<String, Object> argumentMap(Object date, String format) {
private static java.util.Map<String, Object> argumentMap(Object date, String format) {

Map<String, Object> args = new LinkedHashMap<String, Object>(2);
java.util.Map<String, Object> args = new LinkedHashMap<String, Object>(2);
args.put("format", format);
args.put("date", date);
return args;
Expand Down Expand Up @@ -4705,7 +4735,7 @@ protected String getMongoMethod() {

/**
* Creates new {@link Max}.
*
*
* @param fieldReference must not be {@literal null}.
* @return
*/
Expand All @@ -4730,7 +4760,7 @@ public static Max maxOf(AggregationExpression expression) {
/**
* Creates new {@link Max} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
*
* @param fieldReference must not be {@literal null}.
* @return
*/
Expand Down Expand Up @@ -4809,7 +4839,7 @@ public static Min minOf(AggregationExpression expression) {
/**
* Creates new {@link Min} with all previously added arguments appending the given one. <br />
* <strong>NOTE:</strong> Only possible in {@code $project} stage.
*
*
* @param fieldReference must not be {@literal null}.
* @return
*/
Expand Down Expand Up @@ -4942,7 +4972,7 @@ protected String getMongoMethod() {

/**
* Creates new {@link StdDevSamp}.
*
*
* @param fieldReference must not be {@literal null}.
* @return
*/
Expand Down Expand Up @@ -5715,4 +5745,137 @@ public static Not not(AggregationExpression expression) {
}
}

/**
* {@link AggregationExpression} for {@code $map}.
*/
class Map implements AggregationExpression {

private Object sourceArray;
private String itemVariableName;
private AggregationExpression functionToApply;

private Map(Object sourceArray, String itemVariableName, AggregationExpression functionToApply) {

Assert.notNull(sourceArray, "SourceArray must not be null!");
Assert.notNull(itemVariableName, "ItemVariableName must not be null!");
Assert.notNull(functionToApply, "FunctionToApply must not be null!");

this.sourceArray = sourceArray;
this.itemVariableName = itemVariableName;
this.functionToApply = functionToApply;
}

/**
* Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array
* and returns an array with the applied results.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
static AsBuilder itemsOf(final String fieldReference) {

return new AsBuilder() {

@Override
public FunctionBuilder as(final String variableName) {

return new FunctionBuilder() {

@Override
public Map andApply(final AggregationExpression expression) {
return new Map(Fields.field(fieldReference), variableName, expression);
}
};
}

};
};

/**
* Starts building new {@link Map} that applies an {@link AggregationExpression} to each item of a referenced array
* and returns an array with the applied results.
*
* @param source must not be {@literal null}.
* @return
*/
public static AsBuilder itemsOf(final AggregationExpression source) {

return new AsBuilder() {

@Override
public FunctionBuilder as(final String variableName) {

return new FunctionBuilder() {

@Override
public Map andApply(final AggregationExpression expression) {
return new Map(source, variableName, expression);
}
};
}
};
}

@Override
public DBObject toDbObject(final AggregationOperationContext context) {

return toMap(new ExposedFieldsAggregationOperationContext(
ExposedFields.synthetic(Fields.fields(itemVariableName)), context) {

@Override
public FieldReference getReference(Field field) {

FieldReference ref = null;
try {
ref = context.getReference(field);
} catch (Exception e) {
// just ignore that one.
}
return ref != null ? ref : super.getReference(field);
}
});
}

private DBObject toMap(AggregationOperationContext context) {

BasicDBObject map = new BasicDBObject();

BasicDBObject input;
if (sourceArray instanceof Field) {
input = new BasicDBObject("input", context.getReference((Field) sourceArray).toString());
} else {
input = new BasicDBObject("input", ((AggregationExpression) sourceArray).toDbObject(context));
}

map.putAll(context.getMappedObject(input));
map.put("as", itemVariableName);
map.put("in", functionToApply.toDbObject(new NestedDelegatingExpressionAggregationOperationContext(context)));

return new BasicDBObject("$map", map);
}

interface AsBuilder {

/**
* Define the {@literal variableName} for addressing items within the array.
*
* @param variableName must not be {@literal null}.
* @return
*/
FunctionBuilder as(String variableName);
}

interface FunctionBuilder {

/**
* Creates new {@link Map} that applies the given {@link AggregationExpression} to each item of the referenced
* array and returns an array with the applied results.
*
* @param expression must not be {@literal null}.
* @return
*/
Map andApply(AggregationExpression expression);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
@Deprecated
public enum AggregationFunctionExpressions {

SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT;
SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT, ADD;

/**
* Returns an {@link AggregationExpression} build from the current {@link Enum} name and the given parameters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.SetOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.StringOperators;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.VariableOperators;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder;

import com.mongodb.BasicDBObject;
Expand Down Expand Up @@ -1673,6 +1674,36 @@ public void shouldRenderNotAggregationExpression() {
assertThat(agg, is(JSON.parse("{ $project: { result: { $not: [ { $gt: [ \"$qty\", 250 ] } ] } } }")));
}

/**
* @see DATAMONGO-1540
*/
@Test
public void shouldRenderMapAggregationExpression() {

DBObject agg = Aggregation.project()
.and(VariableOperators.mapItemsOf("quizzes").as("grade")
.andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2)))
.as("adjustedGrades").toDBObject(Aggregation.DEFAULT_CONTEXT);

assertThat(agg, is(JSON.parse(
"{ $project:{ adjustedGrades:{ $map: { input: \"$quizzes\", as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}")));
}

/**
* @see DATAMONGO-1540
*/
@Test
public void shouldRenderMapAggregationExpressionOnExpression() {

DBObject agg = Aggregation.project()
.and(VariableOperators.mapItemsOf(AggregationFunctionExpressions.SIZE.of("foo")).as("grade")
.andApply(AggregationFunctionExpressions.ADD.of(field("grade"), 2)))
.as("adjustedGrades").toDBObject(Aggregation.DEFAULT_CONTEXT);

assertThat(agg, is(JSON.parse(
"{ $project:{ adjustedGrades:{ $map: { input: { $size : [\"foo\"]}, as: \"grade\",in: { $add: [ \"$$grade\", 2 ] }}}}}")));
}

private static DBObject exctractOperation(String field, DBObject fromProjectClause) {
return (DBObject) fromProjectClause.get(field);
}
Expand Down
Loading