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 25b816b38..124dd6517 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 @@ -27,6 +27,7 @@ import org.springframework.data.mongodb.core.aggregation.CountOperation.CountOpe import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; +import org.springframework.data.mongodb.core.aggregation.FacetOperation.FacetOperationBuilder; import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; import org.springframework.data.mongodb.core.aggregation.GraphLookupOperation.StartWithBuilder; import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.ReplaceRootDocumentOperationBuilder; @@ -68,7 +69,7 @@ public class Aggregation { */ public static final String CURRENT = SystemVariable.CURRENT.toString(); - public static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext(); + public static final AggregationOperationContext DEFAULT_CONTEXT = AggregationOperationRenderer.DEFAULT_CONTEXT; public static final AggregationOptions DEFAULT_OPTIONS = newAggregationOptions().build(); protected final List operations; @@ -460,6 +461,25 @@ public class Aggregation { return new BucketAutoOperation(groupByExpression, buckets); } + /** + * Creates a new {@link FacetOperation}. + * + * @return + */ + public static FacetOperation facet() { + return FacetOperation.EMPTY; + } + + /** + * Creates a new {@link FacetOperationBuilder} given {@link Aggregation}. + * + * @param aggregationOperations the sub-pipeline, must not be {@literal null}. + * @return + */ + public static FacetOperationBuilder facet(AggregationOperation... aggregationOperations) { + return facet().and(aggregationOperations); + } + /** * Creates a new {@link LookupOperation}. * @@ -551,26 +571,7 @@ public class Aggregation { */ public DBObject toDbObject(String inputCollectionName, AggregationOperationContext rootContext) { - AggregationOperationContext context = rootContext; - List operationDocuments = new ArrayList(operations.size()); - - for (AggregationOperation operation : operations) { - - operationDocuments.add(operation.toDBObject(context)); - - if (operation instanceof FieldsExposingAggregationOperation) { - - FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation; - ExposedFields fields = exposedFieldsOperation.getFields(); - - if (operation instanceof InheritsFieldsAggregationOperation) { - context = new InheritingExposedFieldsAggregationOperationContext(fields, context); - } else { - context = fields.exposesNoFields() ? DEFAULT_CONTEXT - : new ExposedFieldsAggregationOperationContext(fields, context); - } - } - } + List operationDocuments = AggregationOperationRenderer.toDBObject(operations, rootContext); DBObject command = new BasicDBObject("aggregate", inputCollectionName); command.put("pipeline", operationDocuments); @@ -586,43 +587,7 @@ public class Aggregation { */ @Override public String toString() { - return SerializationUtils - .serializeToJsonSafely(toDbObject("__collection__", new NoOpAggregationOperationContext())); - } - - /** - * Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is. - * - * @author Oliver Gierke - */ - private static class NoOpAggregationOperationContext implements AggregationOperationContext { - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.DBObject) - */ - @Override - public DBObject getMappedObject(DBObject dbObject) { - return dbObject; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField) - */ - @Override - public FieldReference getReference(Field field) { - return new DirectFieldReference(new ExposedField(field, true)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String) - */ - @Override - public FieldReference getReference(String name) { - return new DirectFieldReference(new ExposedField(new AggregationField(name), true)); - } + return SerializationUtils.serializeToJsonSafely(toDbObject("__collection__", DEFAULT_CONTEXT)); } /** @@ -662,7 +627,7 @@ public class Aggregation { return false; } - /* + /* * (non-Javadoc) * @see java.lang.Enum#toString() */ diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java new file mode 100644 index 000000000..20dcb58a7 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOperationRenderer.java @@ -0,0 +1,109 @@ +/* + * Copyright 2016 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 java.util.ArrayList; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference; +import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; +import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation; + +import com.mongodb.DBObject; + +/** + * Rendering support for {@link AggregationOperation} into a {@link List} of {@link com.mongodb.DBObject}. + * + * @author Mark Paluch + * @author Christoph Strobl + * @since 1.10 + */ +class AggregationOperationRenderer { + + static final AggregationOperationContext DEFAULT_CONTEXT = new NoOpAggregationOperationContext(); + + /** + * Render a {@link List} of {@link AggregationOperation} given {@link AggregationOperationContext} into their + * {@link DBObject} representation. + * + * @param operations must not be {@literal null}. + * @param context must not be {@literal null}. + * @return the {@link List} of {@link DBObject}. + */ + static List toDBObject(List operations, AggregationOperationContext rootContext) { + + List operationDocuments = new ArrayList(operations.size()); + + AggregationOperationContext contextToUse = rootContext; + + for (AggregationOperation operation : operations) { + + operationDocuments.add(operation.toDBObject(contextToUse)); + + if (operation instanceof FieldsExposingAggregationOperation) { + + FieldsExposingAggregationOperation exposedFieldsOperation = (FieldsExposingAggregationOperation) operation; + ExposedFields fields = exposedFieldsOperation.getFields(); + + if (operation instanceof InheritsFieldsAggregationOperation) { + contextToUse = new InheritingExposedFieldsAggregationOperationContext(fields, contextToUse); + } else { + contextToUse = fields.exposesNoFields() ? DEFAULT_CONTEXT + : new ExposedFieldsAggregationOperationContext(exposedFieldsOperation.getFields(), contextToUse); + } + } + } + + return operationDocuments; + } + + /** + * Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is. + * + * @author Oliver Gierke + */ + private static class NoOpAggregationOperationContext implements AggregationOperationContext { + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getMappedObject(com.mongodb.DBObject) + */ + @Override + public DBObject getMappedObject(DBObject dbObject) { + return dbObject; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(org.springframework.data.mongodb.core.aggregation.ExposedFields.AvailableField) + */ + @Override + public FieldReference getReference(Field field) { + return new DirectFieldReference(new ExposedField(field, true)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperationContext#getReference(java.lang.String) + */ + @Override + public FieldReference getReference(String name) { + return new DirectFieldReference(new ExposedField(new AggregationField(name), true)); + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java new file mode 100644 index 000000000..9082acedf --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/FacetOperation.java @@ -0,0 +1,231 @@ +/* + * Copyright 2016 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 java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mongodb.core.aggregation.BucketOperationSupport.Output; +import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; +import org.springframework.util.Assert; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Encapsulates the aggregation framework {@code $facet}-operation. + *

+ * Facet of {@link AggregationOperation}s to be used in an {@link Aggregation}. Processes multiple + * {@link AggregationOperation} pipelines within a single stage on the same set of input documents. Each sub-pipeline + * has its own field in the output document where its results are stored as an array of documents. + * {@link FacetOperation} enables various aggregations on the same set of input documents, without needing to retrieve + * the input documents multiple times. + *

+ * As of MongoDB 3.4, {@link FacetOperation} cannot be used with nested pipelines containing {@link GeoNearOperation}, + * {@link OutOperation} and {@link FacetOperation}. + *

+ * We recommend to use the static factory method {@link Aggregation#facet()} instead of creating instances of this class + * directly. + * + * @see http://docs.mongodb.org/manual/reference/aggregation/facet/ + * @author Mark Paluch + * @since 1.10 + */ +public class FacetOperation implements FieldsExposingAggregationOperation { + + /** + * Empty (initial) {@link FacetOperation}. + */ + public static final FacetOperation EMPTY = new FacetOperation(); + + private final Facets facets; + + /** + * Creates a new {@link FacetOperation}. + */ + public FacetOperation() { + this(Facets.EMPTY); + } + + private FacetOperation(Facets facets) { + this.facets = facets; + } + + /** + * Creates a new {@link FacetOperationBuilder} to append a new facet using {@literal operations}. + *

+ * {@link FacetOperationBuilder} takes a pipeline of {@link AggregationOperation} to categorize documents into a + * single facet. + * + * @param operations must not be {@literal null} or empty. + * @return + */ + public FacetOperationBuilder and(AggregationOperation... operations) { + + Assert.notNull(operations, "AggregationOperations must not be null!"); + Assert.notEmpty(operations, "AggregationOperations must not be empty!"); + + return new FacetOperationBuilder(facets, Arrays.asList(operations)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) + */ + @Override + public DBObject toDBObject(AggregationOperationContext context) { + return new BasicDBObject("$facet", facets.toDBObject(context)); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return facets.asExposedFields(); + } + + /** + * Builder for {@link FacetOperation} by adding existing and the new pipeline of {@link AggregationOperation} to the + * new {@link FacetOperation}. + * + * @author Mark Paluch + */ + public static class FacetOperationBuilder { + + private final Facets current; + private final List operations; + + private FacetOperationBuilder(Facets current, List operations) { + this.current = current; + this.operations = operations; + } + + /** + * Creates a new {@link FacetOperation} that contains the configured pipeline of {@link AggregationOperation} + * exposed as {@literal fieldName} in the resulting facet document. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public FacetOperation as(String fieldName) { + + Assert.hasText(fieldName, "FieldName must not be null or empty!"); + + return new FacetOperation(current.and(fieldName, operations)); + } + } + + /** + * Encapsulates multiple {@link Facet}s + * + * @author Mark Paluch + */ + private static class Facets { + + private static final Facets EMPTY = new Facets(Collections. emptyList()); + + private List facets; + + /** + * Creates a new {@link Facets} given {@link List} of {@link Facet}. + * + * @param facets + */ + private Facets(List facets) { + this.facets = facets; + } + + /** + * @return the {@link ExposedFields} derived from {@link Output}. + */ + protected ExposedFields asExposedFields() { + + ExposedFields fields = ExposedFields.from(); + + for (Facet facet : facets) { + fields = fields.and(facet.getExposedField()); + } + + return fields; + } + + protected DBObject toDBObject(AggregationOperationContext context) { + + DBObject dbObject = new BasicDBObject(facets.size()); + + for (Facet facet : facets) { + dbObject.put(facet.getExposedField().getName(), facet.toDBObjects(context)); + } + + return dbObject; + } + + /** + * Adds a facet to this {@link Facets}. + * + * @param fieldName must not be {@literal null}. + * @param operations must not be {@literal null}. + * @return the new {@link Facets}. + */ + public Facets and(String fieldName, List operations) { + + Assert.hasText(fieldName, "FieldName must not be null or empty!"); + Assert.notNull(operations, "AggregationOperations must not be null!"); + + List facets = new ArrayList(this.facets.size() + 1); + facets.addAll(this.facets); + facets.add(new Facet(new ExposedField(fieldName, true), operations)); + + return new Facets(facets); + } + } + + /** + * A single facet with a {@link ExposedField} and its {@link AggregationOperation} pipeline. + * + * @author Mark Paluch + */ + private static class Facet { + + private final ExposedField exposedField; + private final List operations; + + /** + * Creates a new {@link Facet} given {@link ExposedField} and {@link AggregationOperation} pipeline. + * + * @param exposedField must not be {@literal null}. + * @param operations must not be {@literal null}. + */ + protected Facet(ExposedField exposedField, List operations) { + + Assert.notNull(exposedField, "ExposedField must not be null!"); + Assert.notNull(operations, "AggregationOperations must not be null!"); + + this.exposedField = exposedField; + this.operations = operations; + } + + protected ExposedField getExposedField() { + return exposedField; + } + + protected List toDBObjects(AggregationOperationContext context) { + return AggregationOperationRenderer.toDBObject(operations, context); + } + } +} 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 63243e1b8..6112c86a9 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 @@ -1747,6 +1747,54 @@ public class AggregationTests { assertThat((Double) bound1.get("sum"), is(closeTo(1673.0, 0.1))); } + /** + * @see DATAMONGO-1552 + */ + @Test + public void facetShouldCreateFacets() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + Art a1 = Art.builder().id(1).title("The Pillars of Society").artist("Grosz").year(1926).price(199.99).build(); + Art a2 = Art.builder().id(2).title("Melancholy III").artist("Munch").year(1902).price(280.00).build(); + Art a3 = Art.builder().id(3).title("Dancer").artist("Miro").year(1925).price(76.04).build(); + Art a4 = Art.builder().id(4).title("The Great Wave off Kanagawa").artist("Hokusai").price(167.30).build(); + + mongoTemplate.insert(Arrays.asList(a1, a2, a3, a4), Art.class); + + BucketAutoOperation bucketPrice = bucketAuto(Multiply.valueOf("price").multiplyBy(10), 3) // + .withGranularity(Granularities.E12) // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles") // + .andOutputExpression("price * 10") // + .sum().as("sum"); + + TypedAggregation aggregation = newAggregation(Art.class, // + project("title", "artist", "year", "price"), // + facet(bucketPrice).as("categorizeByPrice") // + .and(bucketAuto("year", 3)).as("categorizeByYear")); + + AggregationResults result = mongoTemplate.aggregate(aggregation, DBObject.class); + assertThat(result.getMappedResults().size(), is(1)); + + DBObject mappedResult = result.getUniqueMappedResult(); + + // [ { "_id" : { "min" : 680.0 , "max" : 820.0} , "count" : 1 , "titles" : [ "Dancer"] , "sum" : 760.4000000000001} + // , + // { "_id" : { "min" : 820.0 , "max" : 1800.0} , "count" : 1 , "titles" : [ "The Great Wave off Kanagawa"] , "sum" : + // 1673.0} , + // { "_id" : { "min" : 1800.0 , "max" : 3300.0} , "count" : 2 , "titles" : [ "The Pillars of Society" , "Melancholy + // III"] , "sum" : 4799.9}] + BasicDBList categorizeByPrice = (BasicDBList) mappedResult.get("categorizeByPrice"); + assertThat(categorizeByPrice, hasSize(3)); + + // [ { "_id" : { "min" : null , "max" : 1902} , "count" : 1} , + // { "_id" : { "min" : 1902 , "max" : 1925} , "count" : 1} , + // { "_id" : { "min" : 1925 , "max" : 1926} , "count" : 2}] + BasicDBList categorizeByYear = (BasicDBList) mappedResult.get("categorizeByYear"); + assertThat(categorizeByYear, hasSize(3)); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); 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 275d35c3b..c865f2a26 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 @@ -578,6 +578,23 @@ public class AggregationUnitTests { isBsonObject().containing("$ifNull", Arrays.asList("$chroma", "$fallback"))); } + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldHonorDefaultCountField() { + + DBObject agg = Aggregation + .newAggregation(// + bucket("year"), // + project("count")) // + .toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + DBObject project = extractPipelineElement(agg, 1, "$project"); + + assertThat(project, isBsonObject().containing("count", 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/FacetOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java new file mode 100644 index 000000000..ee40128d5 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/FacetOperationUnitTests.java @@ -0,0 +1,117 @@ +/* + * Copyright 2016 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.MatcherAssert.*; +import static org.hamcrest.Matchers.*; +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +import org.junit.Test; +import org.springframework.data.mongodb.core.query.Criteria; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link FacetOperation}. + * + * @author Mark Paluch + * @soundtrack Stanley Foort - You Make Me Believe In Magic (Extended Mix) + */ +public class FacetOperationUnitTests { + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderCorrectly() throws Exception { + + FacetOperation facetOperation = new FacetOperation() + .and(match(Criteria.where("price").exists(true)), // + bucket("price") // + .withBoundaries(0, 150, 200, 300, 400) // + .withDefaultBucket("Other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles")) // + .as("categorizedByPrice") // + .and(bucketAuto("year", 5)).as("categorizedByYears"); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $facet: { categorizedByPrice: [" + "{ $match: { price: { $exists: true } } }, " + + "{ $bucket: { boundaries: [ 0, 150, 200, 300, 400 ], groupBy: \"$price\", default: \"Other\", " + + "output: { count: { $sum: 1 }, titles: { $push: \"$title\" } } } } ]," + + "categorizedByYears: [ { $bucketAuto: { buckets: 5, groupBy: \"$year\" } } ] } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldRenderEmpty() throws Exception { + + FacetOperation facetOperation = facet(); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{ $facet: { } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test(expected = IllegalArgumentException.class) + public void shouldRejectNonExistingFields() throws Exception { + + FacetOperation facetOperation = new FacetOperation() + .and(project("price"), // + bucket("price") // + .withBoundaries(0, 150, 200, 300, 400) // + .withDefaultBucket("Other") // + .andOutputCount().as("count") // + .andOutput("title").push().as("titles")) // + .as("categorizedByPrice"); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $facet: { categorizedByPrice: [" + "{ $match: { price: { $exists: true } } }, " + + "{ $bucket: {boundaries: [ 0, 150, 200, 300, 400 ], groupBy: \"$price\", default: \"Other\", " + + "output: { count: { $sum: 1 }, titles: { $push: \"$title\" } } } } ]," + + "categorizedByYears: [ { $bucketAuto: { buckets: 5, groupBy: \"$year\" } } ] } }"))); + } + + /** + * @see DATAMONGO-1552 + */ + @Test + public void shouldHonorProjectedFields() { + + FacetOperation facetOperation = new FacetOperation() + .and(project("price").and("title").as("name"), // + bucketAuto("price", 5) // + .andOutput("name").push().as("titles")) // + .as("categorizedByPrice"); + + DBObject dbObject = facetOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, + is(JSON.parse("{ $facet: { categorizedByPrice: [" + "{ $project: { price: 1, name: \"$title\" } }, " + + "{ $bucketAuto: { buckets: 5, groupBy: \"$price\", " + + "output: { titles: { $push: \"$name\" } } } } ] } }"))); + } +}