DATAMONGO-1906 - Add SystemVariable $$REMOVE for aggregation $project stage.

Add, document and make sure conditional projection in aggregation is treated correctly.

Original pull request: #540.
This commit is contained in:
Christoph Strobl
2018-03-20 15:29:52 +01:00
committed by Mark Paluch
parent f7d65cf8d4
commit 4d8f5d63c7
3 changed files with 80 additions and 2 deletions

View File

@@ -64,6 +64,33 @@ public class Aggregation {
*/
public static final String CURRENT = SystemVariable.CURRENT.toString();
/**
* A variable which evaluates to the missing value. Allows for the conditional exclusion of fields. In a
* {@code $projection}, a field set to the variable {@literal REMOVE} is excluded from the output.
*
* <pre>
* <code>
*
* db.books.aggregate( [
* {
* $project: {
* title: 1,
* "author.first": 1,
* "author.last" : 1,
* "author.middle": {
* $cond: {
* if: { $eq: [ "", "$author.middle" ] },
* then: "$$REMOVE",
* else: "$author.middle"
* }
* }
* }
* } ] )
* </code>
* </pre>
*/
public static final String REMOVE = SystemVariable.REMOVE.toString();
public static final AggregationOperationContext DEFAULT_CONTEXT = AggregationOperationRenderer.DEFAULT_CONTEXT;
public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build();
@@ -648,11 +675,12 @@ public class Aggregation {
* Describes the system variables available in MongoDB aggregation framework pipeline expressions.
*
* @author Thomas Darimont
* @see <a href="https://docs.mongodb.org/manual/reference/aggregation-variables">Aggregation Variables</a>
* @author Christoph Strobl
* @see <a href="https://docs.mongodb.com/manual/reference/aggregation-variables">Aggregation Variables</a>.
*/
enum SystemVariable {
ROOT, CURRENT;
ROOT, CURRENT, REMOVE;
private static final String PREFIX = "$$";

View File

@@ -21,6 +21,8 @@ import static org.springframework.data.mongodb.core.aggregation.Fields.*;
import static org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable.*;
import static org.springframework.data.mongodb.test.util.Assertions.*;
import lombok.Data;
import java.util.Arrays;
import java.util.List;
@@ -212,6 +214,21 @@ public class ProjectionOperationUnitTests {
assertThat((Integer) projectClause.get(Fields.UNDERSCORE_ID)).isEqualTo(0);
}
@Test // DATAMONGO-1906
public void rendersConditionalProjectionCorrectly() {
TypedAggregation aggregation = Aggregation.newAggregation(Book.class,
Aggregation.project("title")
.and(ConditionalOperators.when(ComparisonOperators.valueOf("author.middle").equalToValue(""))
.then("$$REMOVE").otherwiseValueOf("author.middle"))
.as("author.middle"));
Document document = aggregation.toDocument("books", Aggregation.DEFAULT_CONTEXT);
assertThat(document).isEqualTo(Document.parse(
"{\"aggregate\" : \"books\", \"pipeline\" : [{\"$project\" : {\"title\" : 1, \"author.middle\" : {\"$cond\" : {\"if\" : {\"$eq\" : [\"$author.middle\", \"\"]}, \"then\" : \"$$REMOVE\",\"else\" : \"$author.middle\"} }}}]}"));
}
@Test // DATAMONGO-757
public void usesImplictAndExplicitFieldAliasAndIncludeExclude() {
@@ -1715,4 +1732,17 @@ public class ProjectionOperationUnitTests {
return (Document) fromProjectClause.get(field);
}
@Data
static class Book {
String title;
Author author;
}
@Data
static class Author {
String first;
String last;
String middle;
}
}

View File

@@ -2565,6 +2565,26 @@ List<InventoryItemProjection> stateStatsList = result.getMappedResults();
* This one-step aggregation uses a projection operation with the `inventory` collection. We project the `discount` field using a conditional operation for all inventory items that have a `qty` greater or equal to `250`. A second conditional projection is performed for the `description` field. We apply the description `Unspecified` to all items that either do not have a `description` field of items that have a `null` description.
As of MongoDB 3.6 it is possible to exclude fields from the projection using a conditional expression.
.Conditional aggregation projection
====
[source,java]
----
TypedAggregation<Book> agg = Aggregation.newAggregation(Book.class,
project("title")
.and(ConditionalOperators.when(ComparisonOperators.valueOf("author.middle") <1>
.equalToValue("")) <2>
.then("$$REMOVE") <3>
.otherwiseValueOf("author.middle") <4>
)
.as("author.middle"));
----
<1> If the value of the field `author.middle`
<2> does not contain a value
<3> then use https://docs.mongodb.com/manual/reference/aggregation-variables/#variable.REMOVE[``$$REMOVE``] to exclude the field
<4> else add the field value of `author.middle`.
====
[[mongo.custom-converters]]
== Overriding default mapping with custom converters