DATAMONGO-1552 - Add $facet aggregation stage.
Original Pull Request: #426
This commit is contained in:
committed by
Christoph Strobl
parent
b7a0b1d523
commit
450549150d
@@ -28,6 +28,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;
|
||||
@@ -66,7 +67,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<AggregationOperation> operations;
|
||||
@@ -458,6 +459,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}.
|
||||
*
|
||||
@@ -549,26 +569,7 @@ public class Aggregation {
|
||||
*/
|
||||
public Document toDocument(String inputCollectionName, AggregationOperationContext rootContext) {
|
||||
|
||||
AggregationOperationContext context = rootContext;
|
||||
List<Document> operationDocuments = new ArrayList<Document>(operations.size());
|
||||
|
||||
for (AggregationOperation operation : operations) {
|
||||
|
||||
operationDocuments.add(operation.toDocument(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<Document> operationDocuments = AggregationOperationRenderer.toDocument(operations, rootContext);
|
||||
|
||||
Document command = new Document("aggregate", inputCollectionName);
|
||||
command.put("pipeline", operationDocuments);
|
||||
@@ -584,43 +585,7 @@ public class Aggregation {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return SerializationUtils
|
||||
.serializeToJsonSafely(toDocument("__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.Document)
|
||||
*/
|
||||
@Override
|
||||
public Document getMappedObject(Document document) {
|
||||
return document;
|
||||
}
|
||||
|
||||
/*
|
||||
* (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(toDocument("__collection__", DEFAULT_CONTEXT));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -660,7 +625,7 @@ public class Aggregation {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Enum#toString()
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.bson.Document;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Rendering support for {@link AggregationOperation} into a {@link List} of {@link com.mongodb.Document}.
|
||||
*
|
||||
* @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 Document} representation.
|
||||
*
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @return the {@link List} of {@link Document}.
|
||||
*/
|
||||
static List<Document> toDocument(List<AggregationOperation> operations, AggregationOperationContext rootContext) {
|
||||
|
||||
List<Document> operationDocuments = new ArrayList<Document>(operations.size());
|
||||
|
||||
AggregationOperationContext contextToUse = rootContext;
|
||||
|
||||
for (AggregationOperation operation : operations) {
|
||||
|
||||
operationDocuments.add(operation.toDocument(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(org.bson.Document)
|
||||
*/
|
||||
@Override
|
||||
public Document getMappedObject(Document document) {
|
||||
return document;
|
||||
}
|
||||
|
||||
/*
|
||||
* (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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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 org.bson.Document;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework {@code $facet}-operation.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* As of MongoDB 3.4, {@link FacetOperation} cannot be used with nested pipelines containing {@link GeoNearOperation},
|
||||
* {@link OutOperation} and {@link FacetOperation}.
|
||||
* <p>
|
||||
* 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}.
|
||||
* <p>
|
||||
* {@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#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
return new Document("$facet", facets.toDocument(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<AggregationOperation> operations;
|
||||
|
||||
private FacetOperationBuilder(Facets current, List<AggregationOperation> 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.<Facet> emptyList());
|
||||
|
||||
private List<Facet> facets;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Facets} given {@link List} of {@link Facet}.
|
||||
*
|
||||
* @param facets
|
||||
*/
|
||||
private Facets(List<Facet> 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 Document toDocument(AggregationOperationContext context) {
|
||||
|
||||
Document document = new Document();
|
||||
|
||||
for (Facet facet : facets) {
|
||||
document.put(facet.getExposedField().getName(), facet.toDocuments(context));
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<AggregationOperation> operations) {
|
||||
|
||||
Assert.hasText(fieldName, "FieldName must not be null or empty!");
|
||||
Assert.notNull(operations, "AggregationOperations must not be null!");
|
||||
|
||||
List<Facet> facets = new ArrayList<Facet>(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<AggregationOperation> 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<AggregationOperation> 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<Document> toDocuments(AggregationOperationContext context) {
|
||||
return AggregationOperationRenderer.toDocument(operations, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1741,6 +1741,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<Art> aggregation = newAggregation(Art.class, //
|
||||
project("title", "artist", "year", "price"), //
|
||||
facet(bucketPrice).as("categorizeByPrice") //
|
||||
.and(bucketAuto("year", 3)).as("categorizeByYear"));
|
||||
|
||||
AggregationResults<Document> result = mongoTemplate.aggregate(aggregation, Document.class);
|
||||
assertThat(result.getMappedResults().size(), is(1));
|
||||
|
||||
Document 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}]
|
||||
List<Object> categorizeByPrice = (List<Object>) 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}]
|
||||
List<Object> categorizeByYear = (List<Object>) mappedResult.get("categorizeByYear");
|
||||
assertThat(categorizeByYear, hasSize(3));
|
||||
}
|
||||
|
||||
private void createUsersWithReferencedPersons() {
|
||||
|
||||
mongoTemplate.dropCollection(User.class);
|
||||
|
||||
@@ -573,6 +573,23 @@ public class AggregationUnitTests {
|
||||
isBsonObject().containing("$ifNull", Arrays.asList("$chroma", "$fallback")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-1552
|
||||
*/
|
||||
@Test
|
||||
public void shouldHonorDefaultCountField() {
|
||||
|
||||
Document agg = Aggregation
|
||||
.newAggregation(//
|
||||
bucket("year"), //
|
||||
project("count")) //
|
||||
.toDocument("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
Document project = extractPipelineElement(agg, 1, "$project");
|
||||
|
||||
assertThat(project, isBsonObject().containing("count", 1));
|
||||
}
|
||||
|
||||
private Document extractPipelineElement(Document agg, int index, String operation) {
|
||||
|
||||
List<Document> pipeline = (List<Document>) agg.get("pipeline");
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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 org.bson.Document;
|
||||
|
||||
/**
|
||||
* 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");
|
||||
|
||||
Document agg = facetOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg,
|
||||
is(Document.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();
|
||||
|
||||
Document agg = facetOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg, is(Document.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");
|
||||
|
||||
Document agg = facetOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg,
|
||||
is(Document.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");
|
||||
|
||||
Document agg = facetOperation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(agg,
|
||||
is(Document.parse("{ $facet: { categorizedByPrice: [" + "{ $project: { price: 1, name: \"$title\" } }, "
|
||||
+ "{ $bucketAuto: { buckets: 5, groupBy: \"$price\", "
|
||||
+ "output: { titles: { $push: \"$name\" } } } } ] } }")));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user