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 ff9ec46d1..fe8a0f968 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 @@ -23,6 +23,7 @@ import java.util.List; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.mongodb.core.aggregation.CountOperation.CountOperationBuilder; 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; @@ -396,6 +397,16 @@ public class Aggregation { return new LookupOperation(from, localField, foreignField, as); } + /** + * Creates a new {@link CountOperationBuilder}. + * + * @return never {@literal null}. + * @since 1.10 + */ + public static CountOperationBuilder count() { + return new CountOperationBuilder(); + } + /** * Creates a new {@link Fields} instance for the given field names. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java new file mode 100644 index 000000000..ed4c274c2 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/CountOperation.java @@ -0,0 +1,82 @@ +/* + * 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 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 $count}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#count()} instead of creating instances of this + * class directly. + * + * @see https://docs.mongodb.com/manual/reference/operator/aggregation/count/#pipe._S_count + * @author Mark Paluch + * @since 1.10 + */ +public class CountOperation implements FieldsExposingAggregationOperation { + + private final String fieldName; + + /** + * Creates a new {@link CountOperation} given the {@link fieldName} field name. + * + * @param asFieldName must not be {@literal null} or empty. + */ + public CountOperation(String fieldName) { + + Assert.hasText(fieldName, "Field name must not be null or empty!"); + this.fieldName = fieldName; + } + + /* (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("$count", fieldName); + } + + /* (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields() + */ + @Override + public ExposedFields getFields() { + return ExposedFields.from(new ExposedField(fieldName, true)); + } + + /** + * Builder for {@link CountOperation}. + * + * @author Mark Paluch + */ + public static class CountOperationBuilder { + + /** + * Returns the finally to be applied {@link CountOperation} with the given alias. + * + * @param fieldName must not be {@literal null} or empty. + * @return + */ + public CountOperation as(String fieldName) { + return new CountOperation(fieldName); + } + } +} 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 d89d1a782..fffe2d304 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 @@ -100,6 +100,7 @@ public class AggregationTests { private static final Version TWO_DOT_FOUR = new Version(2, 4); private static final Version TWO_DOT_SIX = new Version(2, 6); private static final Version THREE_DOT_TWO = new Version(3, 2); + private static final Version THREE_DOT_FOUR = new Version(3, 4); private static boolean initialized = false; @@ -1298,6 +1299,30 @@ public class AggregationTests { assertThat(result.getMappedResults(), hasSize(2)); } + /** + * @see DATAMONGO-1549 + */ + @Test + public void shouldApplyCountCorrectly() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(THREE_DOT_FOUR)); + + mongoTemplate.save(new Reservation("0123", "42", 100)); + mongoTemplate.save(new Reservation("0360", "43", 200)); + mongoTemplate.save(new Reservation("0360", "44", 300)); + + Aggregation agg = newAggregation( // + count().as("documents"), // + project("documents") // + .andExpression("documents * 2").as("twice")); + AggregationResults result = mongoTemplate.aggregate(agg, Reservation.class, DBObject.class); + + assertThat(result.getMappedResults(), hasSize(1)); + + DBObject dbObject = result.getMappedResults().get(0); + assertThat(dbObject, isBsonObject().containing("documents", 3).containing("twice", 6)); + } + /** * @see DATAMONGO-975 */ diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java new file mode 100644 index 000000000..2b60442f1 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/CountOperationUnitTests.java @@ -0,0 +1,65 @@ +/* + * 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.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.mongodb.DBObject; +import com.mongodb.util.JSON; + +/** + * Unit tests for {@link CountOperation}. + * + * @author Mark Paluch + */ +public class CountOperationUnitTests { + + /** + * @see DATAMONGO-1549 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsEmptyFieldName() { + new CountOperation(""); + } + + /** + * @see DATAMONGO-1549 + */ + @Test + public void shouldRenderCorrectly() { + + CountOperation countOperation = new CountOperation("field"); + DBObject dbObject = countOperation.toDBObject(Aggregation.DEFAULT_CONTEXT); + + assertThat(dbObject, is(JSON.parse("{$count : \"field\" }"))); + } + + /** + * @see DATAMONGO-1549 + */ + @Test + public void countExposesFields() { + + CountOperation countOperation = new CountOperation("field"); + + assertThat(countOperation.getFields().exposesNoFields(), is(false)); + assertThat(countOperation.getFields().exposesSingleFieldOnly(), is(true)); + assertThat(countOperation.getFields().getField("field"), notNullValue()); + } +}