DATAMONGO-1538 - Add support for $let to aggregation.
We now support $let in aggregation $project stage.
ExpressionVariable total = newExpressionVariable("total").forExpression(ADD.of(field("price"), field("tax")));
ExpressionVariable discounted = newExpressionVariable("discounted").forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D));
newAggregation(Sales.class,
project()
.and(define(total, discounted)
.andApply(MULTIPLY.of(field("total"), field("discounted"))))
.as("finalTotal"));
Original pull request: #417.
This commit is contained in:
committed by
Mark Paluch
parent
f512d8cb16
commit
696e53ff60
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -26,6 +27,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
@@ -1984,6 +1986,28 @@ public interface AggregationExpressions {
|
||||
public static Map.AsBuilder mapItemsOf(AggregationExpression expression) {
|
||||
return Map.itemsOf(expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a
|
||||
* nested {@link AggregationExpression}.
|
||||
*
|
||||
* @param variables must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Let.LetBuilder define(ExpressionVariable... variables) {
|
||||
return Let.define(variables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start creating new {@link Let} that allows definition of {@link ExpressionVariable} that can be used within a
|
||||
* nested {@link AggregationExpression}.
|
||||
*
|
||||
* @param variables must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Let.LetBuilder define(Collection<ExpressionVariable> variables) {
|
||||
return Let.define(variables);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6694,4 +6718,185 @@ public interface AggregationExpressions {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link AggregationExpression} for {@code $let} that binds {@link AggregationExpression} to variables for use in the
|
||||
* specified {@code in} expression, and returns the result of the expression.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
*/
|
||||
class Let implements AggregationExpression {
|
||||
|
||||
private final List<ExpressionVariable> vars;
|
||||
private final AggregationExpression expression;
|
||||
|
||||
private Let(List<ExpressionVariable> vars, AggregationExpression expression) {
|
||||
|
||||
this.vars = vars;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start creating new {@link Let} by defining the variables for {@code $vars}.
|
||||
*
|
||||
* @param variables must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static LetBuilder define(final Collection<ExpressionVariable> variables) {
|
||||
|
||||
Assert.notNull(variables, "Variables must not be null!");
|
||||
|
||||
return new LetBuilder() {
|
||||
@Override
|
||||
public Let andApply(final AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new Let(new ArrayList<ExpressionVariable>(variables), expression);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Start creating new {@link Let} by defining the variables for {@code $vars}.
|
||||
*
|
||||
* @param variables must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static LetBuilder define(final ExpressionVariable... variables) {
|
||||
|
||||
Assert.notNull(variables, "Variables must not be null!");
|
||||
|
||||
return new LetBuilder() {
|
||||
@Override
|
||||
public Let andApply(final AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new Let(Arrays.asList(variables), expression);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public interface LetBuilder {
|
||||
|
||||
/**
|
||||
* Define the {@link AggregationExpression} to evaluate.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Let andApply(AggregationExpression expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Document toDocument(final AggregationOperationContext context) {
|
||||
|
||||
return toLet(new ExposedFieldsAggregationOperationContext(
|
||||
ExposedFields.synthetic(Fields.fields(getVariableNames())), 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 String[] getVariableNames() {
|
||||
|
||||
String[] varNames = new String[this.vars.size()];
|
||||
for (int i = 0; i < this.vars.size(); i++) {
|
||||
varNames[i] = this.vars.get(i).variableName;
|
||||
}
|
||||
return varNames;
|
||||
}
|
||||
|
||||
private Document toLet(AggregationOperationContext context) {
|
||||
|
||||
Document letExpression = new Document();
|
||||
|
||||
Document mappedVars = new Document();
|
||||
for (ExpressionVariable var : this.vars) {
|
||||
mappedVars.putAll(getMappedVariable(var, context));
|
||||
}
|
||||
|
||||
letExpression.put("vars", mappedVars);
|
||||
letExpression.put("in", getMappedIn(context));
|
||||
|
||||
return new Document("$let", letExpression);
|
||||
}
|
||||
|
||||
private Document getMappedVariable(ExpressionVariable var, AggregationOperationContext context) {
|
||||
|
||||
return new Document(var.variableName, var.expression instanceof AggregationExpression
|
||||
? ((AggregationExpression) var.expression).toDocument(context) : var.expression);
|
||||
}
|
||||
|
||||
private Object getMappedIn(AggregationOperationContext context) {
|
||||
return expression.toDocument(new NestedDelegatingExpressionAggregationOperationContext(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public static class ExpressionVariable {
|
||||
|
||||
private final String variableName;
|
||||
private final Object expression;
|
||||
|
||||
/**
|
||||
* Creates new {@link ExpressionVariable}.
|
||||
*
|
||||
* @param variableName can be {@literal null}.
|
||||
* @param expression can be {@literal null}.
|
||||
*/
|
||||
private ExpressionVariable(String variableName, Object expression) {
|
||||
|
||||
this.variableName = variableName;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExpressionVariable} with given name.
|
||||
*
|
||||
* @param variableName must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public static ExpressionVariable newVariable(String variableName) {
|
||||
|
||||
Assert.notNull(variableName, "VariableName must not be null!");
|
||||
return new ExpressionVariable(variableName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExpressionVariable} with current name and given {@literal expression}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public ExpressionVariable forExpression(AggregationExpression expression) {
|
||||
|
||||
Assert.notNull(expression, "Expression must not be null!");
|
||||
return new ExpressionVariable(variableName, expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ExpressionVariable} with current name and given {@literal expressionObject}.
|
||||
*
|
||||
* @param expressionObject must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public ExpressionVariable forExpression(Document expressionObject) {
|
||||
|
||||
Assert.notNull(expressionObject, "Expression must not be null!");
|
||||
return new ExpressionVariable(variableName, expressionObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.springframework.util.Assert;
|
||||
@Deprecated
|
||||
public enum AggregationFunctionExpressions {
|
||||
|
||||
SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT, ADD;
|
||||
SIZE, CMP, EQ, GT, GTE, LT, LTE, NE, SUBTRACT, ADD, MULTIPLY;
|
||||
|
||||
/**
|
||||
* Returns an {@link AggregationExpression} build from the current {@link Enum} name and the given parameters.
|
||||
|
||||
@@ -17,10 +17,12 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.IfNull;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
@@ -850,7 +852,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
/**
|
||||
* Generates a {@code $setIsSubset} expression that takes array of the previously mentioned field and returns
|
||||
* {@literal true} if it is a subset of the given {@literal array}.
|
||||
*
|
||||
*
|
||||
* @param array must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
@@ -1193,7 +1195,35 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
return this.operation.and(AggregationExpressions.DateToString.dateOf(name).toString(format));
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the
|
||||
* result of the expression.
|
||||
*
|
||||
* @param valueExpression The {@link AggregationExpression} bound to {@literal variableName}.
|
||||
* @param variableName The variable name to be used in the {@literal in} {@link AggregationExpression}.
|
||||
* @param in The {@link AggregationExpression} to evaluate.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public ProjectionOperationBuilder let(AggregationExpression valueExpression, String variableName,
|
||||
AggregationExpression in) {
|
||||
return this.operation.and(AggregationExpressions.Let.define(ExpressionVariable.newVariable(variableName).forExpression(valueExpression)).andApply(in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a {@code $let} expression that binds variables for use in the specified expression, and returns the
|
||||
* result of the expression.
|
||||
*
|
||||
* @param variables The bound {@link ExpressionVariable}s.
|
||||
* @param in The {@link AggregationExpression} to evaluate.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public ProjectionOperationBuilder let(Collection<ExpressionVariable> variables, AggregationExpression in) {
|
||||
return this.operation.and(AggregationExpressions.Let.define(variables).andApply(in));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
|
||||
@@ -60,6 +60,8 @@ import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.Venue;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry;
|
||||
import org.springframework.data.mongodb.core.index.GeospatialIndex;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
@@ -647,9 +649,9 @@ public class AggregationTests {
|
||||
mongoTemplate.insert(new LineItem("idonly", null, 0));
|
||||
|
||||
TypedAggregation<LineItem> aggregation = newAggregation(LineItem.class, //
|
||||
project("id") //
|
||||
.and("caption")//
|
||||
.applyCondition(ConditionalOperators.ifNull("caption").then("unknown")),
|
||||
project("id") //
|
||||
.and("caption")//
|
||||
.applyCondition(ConditionalOperators.ifNull("caption").then("unknown")),
|
||||
sort(ASC, "id"));
|
||||
|
||||
assertThat(aggregation.toString(), is(notNullValue()));
|
||||
@@ -1541,6 +1543,36 @@ project("id") //
|
||||
Sales.builder().id("2").items(Collections.<Item> emptyList()).build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1538
|
||||
*/
|
||||
@Test
|
||||
public void letShouldBeAppliedCorrectly() {
|
||||
|
||||
assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_TWO));
|
||||
|
||||
Sales2 sales1 = Sales2.builder().id("1").price(10).tax(0.5F).applyDiscount(true).build();
|
||||
Sales2 sales2 = Sales2.builder().id("2").price(10).tax(0.25F).applyDiscount(false).build();
|
||||
|
||||
mongoTemplate.insert(Arrays.asList(sales1, sales2), Sales2.class);
|
||||
|
||||
ExpressionVariable total = ExpressionVariable.newVariable("total")
|
||||
.forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax")));
|
||||
ExpressionVariable discounted = ExpressionVariable.newVariable("discounted")
|
||||
.forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D));
|
||||
|
||||
TypedAggregation<Sales2> agg = Aggregation.newAggregation(Sales2.class,
|
||||
Aggregation.project()
|
||||
.and(Let.define(total, discounted).andApply(
|
||||
AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted"))))
|
||||
.as("finalTotal"));
|
||||
|
||||
AggregationResults<Document> result = mongoTemplate.aggregate(agg, Document.class);
|
||||
assertThat(result.getMappedResults(),
|
||||
contains(new Document("_id", "1").append("finalTotal", 9.450000000000001D),
|
||||
new Document("_id", "2").append("finalTotal", 10.25D)));
|
||||
}
|
||||
|
||||
private void createUsersWithReferencedPersons() {
|
||||
|
||||
mongoTemplate.dropCollection(User.class);
|
||||
@@ -1782,6 +1814,9 @@ project("id") //
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @DATAMONGO-1491
|
||||
*/
|
||||
@lombok.Data
|
||||
@Builder
|
||||
static class Sales {
|
||||
@@ -1790,6 +1825,9 @@ project("id") //
|
||||
List<Item> items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @DATAMONGO-1491
|
||||
*/
|
||||
@lombok.Data
|
||||
@Builder
|
||||
static class Item {
|
||||
@@ -1799,4 +1837,17 @@ project("id") //
|
||||
Integer quantity;
|
||||
Long price;
|
||||
}
|
||||
|
||||
/**
|
||||
* @DATAMONGO-1538
|
||||
*/
|
||||
@lombok.Data
|
||||
@Builder
|
||||
static class Sales2 {
|
||||
|
||||
String id;
|
||||
Integer price;
|
||||
Float tax;
|
||||
boolean applyDiscount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.AggregationFunctionExpressions.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
|
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
@@ -25,22 +27,14 @@ import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
import com.mongodb.util.JSON;
|
||||
import org.bson.Document;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.DocumentTestUtils;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArrayOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.BooleanOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ComparisonOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ConditionalOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.DateOperators;
|
||||
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.AggregationExpressions.Let.ExpressionVariable;
|
||||
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.*;
|
||||
|
||||
import com.mongodb.util.JSON;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ProjectionOperation}.
|
||||
@@ -286,9 +280,8 @@ public class ProjectionOperationUnitTests {
|
||||
.and("foo").as("bar"); //
|
||||
|
||||
Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
assertThat(
|
||||
document,
|
||||
is(Document.parse("{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}")));
|
||||
assertThat(document, is(Document.parse(
|
||||
"{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -393,8 +386,7 @@ public class ProjectionOperationUnitTests {
|
||||
Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
Document projected = exctractOperation("$project", document);
|
||||
|
||||
assertThat(projected.get("renamed"),
|
||||
is((Object) new Document("$slice", Arrays.<Object> asList("$field", 10))));
|
||||
assertThat(projected.get("renamed"), is((Object) new Document("$slice", Arrays.<Object> asList("$field", 10))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -408,8 +400,7 @@ public class ProjectionOperationUnitTests {
|
||||
Document document = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
Document projected = exctractOperation("$project", document);
|
||||
|
||||
assertThat(projected.get("renamed"),
|
||||
is((Object) new Document("$slice", Arrays.<Object> asList("$field", 5, 10))));
|
||||
assertThat(projected.get("renamed"), is((Object) new Document("$slice", Arrays.<Object> asList("$field", 5, 10))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -602,7 +593,8 @@ public class ProjectionOperationUnitTests {
|
||||
Document agg = project("A", "B").and("A").subsetOfArray("B").as("aIsSubsetOfB")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}")));
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -614,7 +606,8 @@ public class ProjectionOperationUnitTests {
|
||||
Document agg = project("A", "B").and(SetOperators.arrayAsSet("A").isSubsetOf("B")).as("aIsSubsetOfB")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}")));
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { A: 1, B: 1, aIsSubsetOfB: { $setIsSubset: [ \"$A\", \"$B\" ] }}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -626,7 +619,8 @@ public class ProjectionOperationUnitTests {
|
||||
Document agg = project("responses").and("responses").anyElementInArrayTrue().as("isAnyTrue")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}")));
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -638,7 +632,8 @@ public class ProjectionOperationUnitTests {
|
||||
Document agg = project("responses").and(SetOperators.arrayAsSet("responses").anyElementTrue()).as("isAnyTrue")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}")));
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { responses: 1, isAnyTrue: { $anyElementTrue: [ \"$responses\" ] }}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -739,8 +734,8 @@ public class ProjectionOperationUnitTests {
|
||||
.divide(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).as("result")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { result: { $divide: [ \"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}")));
|
||||
assertThat(agg, is(Document
|
||||
.parse("{ $project: { result: { $divide: [ \"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -852,7 +847,8 @@ public class ProjectionOperationUnitTests {
|
||||
ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).log(2))
|
||||
.as("result").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.parse("{ $project: { result: { $log: [ { $subtract: [ \"$start\", \"$end\" ] }, 2] } }}")));
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { result: { $log: [ { $subtract: [ \"$start\", \"$end\" ] }, 2] } }}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -902,7 +898,8 @@ public class ProjectionOperationUnitTests {
|
||||
ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).mod(2))
|
||||
.as("result").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.parse("{ $project: { result: { $mod: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}")));
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { result: { $mod: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -915,8 +912,8 @@ public class ProjectionOperationUnitTests {
|
||||
.multiply(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).as("result")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(
|
||||
Document.parse("{ $project: { result: { $multiply: [\"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}")));
|
||||
assertThat(agg, is(Document
|
||||
.parse("{ $project: { result: { $multiply: [\"$value\", { $subtract: [ \"$start\", \"$end\" ] }] } }}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -955,7 +952,8 @@ public class ProjectionOperationUnitTests {
|
||||
ArithmeticOperators.valueOf(AggregationFunctionExpressions.SUBTRACT.of(field("start"), field("end"))).pow(2))
|
||||
.as("result").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.parse("{ $project: { result: { $pow: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}")));
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { result: { $pow: [{ $subtract: [ \"$start\", \"$end\" ] }, 2] } }}")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -991,8 +989,8 @@ public class ProjectionOperationUnitTests {
|
||||
Document agg = project().and("numericField").minus(AggregationFunctionExpressions.SIZE.of(field("someArray")))
|
||||
.as("result").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }")));
|
||||
assertThat(agg, is(
|
||||
Document.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1006,8 +1004,8 @@ public class ProjectionOperationUnitTests {
|
||||
.subtract(AggregationFunctionExpressions.SIZE.of(field("someArray"))))
|
||||
.as("result").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }")));
|
||||
assertThat(agg, is(
|
||||
Document.parse("{ $project: { result: { $subtract: [ \"$numericField\", { $size : [\"$someArray\"]}] } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1140,7 +1138,8 @@ public class ProjectionOperationUnitTests {
|
||||
Document agg = project().and("quarter").strCaseCmp("13q4").as("comparisonResult")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }")));
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1152,7 +1151,8 @@ public class ProjectionOperationUnitTests {
|
||||
Document agg = project().and(StringOperators.valueOf("quarter").strCaseCmp("13q4")).as("comparisonResult")
|
||||
.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }")));
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { comparisonResult: { $strcasecmp: [ \"$quarter\", \"13q4\" ] } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1644,8 +1644,8 @@ public class ProjectionOperationUnitTests {
|
||||
.and(ComparisonOperators.valueOf("qty").lessThanValue(250)))
|
||||
.as("result").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(
|
||||
Document.parse("{ $project: { result: { $and: [ { $gt: [ \"$qty\", 100 ] }, { $lt: [ \"$qty\", 250 ] } ] } } }")));
|
||||
assertThat(agg, is(Document
|
||||
.parse("{ $project: { result: { $and: [ { $gt: [ \"$qty\", 100 ] }, { $lt: [ \"$qty\", 250 ] } ] } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1659,8 +1659,8 @@ public class ProjectionOperationUnitTests {
|
||||
.or(ComparisonOperators.valueOf("qty").lessThanValue(200)))
|
||||
.as("result").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(
|
||||
Document.parse("{ $project: { result: { $or: [ { $gt: [ \"$qty\", 250 ] }, { $lt: [ \"$qty\", 200 ] } ] } } }")));
|
||||
assertThat(agg, is(Document
|
||||
.parse("{ $project: { result: { $or: [ { $gt: [ \"$qty\", 250 ] }, { $lt: [ \"$qty\", 200 ] } ] } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1711,11 +1711,12 @@ public class ProjectionOperationUnitTests {
|
||||
@Test
|
||||
public void shouldRenderIfNullConditionAggregationExpression() {
|
||||
|
||||
Document agg = project().and(ConditionalOperators.ifNull(ArrayOperators.arrayOf("array").elementAt(1)).then("a more sophisticated value"))
|
||||
Document agg = project().and(
|
||||
ConditionalOperators.ifNull(ArrayOperators.arrayOf("array").elementAt(1)).then("a more sophisticated value"))
|
||||
.as("result").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project: { result: { $ifNull: [ { $arrayElemAt: [\"$array\", 1] }, \"a more sophisticated value\" ] } } }")));
|
||||
assertThat(agg, is(Document.parse(
|
||||
"{ $project: { result: { $ifNull: [ { $arrayElemAt: [\"$array\", 1] }, \"a more sophisticated value\" ] } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1744,6 +1745,58 @@ public class ProjectionOperationUnitTests {
|
||||
assertThat(agg, is(Document.parse("{ $project: { result: { $ifNull: [ \"$optional\", \"$never-null\" ] } } }")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1538
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderLetExpressionCorrectly() {
|
||||
|
||||
Document agg = Aggregation.project()
|
||||
.and(VariableOperators
|
||||
.define(
|
||||
newVariable("total")
|
||||
.forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax"))),
|
||||
newVariable("discounted").forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D)))
|
||||
.andApply(AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))) //
|
||||
.as("finalTotal").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project:{ \"finalTotal\" : { \"$let\": {" + //
|
||||
"\"vars\": {" + //
|
||||
"\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + //
|
||||
"\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + //
|
||||
"}," + //
|
||||
"\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + //
|
||||
"}}}}")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1538
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderLetExpressionCorrectlyWhenUsingLetOnProjectionBuilder() {
|
||||
|
||||
ExpressionVariable var1 = newVariable("total")
|
||||
.forExpression(AggregationFunctionExpressions.ADD.of(Fields.field("price"), Fields.field("tax")));
|
||||
|
||||
ExpressionVariable var2 = newVariable("discounted")
|
||||
.forExpression(Cond.when("applyDiscount").then(0.9D).otherwise(1.0D));
|
||||
|
||||
Document agg = Aggregation.project().and("foo")
|
||||
.let(Arrays.asList(var1, var2),
|
||||
AggregationFunctionExpressions.MULTIPLY.of(Fields.field("total"), Fields.field("discounted")))
|
||||
.as("finalTotal").toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $project:{ \"finalTotal\" : { \"$let\": {" + //
|
||||
"\"vars\": {" + //
|
||||
"\"total\": { \"$add\": [ \"$price\", \"$tax\" ] }," + //
|
||||
"\"discounted\": { \"$cond\": { \"if\": \"$applyDiscount\", \"then\": 0.9, \"else\": 1.0 } }" + //
|
||||
"}," + //
|
||||
"\"in\": { \"$multiply\": [ \"$$total\", \"$$discounted\" ] }" + //
|
||||
"}}}}")));
|
||||
}
|
||||
|
||||
private static Document exctractOperation(String field, Document fromProjectClause) {
|
||||
return (Document) fromProjectClause.get(field);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user