Skip to content

Commit b0336c2

Browse files
christophstroblodrotbohm
authored andcommitted
DATAMONGO-1334 - Map-reduce operations now honor MapReduceOptions.limit.
We now also consider the limit set via MapReduceOptions when executing mapReduce operations via MongoTemplate.mapReduce(…). MapReduceOptions.limit(…) supersedes a potential limit set via the Query itself. This change also allows to define a limit even when no explicit Query is used. Original pull request: #338.
1 parent 0b492b6 commit b0336c2

File tree

4 files changed

+161
-4
lines changed

4 files changed

+161
-4
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,15 +1579,18 @@ private void copyMapReduceOptionsToCommand(Query query, MapReduceOptions mapRedu
15791579
throw new InvalidDataAccessApiUsageException(
15801580
"Can not use skip or field specification with map reduce operations");
15811581
}
1582-
1583-
if (query.getLimit() > 0) {
1582+
if (query.getLimit() > 0 && mapReduceOptions.getLimit() == null) {
15841583
mapReduceCommand.setLimit(query.getLimit());
15851584
}
15861585
if (query.getSortObject() != null) {
15871586
mapReduceCommand.setSort(queryMapper.getMappedObject(query.getSortObject(), null));
15881587
}
15891588
}
15901589

1590+
if (mapReduceOptions.getLimit() != null && mapReduceOptions.getLimit().intValue() > 0) {
1591+
mapReduceCommand.setLimit(mapReduceOptions.getLimit());
1592+
}
1593+
15911594
if (mapReduceOptions.getJavaScriptMode() != null) {
15921595
mapReduceCommand.setJsMode(true);
15931596
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptions.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public class MapReduceOptions {
4545

4646
private Boolean verbose = true;
4747

48+
private Integer limit;
49+
4850
private Map<String, Object> extraOptions = new HashMap<String, Object>();
4951

5052
/**
@@ -64,6 +66,8 @@ public static MapReduceOptions options() {
6466
* @return MapReduceOptions so that methods can be chained in a fluent API style
6567
*/
6668
public MapReduceOptions limit(int limit) {
69+
70+
this.limit = limit;
6771
return this;
6872
}
6973

@@ -247,6 +251,15 @@ public Map<String, Object> getScopeVariables() {
247251
return this.scopeVariables;
248252
}
249253

254+
/**
255+
* Get the maximum number of documents for the input into the map function.
256+
*
257+
* @return {@literal null} if not set.
258+
*/
259+
public Integer getLimit() {
260+
return limit;
261+
}
262+
250263
public DBObject getOptionsObject() {
251264
BasicDBObject cmd = new BasicDBObject();
252265

@@ -264,6 +277,10 @@ public DBObject getOptionsObject() {
264277
cmd.put("scope", scopeVariables);
265278
}
266279

280+
if (limit != null) {
281+
cmd.put("limit", limit);
282+
}
283+
267284
if (!extraOptions.keySet().isEmpty()) {
268285
cmd.putAll(extraOptions);
269286
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateUnitTests.java

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2010-2014 the original author or authors.
2+
* Copyright 2010-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -52,6 +52,7 @@
5252
import org.springframework.data.mongodb.core.convert.QueryMapper;
5353
import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCreator;
5454
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
55+
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
5556
import org.springframework.data.mongodb.core.query.BasicQuery;
5657
import org.springframework.data.mongodb.core.query.Criteria;
5758
import org.springframework.data.mongodb.core.query.NearQuery;
@@ -66,6 +67,8 @@
6667
import com.mongodb.DBCollection;
6768
import com.mongodb.DBCursor;
6869
import com.mongodb.DBObject;
70+
import com.mongodb.MapReduceCommand;
71+
import com.mongodb.MapReduceOutput;
6972
import com.mongodb.Mongo;
7073
import com.mongodb.MongoException;
7174
import com.mongodb.ReadPreference;
@@ -422,6 +425,112 @@ public void geoNearShouldIgnoreReadPreferenceWhenNotSet() {
422425
verify(this.db, times(1)).command(Mockito.any(DBObject.class));
423426
}
424427

428+
/**
429+
* @see DATAMONGO-1334
430+
*/
431+
@Test
432+
public void mapReduceShouldUseZeroAsDefaultLimit() {
433+
434+
ArgumentCaptor<MapReduceCommand> captor = ArgumentCaptor.forClass(MapReduceCommand.class);
435+
436+
MapReduceOutput output = mock(MapReduceOutput.class);
437+
when(output.results()).thenReturn(Collections.<DBObject> emptySet());
438+
when(collection.mapReduce(Mockito.any(MapReduceCommand.class))).thenReturn(output);
439+
440+
Query query = new BasicQuery("{'foo':'bar'}");
441+
442+
template.mapReduce(query, "collection", "function(){}", "function(key,values){}", Wrapper.class);
443+
444+
verify(collection).mapReduce(captor.capture());
445+
446+
assertThat(captor.getValue().getLimit(), is(0));
447+
}
448+
449+
/**
450+
* @see DATAMONGO-1334
451+
*/
452+
@Test
453+
public void mapReduceShouldPickUpLimitFromQuery() {
454+
455+
ArgumentCaptor<MapReduceCommand> captor = ArgumentCaptor.forClass(MapReduceCommand.class);
456+
457+
MapReduceOutput output = mock(MapReduceOutput.class);
458+
when(output.results()).thenReturn(Collections.<DBObject> emptySet());
459+
when(collection.mapReduce(Mockito.any(MapReduceCommand.class))).thenReturn(output);
460+
461+
Query query = new BasicQuery("{'foo':'bar'}");
462+
query.limit(100);
463+
464+
template.mapReduce(query, "collection", "function(){}", "function(key,values){}", Wrapper.class);
465+
466+
verify(collection).mapReduce(captor.capture());
467+
468+
assertThat(captor.getValue().getLimit(), is(100));
469+
}
470+
471+
/**
472+
* @see DATAMONGO-1334
473+
*/
474+
@Test
475+
public void mapReduceShouldPickUpLimitFromOptions() {
476+
477+
ArgumentCaptor<MapReduceCommand> captor = ArgumentCaptor.forClass(MapReduceCommand.class);
478+
479+
MapReduceOutput output = mock(MapReduceOutput.class);
480+
when(output.results()).thenReturn(Collections.<DBObject> emptySet());
481+
when(collection.mapReduce(Mockito.any(MapReduceCommand.class))).thenReturn(output);
482+
483+
Query query = new BasicQuery("{'foo':'bar'}");
484+
485+
template.mapReduce(query, "collection", "function(){}", "function(key,values){}",
486+
new MapReduceOptions().limit(1000), Wrapper.class);
487+
488+
verify(collection).mapReduce(captor.capture());
489+
assertThat(captor.getValue().getLimit(), is(1000));
490+
}
491+
492+
/**
493+
* @see DATAMONGO-1334
494+
*/
495+
@Test
496+
public void mapReduceShouldPickUpLimitFromOptionsWhenQueryIsNotPresent() {
497+
498+
ArgumentCaptor<MapReduceCommand> captor = ArgumentCaptor.forClass(MapReduceCommand.class);
499+
500+
MapReduceOutput output = mock(MapReduceOutput.class);
501+
when(output.results()).thenReturn(Collections.<DBObject> emptySet());
502+
when(collection.mapReduce(Mockito.any(MapReduceCommand.class))).thenReturn(output);
503+
504+
template.mapReduce("collection", "function(){}", "function(key,values){}", new MapReduceOptions().limit(1000),
505+
Wrapper.class);
506+
507+
verify(collection).mapReduce(captor.capture());
508+
assertThat(captor.getValue().getLimit(), is(1000));
509+
}
510+
511+
/**
512+
* @see DATAMONGO-1334
513+
*/
514+
@Test
515+
public void mapReduceShouldPickUpLimitFromOptionsEvenWhenQueryDefinesItDifferently() {
516+
517+
ArgumentCaptor<MapReduceCommand> captor = ArgumentCaptor.forClass(MapReduceCommand.class);
518+
519+
MapReduceOutput output = mock(MapReduceOutput.class);
520+
when(output.results()).thenReturn(Collections.<DBObject> emptySet());
521+
when(collection.mapReduce(Mockito.any(MapReduceCommand.class))).thenReturn(output);
522+
523+
Query query = new BasicQuery("{'foo':'bar'}");
524+
query.limit(100);
525+
526+
template.mapReduce(query, "collection", "function(){}", "function(key,values){}",
527+
new MapReduceOptions().limit(1000), Wrapper.class);
528+
529+
verify(collection).mapReduce(captor.capture());
530+
531+
assertThat(captor.getValue().getLimit(), is(1000));
532+
}
533+
425534
class AutogenerateableId {
426535

427536
@Id BigInteger id;

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapreduce/MapReduceOptionsTests.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2010-2011 the original author or authors.
2+
* Copyright 2010-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,12 +15,40 @@
1515
*/
1616
package org.springframework.data.mongodb.core.mapreduce;
1717

18+
import static org.junit.Assert.*;
19+
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
20+
1821
import org.junit.Test;
1922

23+
/**
24+
* @author Mark Pollack
25+
* @author Oliver Gierke
26+
* @author Christoph Strobl
27+
*/
2028
public class MapReduceOptionsTests {
2129

2230
@Test
2331
public void testFinalize() {
2432
new MapReduceOptions().finalizeFunction("code");
2533
}
34+
35+
/**
36+
* @see DATAMONGO-1334
37+
*/
38+
@Test
39+
public void limitShouldBeIncludedCorrectly() {
40+
41+
MapReduceOptions options = new MapReduceOptions();
42+
options.limit(10);
43+
44+
assertThat(options.getOptionsObject(), isBsonObject().containing("limit", 10));
45+
}
46+
47+
/**
48+
* @see DATAMONGO-1334
49+
*/
50+
@Test
51+
public void limitShouldNotBePresentInDboWhenNotSet() {
52+
assertThat(new MapReduceOptions().getOptionsObject(), isBsonObject().notContaining("limit"));
53+
}
2654
}

0 commit comments

Comments
 (0)