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 1c98ebd315..9eaf19541d 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 @@ -1,3 +1,4 @@ +/* /* * Copyright 2013-2016 the original author or authors. * @@ -44,6 +45,7 @@ * @author Oliver Gierke * @author Mark Paluch * @author Alessio Fachechi + * @author Nikolay Bogdanov * @since 1.3 */ public class Aggregation { @@ -159,6 +161,13 @@ protected Aggregation(List aggregationOperations, Aggregat Assert.isTrue(!aggregationOperations.isEmpty(), "At least one AggregationOperation has to be provided"); Assert.notNull(options, "AggregationOptions must not be null!"); + //check $out is the last operation if exist + for (int i = 0; i < aggregationOperations.size(); i++) { + if (aggregationOperations.get(i) instanceof OutOperation && i != aggregationOperations.size() - 1) { + throw new IllegalArgumentException("The $out operator must be the last stage in the pipeline."); + } + } + this.operations = aggregationOperations; this.options = options; } @@ -273,6 +282,20 @@ public static MatchOperation match(Criteria criteria) { return new MatchOperation(criteria); } + /** + * Creates a new {@link OutOperation} using the given collection name. This operation must be the last operation + * in the pipeline. + * + * @param outCollectionName collection name to export aggregation results. The {@link OutOperation} creates a new + * collection in the current database if one does not already exist. The collection is + * not visible until the aggregation completes. If the aggregation fails, MongoDB does + * not create the collection. Must not be {@literal null}. + * @return + */ + public static OutOperation out(String outCollectionName) { + return new OutOperation(outCollectionName); + } + /** * Creates a new {@link LookupOperation}. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java new file mode 100644 index 0000000000..68b357a62e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java @@ -0,0 +1,51 @@ +/* + * 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 com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import org.springframework.util.Assert; + +/** + * Encapsulates the {@code $out}-operation. + *

+ * We recommend to use the static factory method {@link Aggregation#out(String)} instead of creating instances of this + * class directly. + * + * @see http://docs.mongodb.org/manual/reference/aggregation/out/ + * @author Nikolay Bogdanov + */ +public class OutOperation implements AggregationOperation { + + private final String collectionName; + + /** + * @param outCollectionName Collection name to export the results. Must not be {@literal null}. + */ + public OutOperation(String outCollectionName) { + Assert.notNull(outCollectionName, "Collection name must not be null!"); + this.collectionName = outCollectionName; + } + + /* + * (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("$out", collectionName); + } +} 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 7a23b03782..eaecdb2f77 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 @@ -78,6 +78,7 @@ * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch + * @author Nikolay Bogdanov */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:infrastructure.xml") @@ -1120,6 +1121,40 @@ public void shouldGroupByAndLookupPeopleCorectly() { assertThat(firstItem, isBsonObject().containing("linkedPerson.[0].firstname", "u1")); } + /** + * @see DATAMONGO-1418 + */ + @Test + public void shouldCreateOutputCollection() { + + assumeTrue(mongoVersion.isGreaterThanOrEqualTo(TWO_DOT_SIX)); + mongoTemplate.save(new Person("Anna", "Ivanova", 21, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Pavel", "Sidorov", 36, Person.Sex.MALE)); + mongoTemplate.save(new Person("Anastasia", "Volochkova", 29, Person.Sex.FEMALE)); + mongoTemplate.save(new Person("Igor", "Stepanov", 31, Person.Sex.MALE)); + mongoTemplate.save(new Person("Leoniv", "Yakubov", 55, Person.Sex.MALE)); + + String tempOutCollection = "personQueryTemp"; + TypedAggregation agg = newAggregation(Person.class, + group("sex") + .count().as("count"), + sort(DESC, "count"), + out(tempOutCollection)); + + AggregationResults results = mongoTemplate.aggregate(agg, DBObject.class); + assertTrue(results.getMappedResults().isEmpty()); + + List list = mongoTemplate.findAll(DBObject.class, tempOutCollection); + assertEquals("Size incorrect in temp collection", 2, list.size()); + DBObject first = list.get(0); + DBObject second = list.get(1); + assertThat(first, isBsonObject().containing("_id", "MALE") + .containing("count", 3)); + assertThat(second, isBsonObject().containing("_id", "FEMALE") + .containing("count", 2)); + mongoTemplate.dropCollection(tempOutCollection); + } + private void createUsersWithReferencedPersons() { mongoTemplate.dropCollection(User.class); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java new file mode 100644 index 0000000000..611ad319ef --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java @@ -0,0 +1,45 @@ +/* + * 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.junit.Test; +import org.springframework.data.mongodb.core.query.Criteria; + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +/** + * Unit tests for {@link OutOperation}. + * + * @author Nikolay Bogdanov + */ +public class OutOperationUnitTest { + + @Test(expected = IllegalArgumentException.class) + public void shouldOutBeTheLastOperation() { + newAggregation( + match(new Criteria()), + group("field1") + .count().as("totalCount"), + out("collection1"), + skip(100), + limit(50)); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldCheckNPEInCreation() { + new OutOperation(null); + } +}