diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperation.java
new file mode 100644
index 000000000..508024e2e
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperation.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2018 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;
+
+import java.util.List;
+
+import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
+import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
+import org.springframework.data.mongodb.core.query.Query;
+
+/**
+ * {@link ExecutableMapReduceOperation} allows creation and execution of MongoDB mapReduce operations in a fluent API
+ * style. The starting {@literal domainType} is used for mapping an optional {@link Query} provided via {@code matching}
+ * into the MongoDB specific representation. By default, the originating {@literal domainType} is also used for mapping
+ * back the results from the {@link org.bson.Document}. However, it is possible to define an different
+ * {@literal returnType} via {@code as} to mapping the result.
+ * The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there
+ * via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
+ * collection name for the execution.
+ *
+ *
+ *
+ * mapReduce(Human.class)
+ * .map("function() { emit(this.id, this.firstname) }")
+ * .reduce("function(id, name) { return sum(id, name); }")
+ * .inCollection("star-wars")
+ * .as(Jedi.class)
+ * .matching(query(where("lastname").is("skywalker")))
+ * .all();
+ *
+ *
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+public interface ExecutableMapReduceOperation {
+
+ /**
+ * Start creating a mapReduce operation for the given {@literal domainType}.
+ *
+ * @param domainType must not be {@literal null}.
+ * @return new instance of {@link ExecutableFind}.
+ * @throws IllegalArgumentException if domainType is {@literal null}.
+ */
+ MapReduceWithMapFunction mapReduce(Class domainType);
+
+ /**
+ * Trigger mapReduce execution by calling one of the terminating methods.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface TerminatingMapReduce {
+
+ /**
+ * Get the mapReduce results.
+ *
+ * @return never {@literal null}.
+ */
+ List all();
+ }
+
+ /**
+ * Provide the Javascript {@code function()} used to map matching documents.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithMapFunction {
+
+ /**
+ * Set the Javascript map {@code function()}.
+ *
+ * @param mapFunction must not be {@literal null} nor empty.
+ * @return new instance of {@link MapReduceWithReduceFunction}.
+ * @throws IllegalArgumentException if {@literal mapFunction} is {@literal null} or empty.
+ */
+ MapReduceWithReduceFunction map(String mapFunction);
+
+ }
+
+ /**
+ * Provide the Javascript {@code function()} used to reduce matching documents.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithReduceFunction {
+
+ /**
+ * Set the Javascript map {@code function()}.
+ *
+ * @param reduceFunction must not be {@literal null} nor empty.
+ * @return new instance of {@link ExecutableMapReduce}.
+ * @throws IllegalArgumentException if {@literal reduceFunction} is {@literal null} or empty.
+ */
+ ExecutableMapReduce reduce(String reduceFunction);
+
+ }
+
+ /**
+ * Collection override (Optional).
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithCollection extends MapReduceWithQuery {
+
+ /**
+ * Explicitly set the name of the collection to perform the mapReduce operation on.
+ * Skip this step to use the default collection derived from the domain type.
+ *
+ * @param collection must not be {@literal null} nor {@literal empty}.
+ * @return new instance of {@link MapReduceWithProjection}.
+ * @throws IllegalArgumentException if collection is {@literal null}.
+ */
+ MapReduceWithProjection inCollection(String collection);
+ }
+
+ /**
+ * Input document filter query (Optional).
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithQuery extends TerminatingMapReduce {
+
+ /**
+ * Set the filter query to be used.
+ *
+ * @param query must not be {@literal null}.
+ * @return new instance of {@link TerminatingMapReduce}.
+ * @throws IllegalArgumentException if query is {@literal null}.
+ */
+ TerminatingMapReduce matching(Query query);
+ }
+
+ /**
+ * Result type override (Optional).
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithProjection extends MapReduceWithQuery {
+
+ /**
+ * Define the target type fields should be mapped to.
+ * Skip this step if you are anyway only interested in the original domain type.
+ *
+ * @param resultType must not be {@literal null}.
+ * @param result type.
+ * @return new instance of {@link TerminatingMapReduce}.
+ * @throws IllegalArgumentException if resultType is {@literal null}.
+ */
+ MapReduceWithQuery as(Class resultType);
+ }
+
+ /**
+ * Additional mapReduce options (Optional).
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithOptions {
+
+ /**
+ * Set additional options to apply to the mapReduce operation.
+ *
+ * @param options must not be {@literal null}.
+ * @return new instance of {@link ExecutableMapReduce}.
+ * @throws IllegalArgumentException if options is {@literal null}.
+ */
+ ExecutableMapReduce with(MapReduceOptions options);
+ }
+
+ /**
+ * {@link ExecutableMapReduce} provides methods for constructing mapReduce operations in a fluent way.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface ExecutableMapReduce extends MapReduceWithMapFunction, MapReduceWithReduceFunction,
+ MapReduceWithCollection, MapReduceWithProjection, MapReduceWithOptions {
+
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupport.java
new file mode 100644
index 000000000..3e7defa53
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupport.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2018 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;
+
+import java.util.List;
+
+import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Implementation of {@link ExecutableMapReduceOperation}.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+class ExecutableMapReduceOperationSupport implements ExecutableMapReduceOperation {
+
+ private static final Query ALL_QUERY = new Query();
+
+ private final MongoTemplate template;
+
+ /**
+ * Create new {@link ExecutableMapReduceOperationSupport}.
+ *
+ * @param template must not be {@literal null}.
+ * @throws IllegalArgumentException if template is {@literal null}.
+ */
+ ExecutableMapReduceOperationSupport(MongoTemplate template) {
+
+ Assert.notNull(template, "Template must not be null!");
+ this.template = template;
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation#mapReduce(java.lang.Class)
+ */
+ @Override
+ public ExecutableMapReduceSupport mapReduce(Class domainType) {
+
+ Assert.notNull(domainType, "DomainType must not be null!");
+
+ return new ExecutableMapReduceSupport(template, domainType, domainType, null, ALL_QUERY, null, null, null);
+ }
+
+ /**
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ static class ExecutableMapReduceSupport
+ implements ExecutableMapReduce, MapReduceWithOptions, MapReduceWithCollection,
+ MapReduceWithProjection, MapReduceWithQuery, MapReduceWithReduceFunction, MapReduceWithMapFunction {
+
+ private final MongoTemplate template;
+ private final Class> domainType;
+ private final Class returnType;
+ private final @Nullable String collection;
+ private final Query query;
+ private final @Nullable String mapFunction;
+ private final @Nullable String reduceFunction;
+ private final @Nullable MapReduceOptions options;
+
+ ExecutableMapReduceSupport(MongoTemplate template, Class> domainType, Class returnType, String collection,
+ Query query, @Nullable String mapFunction, @Nullable String reduceFunction, MapReduceOptions options) {
+
+ this.template = template;
+ this.domainType = domainType;
+ this.returnType = returnType;
+ this.collection = collection;
+ this.query = query;
+ this.mapFunction = mapFunction;
+ this.reduceFunction = reduceFunction;
+ this.options = options;
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.TerminatingMapReduce#all()
+ */
+ @Override
+ public List all() {
+ return template.mapReduce(query, domainType, getCollectionName(), mapFunction, reduceFunction, options,
+ returnType);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithCollection#inCollection(java.lang.String)
+ */
+ @Override
+ public MapReduceWithProjection inCollection(String collection) {
+
+ Assert.hasText(collection, "Collection name must not be null nor empty!");
+
+ return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithQuery#query(org.springframework.data.mongodb.core.query.Query)
+ */
+ @Override
+ public TerminatingMapReduce matching(Query query) {
+
+ Assert.notNull(query, "Query must not be null!");
+
+ return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithProjection#as(java.lang.Class)
+ */
+ @Override
+ public MapReduceWithQuery as(Class resultType) {
+
+ Assert.notNull(resultType, "ResultType must not be null!");
+
+ return new ExecutableMapReduceSupport<>(template, domainType, resultType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithOptions#with(org.springframework.data.mongodb.core.mapreduce.MapReduceOptions)
+ */
+ @Override
+ public ExecutableMapReduce with(MapReduceOptions options) {
+
+ Assert.notNull(options, "Options must not be null! Please consider empty MapReduceOptions#options() instead.");
+
+ return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithMapFunction#map(java.lang.String)
+ */
+ @Override
+ public MapReduceWithReduceFunction map(String mapFunction) {
+
+ Assert.hasText(mapFunction, "MapFunction name must not be null nor empty!");
+
+ return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.MapReduceWithReduceFunction#reduce(java.lang.String)
+ */
+ @Override
+ public ExecutableMapReduce reduce(String reduceFunction) {
+
+ Assert.hasText(reduceFunction, "ReduceFunction name must not be null nor empty!");
+
+ return new ExecutableMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ private String getCollectionName() {
+ return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
+ }
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java
index ece0cb776..ff2812020 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/FluentMongoOperations.java
@@ -22,4 +22,4 @@ package org.springframework.data.mongodb.core;
* @since 2.0
*/
public interface FluentMongoOperations extends ExecutableFindOperation, ExecutableInsertOperation,
- ExecutableUpdateOperation, ExecutableRemoveOperation, ExecutableAggregationOperation {}
+ ExecutableUpdateOperation, ExecutableRemoveOperation, ExecutableAggregationOperation, ExecutableMapReduceOperation {}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
index 795464704..421ebffa2 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
@@ -71,7 +71,16 @@ import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.Fields;
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
-import org.springframework.data.mongodb.core.convert.*;
+import org.springframework.data.mongodb.core.convert.DbRefResolver;
+import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
+import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
+import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
+import org.springframework.data.mongodb.core.convert.MongoConverter;
+import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
+import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
+import org.springframework.data.mongodb.core.convert.MongoWriter;
+import org.springframework.data.mongodb.core.convert.QueryMapper;
+import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.index.IndexOperations;
import org.springframework.data.mongodb.core.index.IndexOperationsProvider;
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
@@ -132,7 +141,16 @@ import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
-import com.mongodb.client.model.*;
+import com.mongodb.client.model.CountOptions;
+import com.mongodb.client.model.CreateCollectionOptions;
+import com.mongodb.client.model.DeleteOptions;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.FindOneAndDeleteOptions;
+import com.mongodb.client.model.FindOneAndUpdateOptions;
+import com.mongodb.client.model.ReturnDocument;
+import com.mongodb.client.model.UpdateOptions;
+import com.mongodb.client.model.ValidationAction;
+import com.mongodb.client.model.ValidationLevel;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.mongodb.session.ClientSession;
@@ -1798,9 +1816,29 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public MapReduceResults mapReduce(Query query, String inputCollectionName, String mapFunction,
String reduceFunction, @Nullable MapReduceOptions mapReduceOptions, Class entityClass) {
+ return new MapReduceResults(
+ mapReduce(query, entityClass, inputCollectionName, mapFunction, reduceFunction, mapReduceOptions, entityClass),
+ new Document());
+ }
+
+ /**
+ * @param query
+ * @param domainType
+ * @param inputCollectionName
+ * @param mapFunction
+ * @param reduceFunction
+ * @param mapReduceOptions
+ * @param resultType
+ * @param
+ * @return
+ * @since 2.1
+ */
+ public List mapReduce(Query query, Class> domainType, String inputCollectionName, String mapFunction,
+ String reduceFunction, @Nullable MapReduceOptions mapReduceOptions, Class resultType) {
+
Assert.notNull(query, "Query must not be null!");
Assert.notNull(inputCollectionName, "InputCollectionName must not be null!");
- Assert.notNull(entityClass, "EntityClass must not be null!");
+ Assert.notNull(resultType, "EntityClass must not be null!");
Assert.notNull(reduceFunction, "ReduceFunction must not be null!");
Assert.notNull(mapFunction, "MapFunction must not be null!");
@@ -1818,9 +1856,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (query.getMeta() != null && query.getMeta().getMaxTimeMsec() != null) {
result = result.maxTime(query.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS);
}
- result = result.sort(getMappedSortObject(query, entityClass));
+ result = result.sort(getMappedSortObject(query, domainType));
- result = result.filter(queryMapper.getMappedObject(query.getQueryObject(), Optional.empty()));
+ result = result
+ .filter(queryMapper.getMappedObject(query.getQueryObject(), mappingContext.getPersistentEntity(domainType)));
}
Optional collation = query.getCollation();
@@ -1856,13 +1895,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
result = collation.map(Collation::toMongoCollation).map(result::collation).orElse(result);
List mappedResults = new ArrayList();
- DocumentCallback callback = new ReadDocumentCallback(mongoConverter, entityClass, inputCollectionName);
+ DocumentCallback callback = new ReadDocumentCallback(mongoConverter, resultType, inputCollectionName);
for (Document document : result) {
mappedResults.add(callback.doWith(document));
}
- return new MapReduceResults(mappedResults, new Document());
+ return mappedResults;
}
public GroupByResults group(String inputCollectionName, GroupBy groupBy, Class entityClass) {
@@ -2184,6 +2223,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return new ExecutableAggregationOperationSupport(this).aggregateAndReturn(domainType);
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.ExecutableAggregationOperation#aggregateAndReturn(java.lang.Class)
+ */
+ @Override
+ public ExecutableMapReduce mapReduce(Class domainType) {
+ return new ExecutableMapReduceOperationSupport(this).mapReduce(domainType);
+ }
+
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.ExecutableInsertOperation#insert(java.lang.Class)
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java
index 85ebb5973..15936c65d 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveFluentMongoOperations.java
@@ -19,7 +19,8 @@ package org.springframework.data.mongodb.core;
* Stripped down interface providing access to a fluent API that specifies a basic set of reactive MongoDB operations.
*
* @author Mark Paluch
+ * @author Christoph Strobl
* @since 2.0
*/
public interface ReactiveFluentMongoOperations extends ReactiveFindOperation, ReactiveInsertOperation,
- ReactiveUpdateOperation, ReactiveRemoveOperation, ReactiveAggregationOperation {}
+ ReactiveUpdateOperation, ReactiveRemoveOperation, ReactiveAggregationOperation, ReactiveMapReduceOperation {}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperation.java
new file mode 100644
index 000000000..fca611f74
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperation.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2018 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;
+
+import reactor.core.publisher.Flux;
+
+import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
+import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
+import org.springframework.data.mongodb.core.query.Query;
+
+/**
+ * {@link ReactiveMapReduceOperation} allows creation and execution of MongoDB mapReduce operations in a fluent API
+ * style. The starting {@literal domainType} is used for mapping an optional {@link Query} provided via {@code matching}
+ * into the MongoDB specific representation. By default, the originating {@literal domainType} is also used for mapping
+ * back the results from the {@link org.bson.Document}. However, it is possible to define an different
+ * {@literal returnType} via {@code as} to mapping the result.
+ * The collection to operate on is by default derived from the initial {@literal domainType} and can be defined there
+ * via {@link org.springframework.data.mongodb.core.mapping.Document}. Using {@code inCollection} allows to override the
+ * collection name for the execution.
+ *
+ *
+ *
+ * mapReduce(Human.class)
+ * .map("function() { emit(this.id, this.firstname) }")
+ * .reduce("function(id, name) { return sum(id, name); }")
+ * .inCollection("star-wars")
+ * .as(Jedi.class)
+ * .matching(query(where("lastname").is("skywalker")))
+ * .all();
+ *
+ *
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+public interface ReactiveMapReduceOperation {
+
+ /**
+ * Start creating a mapReduce operation for the given {@literal domainType}.
+ *
+ * @param domainType must not be {@literal null}.
+ * @return new instance of {@link ExecutableFind}.
+ * @throws IllegalArgumentException if domainType is {@literal null}.
+ */
+ MapReduceWithMapFunction mapReduce(Class domainType);
+
+ /**
+ * Trigger mapReduce execution by calling one of the terminating methods.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface TerminatingMapReduce {
+
+ /**
+ * Get the {@link Flux} emitting mapReduce results.
+ *
+ * @return a {@link Flux} emitting the already mapped operation results.
+ */
+ Flux all();
+ }
+
+ /**
+ * Provide the Javascript {@code function()} used to map matching documents.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithMapFunction {
+
+ /**
+ * Set the Javascript map {@code function()}.
+ *
+ * @param mapFunction must not be {@literal null} nor empty.
+ * @return new instance of {@link MapReduceWithReduceFunction}.
+ * @throws IllegalArgumentException if {@literal mapFunction} is {@literal null} or empty.
+ */
+ MapReduceWithReduceFunction map(String mapFunction);
+
+ }
+
+ /**
+ * Provide the Javascript {@code function()} used to reduce matching documents.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithReduceFunction {
+
+ /**
+ * Set the Javascript map {@code function()}.
+ *
+ * @param reduceFunction must not be {@literal null} nor empty.
+ * @return new instance of {@link ReactiveMapReduce}.
+ * @throws IllegalArgumentException if {@literal reduceFunction} is {@literal null} or empty.
+ */
+ ReactiveMapReduce reduce(String reduceFunction);
+
+ }
+
+ /**
+ * Collection override (Optional).
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithCollection extends MapReduceWithQuery {
+
+ /**
+ * Explicitly set the name of the collection to perform the mapReduce operation on.
+ * Skip this step to use the default collection derived from the domain type.
+ *
+ * @param collection must not be {@literal null} nor {@literal empty}.
+ * @return new instance of {@link MapReduceWithProjection}.
+ * @throws IllegalArgumentException if collection is {@literal null}.
+ */
+ MapReduceWithProjection inCollection(String collection);
+ }
+
+ /**
+ * Input document filter query (Optional).
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithQuery extends TerminatingMapReduce {
+
+ /**
+ * Set the filter query to be used.
+ *
+ * @param query must not be {@literal null}.
+ * @return new instance of {@link TerminatingMapReduce}.
+ * @throws IllegalArgumentException if query is {@literal null}.
+ */
+ TerminatingMapReduce matching(Query query);
+ }
+
+ /**
+ * Result type override (Optional).
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithProjection extends MapReduceWithQuery {
+
+ /**
+ * Define the target type fields should be mapped to.
+ * Skip this step if you are anyway only interested in the original domain type.
+ *
+ * @param resultType must not be {@literal null}.
+ * @param result type.
+ * @return new instance of {@link TerminatingMapReduce}.
+ * @throws IllegalArgumentException if resultType is {@literal null}.
+ */
+ MapReduceWithQuery as(Class resultType);
+ }
+
+ /**
+ * Additional mapReduce options (Optional).
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface MapReduceWithOptions {
+
+ /**
+ * Set additional options to apply to the mapReduce operation.
+ *
+ * @param options must not be {@literal null}.
+ * @return new instance of {@link ReactiveMapReduce}.
+ * @throws IllegalArgumentException if options is {@literal null}.
+ */
+ ReactiveMapReduce with(MapReduceOptions options);
+ }
+
+ /**
+ * {@link ReactiveMapReduce} provides methods for constructing reactive mapReduce operations in a fluent way.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ interface ReactiveMapReduce extends MapReduceWithMapFunction, MapReduceWithReduceFunction,
+ MapReduceWithCollection, MapReduceWithProjection, MapReduceWithOptions {
+
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupport.java
new file mode 100644
index 000000000..c1a48c4a4
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupport.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2018 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;
+
+import reactor.core.publisher.Flux;
+
+import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Implementation of {@link ReactiveMapReduceOperation}.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+class ReactiveMapReduceOperationSupport implements ReactiveMapReduceOperation {
+
+ private static final Query ALL_QUERY = new Query();
+
+ private final ReactiveMongoTemplate template;
+
+ /**
+ * Create new {@link ReactiveMapReduceOperationSupport}.
+ *
+ * @param template must not be {@literal null}.
+ * @throws IllegalArgumentException if template is {@literal null}.
+ */
+ ReactiveMapReduceOperationSupport(ReactiveMongoTemplate template) {
+
+ Assert.notNull(template, "Template must not be null!");
+ this.template = template;
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation#mapReduce(java.lang.Class)
+ */
+ @Override
+ public ReactiveMapReduceSupport mapReduce(Class domainType) {
+
+ Assert.notNull(domainType, "DomainType must not be null!");
+
+ return new ReactiveMapReduceSupport(template, domainType, domainType, null, ALL_QUERY, null, null, null);
+ }
+
+ /**
+ * @author Christoph Strobl
+ * @since 2.1
+ */
+ static class ReactiveMapReduceSupport
+ implements ReactiveMapReduce, MapReduceWithOptions, MapReduceWithCollection, MapReduceWithProjection,
+ MapReduceWithQuery, MapReduceWithReduceFunction, MapReduceWithMapFunction {
+
+ private final ReactiveMongoTemplate template;
+ private final Class> domainType;
+ private final Class returnType;
+ private final @Nullable String collection;
+ private final Query query;
+ private final @Nullable String mapFunction;
+ private final @Nullable String reduceFunction;
+ private final @Nullable MapReduceOptions options;
+
+ ReactiveMapReduceSupport(ReactiveMongoTemplate template, Class> domainType, Class returnType,
+ String collection, Query query, @Nullable String mapFunction, @Nullable String reduceFunction,
+ MapReduceOptions options) {
+
+ this.template = template;
+ this.domainType = domainType;
+ this.returnType = returnType;
+ this.collection = collection;
+ this.query = query;
+ this.mapFunction = mapFunction;
+ this.reduceFunction = reduceFunction;
+ this.options = options;
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ExecutableMapReduceOperation.TerminatingMapReduce#all()
+ */
+ @Override
+ public Flux all() {
+
+ return template.mapReduce(query, domainType, getCollectionName(), returnType, mapFunction, reduceFunction,
+ options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithCollection#inCollection(java.lang.String)
+ */
+ @Override
+ public MapReduceWithProjection inCollection(String collection) {
+
+ Assert.hasText(collection, "Collection name must not be null nor empty!");
+
+ return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithQuery#query(org.springframework.data.mongodb.core.query.Query)
+ */
+ @Override
+ public TerminatingMapReduce matching(Query query) {
+
+ Assert.notNull(query, "Query must not be null!");
+
+ return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithProjection#as(java.lang.Class)
+ */
+ @Override
+ public MapReduceWithQuery as(Class resultType) {
+
+ Assert.notNull(resultType, "ResultType must not be null!");
+
+ return new ReactiveMapReduceSupport<>(template, domainType, resultType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithOptions#with(org.springframework.data.mongodb.core.mapreduce.MapReduceOptions)
+ */
+ @Override
+ public ReactiveMapReduce with(MapReduceOptions options) {
+
+ Assert.notNull(options, "Options must not be null! Please consider empty MapReduceOptions#options() instead.");
+
+ return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithMapFunction#map(java.lang.String)
+ */
+ @Override
+ public MapReduceWithReduceFunction map(String mapFunction) {
+
+ Assert.hasText(mapFunction, "MapFunction name must not be null nor empty!");
+
+ return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ /*
+ * (non-Javascript)
+ * @see in org.springframework.data.mongodb.core.ReactiveMapReduceOperation.MapReduceWithReduceFunction#reduce(java.lang.String)
+ */
+ @Override
+ public ReactiveMapReduce reduce(String reduceFunction) {
+
+ Assert.hasText(reduceFunction, "ReduceFunction name must not be null nor empty!");
+
+ return new ReactiveMapReduceSupport<>(template, domainType, returnType, collection, query, mapFunction,
+ reduceFunction, options);
+ }
+
+ private String getCollectionName() {
+ return StringUtils.hasText(collection) ? collection : template.determineCollectionName(domainType);
+ }
+ }
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
index a53ef8168..e2fd433ea 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
@@ -2001,7 +2001,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
if (filterQuery.getLimit() > 0 || (options.getLimit() != null)) {
if (filterQuery.getLimit() > 0 && (options.getLimit() != null)) {
- throw new IllegalArgumentException("Both Query and MapReduceOptions define a limit. Please provide the limit only via one of the two.");
+ throw new IllegalArgumentException(
+ "Both Query and MapReduceOptions define a limit. Please provide the limit only via one of the two.");
}
if (filterQuery.getLimit() > 0) {
@@ -2105,6 +2106,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return new ReactiveAggregationOperationSupport(this).aggregateAndReturn(domainType);
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.ReactiveMapReduceOperation#mapReduce(java.lang.Class)
+ */
+ @Override
+ public ReactiveMapReduce mapReduce(Class domainType) {
+ return new ReactiveMapReduceOperationSupport(this).mapReduce(domainType);
+ }
+
/**
* Retrieve and remove all documents matching the given {@code query} by calling {@link #find(Query, Class, String)}
* and {@link #remove(Query, Class, String)}, whereas the {@link Query} for {@link #remove(Query, Class, String)} is
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupportUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupportUnitTests.java
new file mode 100644
index 000000000..a68b72a16
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ExecutableMapReduceOperationSupportUnitTests.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2018 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;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.*;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Field;
+import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
+import org.springframework.data.mongodb.core.query.BasicQuery;
+import org.springframework.data.mongodb.core.query.Query;
+
+/**
+ * Unit tests for {@link ExecutableMapReduceOperationSupport}.
+ *
+ * @author Christoph Strobl
+ * @currentRead Beyond the Shadows - Brent Weeks
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ExecutableMapReduceOperationSupportUnitTests {
+
+ private static final String STAR_WARS = "star-wars";
+ private static final String MAP_FUNCTION = "function() { emit(this.id, this.firstname) }";
+ private static final String REDUCE_FUNCTION = "function(id, name) { return sum(id, name); }";
+
+ @Mock MongoTemplate template;
+
+ ExecutableMapReduceOperationSupport mapReduceOpsSupport;
+
+ @Before
+ public void setUp() {
+
+ when(template.determineCollectionName(eq(Person.class))).thenReturn(STAR_WARS);
+
+ mapReduceOpsSupport = new ExecutableMapReduceOperationSupport(template);
+ }
+
+ @Test(expected = IllegalArgumentException.class) // DATAMONGO-1929
+ public void throwsExceptionOnNullTemplate() {
+ new ExecutableMapReduceOperationSupport(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class) // DATAMONGO-1929
+ public void throwsExceptionOnNullDomainType() {
+ mapReduceOpsSupport.mapReduce(null);
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesExtractedCollectionName() {
+
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).all();
+
+ verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(MAP_FUNCTION), eq(REDUCE_FUNCTION),
+ isNull(), eq(Person.class));
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesExplicitCollectionName() {
+
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION)
+ .inCollection("the-night-angel").all();
+
+ verify(template).mapReduce(any(Query.class), eq(Person.class), eq("the-night-angel"), eq(MAP_FUNCTION),
+ eq(REDUCE_FUNCTION), isNull(), eq(Person.class));
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesMapReduceOptionsWhenPresent() {
+
+ MapReduceOptions options = MapReduceOptions.options();
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).with(options).all();
+
+ verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(MAP_FUNCTION), eq(REDUCE_FUNCTION),
+ eq(options), eq(Person.class));
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesQueryWhenPresent() {
+
+ Query query = new BasicQuery("{ 'lastname' : 'skywalker' }");
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).matching(query).all();
+
+ verify(template).mapReduce(eq(query), eq(Person.class), eq(STAR_WARS), eq(MAP_FUNCTION), eq(REDUCE_FUNCTION),
+ isNull(), eq(Person.class));
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesProjectionWhenPresent() {
+
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).as(Jedi.class).all();
+
+ verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(MAP_FUNCTION), eq(REDUCE_FUNCTION),
+ isNull(), eq(Jedi.class));
+ }
+
+ interface Contact {}
+
+ @Data
+ @org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS)
+ static class Person implements Contact {
+
+ @Id String id;
+ String firstname;
+ String lastname;
+ Object ability;
+ Person father;
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class Jedi {
+
+ @Field("firstname") String name;
+ }
+}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupportUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupportUnitTests.java
new file mode 100644
index 000000000..3f3209721
--- /dev/null
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMapReduceOperationSupportUnitTests.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2018 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;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.*;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Field;
+import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
+import org.springframework.data.mongodb.core.query.BasicQuery;
+import org.springframework.data.mongodb.core.query.Query;
+
+/**
+ * Unit tests for {@link ReactiveMapReduceOperationSupport}.
+ *
+ * @author Christoph Strobl
+ * @currentRead Beyond the Shadows - Brent Weeks
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class ReactiveMapReduceOperationSupportUnitTests {
+
+ private static final String STAR_WARS = "star-wars";
+ private static final String MAP_FUNCTION = "function() { emit(this.id, this.firstname) }";
+ private static final String REDUCE_FUNCTION = "function(id, name) { return sum(id, name); }";
+
+ @Mock ReactiveMongoTemplate template;
+
+ ReactiveMapReduceOperationSupport mapReduceOpsSupport;
+
+ @Before
+ public void setUp() {
+
+ when(template.determineCollectionName(eq(Person.class))).thenReturn(STAR_WARS);
+
+ mapReduceOpsSupport = new ReactiveMapReduceOperationSupport(template);
+ }
+
+ @Test(expected = IllegalArgumentException.class) // DATAMONGO-1929
+ public void throwsExceptionOnNullTemplate() {
+ new ExecutableMapReduceOperationSupport(null);
+ }
+
+ @Test(expected = IllegalArgumentException.class) // DATAMONGO-1929
+ public void throwsExceptionOnNullDomainType() {
+ mapReduceOpsSupport.mapReduce(null);
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesExtractedCollectionName() {
+
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).all();
+
+ verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(Person.class), eq(MAP_FUNCTION),
+ eq(REDUCE_FUNCTION), isNull());
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesExplicitCollectionName() {
+
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION)
+ .inCollection("the-night-angel").all();
+
+ verify(template).mapReduce(any(Query.class), eq(Person.class), eq("the-night-angel"), eq(Person.class),
+ eq(MAP_FUNCTION), eq(REDUCE_FUNCTION), isNull());
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesMapReduceOptionsWhenPresent() {
+
+ MapReduceOptions options = MapReduceOptions.options();
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).with(options).all();
+
+ verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(Person.class), eq(MAP_FUNCTION),
+ eq(REDUCE_FUNCTION), eq(options));
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesQueryWhenPresent() {
+
+ Query query = new BasicQuery("{ 'lastname' : 'skywalker' }");
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).matching(query).all();
+
+ verify(template).mapReduce(eq(query), eq(Person.class), eq(STAR_WARS), eq(Person.class), eq(MAP_FUNCTION),
+ eq(REDUCE_FUNCTION), isNull());
+ }
+
+ @Test // DATAMONGO-1929
+ public void usesProjectionWhenPresent() {
+
+ mapReduceOpsSupport.mapReduce(Person.class).map(MAP_FUNCTION).reduce(REDUCE_FUNCTION).as(Jedi.class).all();
+
+ verify(template).mapReduce(any(Query.class), eq(Person.class), eq(STAR_WARS), eq(Jedi.class), eq(MAP_FUNCTION),
+ eq(REDUCE_FUNCTION), isNull());
+ }
+
+ interface Contact {}
+
+ @Data
+ @org.springframework.data.mongodb.core.mapping.Document(collection = STAR_WARS)
+ static class Person implements Contact {
+
+ @Id String id;
+ String firstname;
+ String lastname;
+ Object ability;
+ Person father;
+ }
+
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ static class Jedi {
+
+ @Field("firstname") String name;
+ }
+}