From 9858dcd7407c490ef1beec52a250bf21fe4de8c0 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Tue, 24 Jun 2014 17:15:38 +0200 Subject: [PATCH] DATAMONGO-960 - Allow to pass options to the Aggregation Pipeline. We introduced AggregationOptions abstraction to conveniently construct option objects that can be handed to an Aggregation via the new Aggregation.withOptions(...) factory method. For more details, see the Builder class' JavaDoc. Note that we exposed the "rawResults" in AggregationResults and put a null guard in MongoTemplate aggregate in order to support the "explain" option. Original pull request: #195. --- .../data/mongodb/core/MongoTemplate.java | 21 +- .../mongodb/core/aggregation/Aggregation.java | 67 ++++++- .../core/aggregation/AggregationOptions.java | 189 ++++++++++++++++++ .../core/aggregation/AggregationResults.java | 13 +- .../core/aggregation/TypedAggregation.java | 39 +++- .../aggregation/AggregationOptionsTests.java | 64 ++++++ .../core/aggregation/AggregationTests.java | 80 ++++++-- .../aggregation/AggregationUnitTests.java | 37 ++++ ...dAggregationOperationContextUnitTests.java | 23 +++ 9 files changed, 507 insertions(+), 26 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index f2c690afe..f1bac2f05 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -1413,17 +1413,32 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware { CommandResult commandResult = executeCommand(command); handleCommandError(commandResult, command); - // map results + return new AggregationResults(returnPotentiallyMappedResults(outputType, commandResult), commandResult); + } + + /** + * Returns the potentially mapped results of the given {@commandResult} contained some. + * + * @param outputType + * @param commandResult + * @return + */ + private List returnPotentiallyMappedResults(Class outputType, CommandResult commandResult) { + @SuppressWarnings("unchecked") Iterable resultSet = (Iterable) commandResult.get("result"); - List mappedResults = new ArrayList(); + if (resultSet == null) { + return Collections.emptyList(); + } + DbObjectCallback callback = new UnwrapAndReadDbObjectCallback(mongoConverter, outputType); + List mappedResults = new ArrayList(); for (DBObject dbObject : resultSet) { mappedResults.add(callback.doWith(dbObject)); } - return new AggregationResults(mappedResults, commandResult); + return mappedResults; } protected String replaceWithResourceIfNecessary(String function) { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java index dd81685fc..0341d7398 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java @@ -45,8 +45,11 @@ import com.mongodb.DBObject; public class Aggregation { public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext(); + public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build(); - private final List operations; + protected final List operations; + + private final AggregationOptions options; /** * Creates a new {@link Aggregation} from the given {@link AggregationOperation}s. @@ -66,6 +69,20 @@ public class Aggregation { return new Aggregation(operations); } + /** + * Returns a copy of this {@link Aggregation} with the given {@link AggregationOptions} set. Note that options are + * supported in MongoDB version 2.6+. + * + * @param options must not be {@literal null}. + * @return + * @since 1.6 + */ + public Aggregation withOptions(AggregationOptions options) { + + Assert.notNull(options, "AggregationOptions must not be null."); + return new Aggregation(this.operations, options); + } + /** * Creates a new {@link TypedAggregation} for the given type and {@link AggregationOperation}s. * @@ -92,11 +109,43 @@ public class Aggregation { * @param aggregationOperations must not be {@literal null} or empty. */ protected Aggregation(AggregationOperation... aggregationOperations) { + this(asAggregationList(aggregationOperations)); + } + + /** + * @param aggregationOperations must not be {@literal null} or empty. + * @return + */ + protected static List asAggregationList(AggregationOperation... aggregationOperations) { + + Assert.notEmpty(aggregationOperations, "AggregationOperations must not be null or empty!"); + + return Arrays.asList(aggregationOperations); + } + + /** + * Creates a new {@link Aggregation} from the given {@link AggregationOperation}s. + * + * @param aggregationOperations must not be {@literal null} or empty. + */ + protected Aggregation(List aggregationOperations) { + this(aggregationOperations, DEFAULT_OPTIONS); + } + + /** + * Creates a new {@link Aggregation} from the given {@link AggregationOperation}s. + * + * @param aggregationOperations must not be {@literal null} or empty. + * @param options must not be {@literal null} or empty. + */ + protected Aggregation(List aggregationOperations, AggregationOptions options) { Assert.notNull(aggregationOperations, "AggregationOperations must not be null!"); - Assert.isTrue(aggregationOperations.length > 0, "At least one AggregationOperation has to be provided"); + Assert.isTrue(aggregationOperations.size() > 0, "At least one AggregationOperation has to be provided"); + Assert.notNull(options, "AggregationOptions must not be null!"); - this.operations = Arrays.asList(aggregationOperations); + this.operations = aggregationOperations; + this.options = options; } /** @@ -231,6 +280,16 @@ public class Aggregation { return Fields.from(field(name, target)); } + /** + * Returns a new {@link AggregationOptions.Builder}. + * + * @return + * @since 1.6 + */ + public static AggregationOptions.Builder newAggregationOptions() { + return new AggregationOptions.Builder(); + } + /** * Converts this {@link Aggregation} specification to a {@link DBObject}. * @@ -255,6 +314,8 @@ public class Aggregation { DBObject command = new BasicDBObject("aggregate", inputCollectionName); command.put("pipeline", operationDocuments); + command = options.applyAndReturnPotentiallyChangedCommand(command); + return command; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java new file mode 100644 index 000000000..c7c17b835 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java @@ -0,0 +1,189 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Holds a set of configurable aggregation options that can be used within an aggregation pipeline. A list of support + * aggregation options can be found in the MongoDB reference documentation + * http://docs.mongodb.org/manual/reference/command/aggregate/#aggregate + * + * @author Thomas Darimont + * @author Oliver Gierke + * @see Aggregation#withOptions(AggregationOptions) + * @see TypedAggregation#withOptions(AggregationOptions) + * @since 1.6 + */ +public class AggregationOptions { + + private static final String CURSOR = "cursor"; + private static final String EXPLAIN = "explain"; + private static final String ALLOW_DISK_USE = "allowDiskUse"; + + private final boolean allowDiskUse; + private final boolean explain; + private final DBObject cursor; + + /** + * Creates a new {@link AggregationOptions}. + * + * @param allowDiskUse whether to off-load intensive sort-operations to disk. + * @param explain whether to get the execution plan for the aggregation instead of the actual results. + * @param cursor can be {@literal null}, used to pass additional options to the aggregation. + */ + public AggregationOptions(boolean allowDiskUse, boolean explain, DBObject cursor) { + + this.allowDiskUse = allowDiskUse; + this.explain = explain; + this.cursor = cursor; + } + + /** + * Enables writing to temporary files. When set to true, aggregation stages can write data to the _tmp subdirectory in + * the dbPath directory. + * + * @return + */ + public boolean isAllowDiskUse() { + return allowDiskUse; + } + + /** + * Specifies to return the information on the processing of the pipeline. + * + * @return + */ + public boolean isExplain() { + return explain; + } + + /** + * Specify a document that contains options that control the creation of the cursor object. + * + * @return + */ + public DBObject getCursor() { + return cursor; + } + + /** + * Returns a new potentially adjusted copy for the given {@code aggregationCommandObject} with the configuration + * applied. + * + * @param command the aggregation command. + * @return + */ + DBObject applyAndReturnPotentiallyChangedCommand(DBObject command) { + + DBObject result = new BasicDBObject(command.toMap()); + + if (allowDiskUse && !result.containsField(ALLOW_DISK_USE)) { + result.put(ALLOW_DISK_USE, allowDiskUse); + } + + if (explain && !result.containsField(EXPLAIN)) { + result.put(EXPLAIN, explain); + } + + if (cursor != null && !result.containsField(CURSOR)) { + result.put("cursor", cursor); + } + + return result; + } + + /** + * Returns a {@link DBObject} representation of this {@link AggregationOptions}. + * + * @return + */ + public DBObject toDbObject() { + + DBObject dbo = new BasicDBObject(); + dbo.put(ALLOW_DISK_USE, allowDiskUse); + dbo.put(EXPLAIN, explain); + dbo.put(CURSOR, cursor); + + return dbo; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return toDbObject().toString(); + } + + /** + * A Builder for {@link AggregationOptions}. + * + * @author Thomas Darimont + */ + public static class Builder { + + private boolean allowDiskUse; + private boolean explain; + private DBObject cursor; + + /** + * Defines whether to off-load intensive sort-operations to disk. + * + * @param allowDiskUse + * @return + */ + public Builder allowDiskUse(boolean allowDiskUse) { + + this.allowDiskUse = allowDiskUse; + return this; + } + + /** + * Defines whether to get the execution plan for the aggregation instead of the actual results. + * + * @param explain + * @return + */ + public Builder explain(boolean explain) { + + this.explain = explain; + return this; + } + + /** + * Additional options to the aggregation. + * + * @param cursor + * @return + */ + public Builder cursor(DBObject cursor) { + + this.cursor = cursor; + return this; + } + + /** + * Returns a new {@link AggregationOptions} instance with the given configuration. + * + * @return + */ + public AggregationOptions build() { + return new AggregationOptions(allowDiskUse, explain, cursor); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java index 8c0fac644..2fbf96c4c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import com.mongodb.DBObject; * * @author Tobias Trelle * @author Oliver Gierke + * @author Thomas Darimont * @param The class in which the results are mapped onto. * @since 1.3 */ @@ -90,6 +91,16 @@ public class AggregationResults implements Iterable { return serverUsed; } + /** + * Returns the raw result that was returned by the server. + * + * @return + * @since 1.6 + */ + public DBObject getRawResults() { + return rawResults; + } + private String parseServerUsed() { Object object = rawResults.get("serverUsed"); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypedAggregation.java index 9a13dc2a6..0841de225 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypedAggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/TypedAggregation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package org.springframework.data.mongodb.core.aggregation; +import java.util.List; + import org.springframework.util.Assert; /** @@ -30,11 +32,34 @@ public class TypedAggregation extends Aggregation { /** * Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s. * + * @param inputType must not be {@literal null}. * @param operations must not be {@literal null} or empty. */ public TypedAggregation(Class inputType, AggregationOperation... operations) { + this(inputType, asAggregationList(operations)); + } - super(operations); + /** + * Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s. + * + * @param inputType must not be {@literal null}. + * @param operations must not be {@literal null} or empty. + */ + public TypedAggregation(Class inputType, List operations) { + this(inputType, operations, DEFAULT_OPTIONS); + } + + /** + * Creates a new {@link TypedAggregation} from the given {@link AggregationOperation}s and the given + * {@link AggregationOptions}. + * + * @param inputType must not be {@literal null}. + * @param operations must not be {@literal null} or empty. + * @param options must not be {@literal null}. + */ + public TypedAggregation(Class inputType, List operations, AggregationOptions options) { + + super(operations, options); Assert.notNull(inputType, "Input type must not be null!"); this.inputType = inputType; @@ -48,4 +73,14 @@ public class TypedAggregation extends Aggregation { public Class getInputType() { return inputType; } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.Aggregation#withOptions(org.springframework.data.mongodb.core.aggregation.AggregationOptions) + */ + public TypedAggregation withOptions(AggregationOptions options) { + + Assert.notNull(options, "AggregationOptions must not be null."); + return new TypedAggregation(inputType, operations, options); + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java new file mode 100644 index 000000000..ea3f556d4 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationOptionsTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +import org.junit.Before; +import org.junit.Test; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Unit tests for {@link AggregationOptions}. + * + * @author Thomas Darimont + * @since 1.6 + */ +public class AggregationOptionsTests { + + AggregationOptions aggregationOptions; + + @Before + public void setup() { + aggregationOptions = newAggregationOptions().explain(true).cursor(new BasicDBObject("foo", 1)).allowDiskUse(true) + .build(); + + } + + /** + * @see DATAMONGO-960 + */ + @Test + public void aggregationOptionsBuilderShouldSetOptionsAccordingly() { + + assertThat(aggregationOptions.isAllowDiskUse(), is(true)); + assertThat(aggregationOptions.isExplain(), is(true)); + assertThat(aggregationOptions.getCursor(), is((DBObject) new BasicDBObject("foo", 1))); + } + + /** + * @see DATAMONGO-960 + */ + @Test + public void aggregationOptionsToString() { + assertThat(aggregationOptions.toString(), + is("{ \"allowDiskUse\" : true , \"explain\" : true , \"cursor\" : { \"foo\" : 1}}")); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 25cbd3b89..018c0c5fc 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -75,6 +75,7 @@ public class AggregationTests { private static final String INPUT_COLLECTION = "aggregation_test_collection"; private static final Logger LOGGER = LoggerFactory.getLogger(AggregationTests.class); private static final Version TWO_DOT_FOUR = new Version(2, 4); + private static final Version TWO_DOT_SIX = new Version(2, 6); private static boolean initialized = false; @@ -414,23 +415,7 @@ public class AggregationTests { createUserWithLikesDocuments(); - /* - ... - $group: { - _id:"$like", - number:{ $sum:1} - } - ... - - */ - - TypedAggregation agg = newAggregation(UserWithLikes.class, // - unwind("likes"), // - group("likes").count().as("number"), // - sort(DESC, "number"), // - limit(5), // - sort(ASC, previousOperation()) // - ); + TypedAggregation agg = createUsersWithCommonLikesAggregation(); assertThat(agg, is(notNullValue())); assertThat(agg.toString(), is(notNullValue())); @@ -447,6 +432,16 @@ public class AggregationTests { assertLikeStats(result.getMappedResults().get(4), "e", 3); } + protected TypedAggregation createUsersWithCommonLikesAggregation() { + return newAggregation(UserWithLikes.class, // + unwind("likes"), // + group("likes").count().as("number"), // + sort(DESC, "number"), // + limit(5), // + sort(ASC, previousOperation()) // + ); + } + @Test public void arithmenticOperatorsInProjectionExample() { @@ -857,6 +852,57 @@ public class AggregationTests { assertThat(result.getMappedResults(), hasSize(3)); } + /** + * @see DATAMONGO-960 + */ + @Test + public void returnFiveMostCommonLikesAggregationFrameworkExampleWithSortOnDiskOptionEnabled() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + + createUserWithLikesDocuments(); + + TypedAggregation agg = createUsersWithCommonLikesAggregation() // + .withOptions(newAggregationOptions().allowDiskUse(true).build()); + + assertThat(agg, is(notNullValue())); + assertThat(agg.toString(), is(notNullValue())); + + AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); + assertThat(result, is(notNullValue())); + assertThat(result.getMappedResults(), is(notNullValue())); + assertThat(result.getMappedResults().size(), is(5)); + + assertLikeStats(result.getMappedResults().get(0), "a", 4); + assertLikeStats(result.getMappedResults().get(1), "b", 2); + assertLikeStats(result.getMappedResults().get(2), "c", 4); + assertLikeStats(result.getMappedResults().get(3), "d", 2); + assertLikeStats(result.getMappedResults().get(4), "e", 3); + } + + /** + * @see DATAMONGO-960 + */ + @Test + public void returnFiveMostCommonLikesShouldReturnStageExecutionInformationWithExplainOptionEnabled() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + + createUserWithLikesDocuments(); + + TypedAggregation agg = createUsersWithCommonLikesAggregation() // + .withOptions(newAggregationOptions().explain(true).build()); + + AggregationResults result = mongoTemplate.aggregate(agg, LikeStats.class); + + assertThat(result.getMappedResults(), is(empty())); + + DBObject rawResult = result.getRawResults(); + + assertThat(rawResult, is(notNullValue())); + assertThat(rawResult.containsField("stages"), is(true)); + } + private void assertLikeStats(LikeStats like, String id, long count) { assertThat(like, is(notNullValue())); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index 95635bc66..2cb7cf6c4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -219,6 +219,43 @@ public class AggregationUnitTests { assertThat(projection1, is((DBObject) new BasicDBObject("b", "$ba"))); } + /** + * @see DATAMONGO-960 + */ + @Test + public void shouldRenderAggregationWithDefaultOptionsCorrectly() { + + DBObject agg = newAggregation( // + project().and("a").as("aa") // + ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + assertThat(agg.toString(), + is("{ \"aggregate\" : \"foo\" , \"pipeline\" : [ { \"$project\" : { \"aa\" : \"$a\"}}]}")); + } + + /** + * @see DATAMONGO-960 + */ + @Test + public void shouldRenderAggregationWithCustomOptionsCorrectly() { + + AggregationOptions aggregationOptions = newAggregationOptions().explain(true).cursor(new BasicDBObject("foo", 1)) + .allowDiskUse(true).build(); + + DBObject agg = newAggregation( // + project().and("a").as("aa") // + ) // + .withOptions(aggregationOptions) // + .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + assertThat(agg.toString(), is("{ \"aggregate\" : \"foo\" , " // + + "\"pipeline\" : [ { \"$project\" : { \"aa\" : \"$a\"}}] , " // + + "\"allowDiskUse\" : true , " // + + "\"explain\" : true , " // + + "\"cursor\" : { \"foo\" : 1}}" // + )); + } + private DBObject extractPipelineElement(DBObject agg, int index, String operation) { List pipeline = (List) agg.get("pipeline"); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index 3ec62b847..c0c37bb38 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -146,6 +146,29 @@ public class TypeBasedAggregationOperationContextUnitTests { assertThat(age, is((DBObject) new BasicDBObject("v", 10))); } + /** + * @see DATAMONGO-960 + */ + @Test + public void rendersAggregationOptionsInTypedAggregationContextCorrectly() { + + AggregationOperationContext context = getContext(FooPerson.class); + TypedAggregation agg = newAggregation(FooPerson.class, project("name", "age")) // + .withOptions( + newAggregationOptions().allowDiskUse(true).explain(true).cursor(new BasicDBObject("foo", 1)).build()); + + DBObject dbo = agg.toDbObject("person", context); + + DBObject projection = getPipelineElementFromAggregationAt(dbo, 0); + assertThat(projection.containsField("$project"), is(true)); + + assertThat(projection.get("$project"), is((Object) new BasicDBObject("name", 1).append("age", 1))); + + assertThat(dbo.get("allowDiskUse"), is((Object) true)); + assertThat(dbo.get("explain"), is((Object) true)); + assertThat(dbo.get("cursor"), is((Object) new BasicDBObject("foo", 1))); + } + @Document(collection = "person") public static class FooPerson {