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 c6f13241f..23ea78cd1 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 @@ -47,6 +47,7 @@ import org.springframework.util.Assert; * @author Christoph Strobl * @author Nikolay Bogdanov * @author Gustavo de Geus + * @author Jérôme Guyon * @since 1.3 */ public class Aggregation { @@ -480,6 +481,26 @@ public class Aggregation { return new BucketAutoOperation(groupByExpression, buckets); } + /** + * Creates a new {@link SortByCountOperation} given {@literal groupByField} + * + * @param groupByField must not be {@literal null} or empty. + * @return + */ + public static SortByCountOperation sortByCount(String groupByField) { + return new SortByCountOperation(field(groupByField)); + } + + /** + * Creates a new {@link SortByCountOperation} given {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + * @return + */ + public static SortByCountOperation sortByCount(AggregationExpression groupByExpression) { + return new SortByCountOperation(groupByExpression); + } + /** * Creates a new {@link FacetOperation}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortByCountOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortByCountOperation.java new file mode 100644 index 000000000..d5d33ad2b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SortByCountOperation.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017 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 org.bson.Document; +import org.springframework.util.Assert; + +/** + * Encapsulates the aggregation framework {@code $sortByCount}-operation.
+ * + * SortByCount stage is typically used with {@link Aggregation} and {@code $facet}. Groups incoming documents + * based on the value of a specified expression, then computes the count of documents in each distinct group.
+ * + * We recommend to use the static factory method {@link Aggregation#sortByCount(String)} instead of creating instances + * of this class directly. + * + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/sortByCount/ + * @author Jérôme GUYON + */ +public class SortByCountOperation implements AggregationOperation { + + private final Field groupByField; + private final AggregationExpression groupByExpression; + + + /** + * Creates a new {@link SortByCountOperation} given a {@link Field group-by field}. + * + * @param groupByField must not be {@literal null}. + */ + public SortByCountOperation(Field groupByField) { + + Assert.notNull(groupByField, "Group by field must not be null!"); + + this.groupByField = groupByField; + this.groupByExpression = null; + } + + /** + * Creates a new {@link SortByCountOperation} given a {@link AggregationExpression group-by expression}. + * + * @param groupByExpression must not be {@literal null}. + */ + public SortByCountOperation(AggregationExpression groupByExpression) { + + Assert.notNull(groupByExpression, "Group by AggregationExpression must not be null!"); + + this.groupByExpression = groupByExpression; + this.groupByField = null; + } + + @Override + public Document toDocument(AggregationOperationContext context) { + return new Document("$sortByCount", + groupByExpression == null ? context.getReference(groupByField).toString() + : groupByExpression.toDocument(context) + ); + } +} 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 index 8adabcfb3..886b6b2cc 100644 --- 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 @@ -28,6 +28,7 @@ import org.bson.Document; * Unit tests for {@link FacetOperation}. * * @author Mark Paluch + * @author Jérôme Guyon * @soundtrack Stanley Foort - You Make Me Believe In Magic (Extended Mix) */ public class FacetOperationUnitTests { @@ -101,4 +102,17 @@ public class FacetOperationUnitTests { + "{ $bucketAuto: { buckets: 5, groupBy: \"$price\", " + "output: { titles: { $push: \"$name\" } } } } ] } }"))); } + + @Test // DATAMONGO-1553 + public void shouldRenderSortByCountCorrectly() { + + FacetOperation facetOperation = new FacetOperation() + .and(sortByCount("country")) + .as("categorizedByCountry"); + + Document agg = facetOperation.toDocument(Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, + is(Document.parse("{ $facet: { categorizedByCountry: [{ $sortByCount: \"$country\" } ] } }"))); + } } diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index f3afc8336..1da380849 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1914,6 +1914,19 @@ More examples for project operations can be found in the `AggregationTests` clas MongoDB supports as of Version 3.4 faceted classification using the Aggregation Framework. A faceted classification uses semantic categories, either general or subject-specific, that are combined to create the full classification entry. Documents flowing through the aggregation pipeline are classificated into buckets. A multi-faceted classification enables various aggregations on the same set of input documents, without needing to retrieve the input documents multiple times. +==== SortByCount + +SortByCount operations groups incoming documents based on the value of a specified expression, then computes the count of documents in each distinct group. SortVyCount operations require a grouping field or grouping expression. They can be defined via the `sortByCount()` methods of the `Aggregate` class. + +.SortByCount operation example +==== +[source,java] +---- +// will generate { $sortByCount: "$country" } +sortByCount("country"); +---- +==== + ==== Buckets Bucket operations categorize incoming documents into groups, called buckets, based on a specified expression and bucket boundaries. Bucket operations require a grouping field or grouping expression. They can be defined via the `bucket()`/`bucketAuto()` methods of the `Aggregate` class. `BucketOperation` and `BucketAutoOperation` can expose accumulations based on aggregation expressions for input documents. The bucket operation can be extended with additional parameters through a fluent API via the `with…()` methods, the `andOutput(String)` method and aliased via the `as(String)` method. Each bucket is represented as a document in the output. @@ -1975,6 +1988,9 @@ Sub-pipelines can project and filter input documents prior grouping. Common case // will generate {$facet: {categorizedByPrice: [ { $match: { price: {$exists : true}}}, { $bucketAuto: {groupBy: $price, buckets: 5}}]}} facet(match(Criteria.where("price").exists(true)), bucketAuto("price", 5)).as("categorizedByPrice")) +// will generate {$facet: {categorizedByCountry: [ { $match: { country: {$exists : true}}}, { $sortByCount: "$country"}]}} +facet(match(Criteria.where("country").exists(true)), sortByCount("country")).as("categorizedByCountry")) + // will generate {$facet: {categorizedByYear: [ // { $project: { title: 1, publicationYear: { $year: "publicationDate"}}}, // { $bucketAuto: {groupBy: $price, buckets: 5, output: { titles: {$push:"$title"}}}