package org.mongodb.morphia; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.MapReduceCommand.OutputType; import com.mongodb.MongoCommandException; import com.mongodb.MongoException; import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.Collation; import com.mongodb.client.model.CollationStrength; import com.mongodb.client.model.CreateCollectionOptions; import com.mongodb.client.model.ValidationAction; import com.mongodb.client.model.ValidationLevel; import com.mongodb.client.model.ValidationOptions; import org.bson.Document; import org.junit.Assert; import org.junit.Test; import org.mongodb.morphia.aggregation.AggregationTest.Book; import org.mongodb.morphia.aggregation.AggregationTest.CountResult; import org.mongodb.morphia.annotations.Embedded; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; import org.mongodb.morphia.annotations.PreLoad; import org.mongodb.morphia.query.Query; import org.mongodb.morphia.testmodel.Circle; import org.mongodb.morphia.testmodel.Rectangle; import org.mongodb.morphia.testmodel.Shape; import java.util.Iterator; import java.util.Random; import static java.util.Arrays.asList; import static org.junit.Assert.fail; public class TestMapreduce extends TestBase { @Test(expected = MongoException.class) public void testBadMR() throws Exception { final String map = "function () { if(this['radius']) { doEmit('circle', {count:1}); return; } emit('rect', {count:1}); }"; final String reduce = "function (key, values) { var total = 0; for ( var i=0; i<values.length; i++ ) {total += values[i].count;} " + "return { count : total }; }"; getDs().mapReduce(new MapReduceOptions<ResultEntity>() .resultType(ResultEntity.class) .outputType(OutputType.REPLACE) .query(getAds().find(Shape.class)) .map(map) .reduce(reduce)); } @Test @SuppressWarnings("deprecation") public void testOldMapReduce() throws Exception { final Random rnd = new Random(); //create 100 circles and rectangles for (int i = 0; i < 100; i++) { getAds().insert("shapes", new Circle(rnd.nextDouble())); getAds().insert("shapes", new Rectangle(rnd.nextDouble(), rnd.nextDouble())); } final String map = "function () { if(this['radius']) { emit('circle', {count:1}); return; } emit('rect', {count:1}); }"; final String reduce = "function (key, values) { var total = 0; for ( var i=0; i<values.length; i++ ) {total += values[i].count;} " + "return { count : total }; }"; final MapreduceResults<ResultEntity> mrRes = getDs().mapReduce(MapreduceType.REPLACE, getAds().find(Shape.class), map, reduce, null, null, ResultEntity.class); Assert.assertEquals(2, mrRes.createQuery().countAll()); Assert.assertEquals(100, mrRes.createQuery().get().getValue().count, 0); final MapreduceResults<ResultEntity> inline = getDs().mapReduce(MapreduceType.INLINE, getAds().find(Shape.class), map, reduce, null, null, ResultEntity.class); final Iterator<ResultEntity> iterator = inline.iterator(); Assert.assertEquals(2, count(iterator)); Assert.assertEquals(100, inline.iterator().next().getValue().count, 0); } @Test public void testMapReduce() throws Exception { final Random rnd = new Random(); //create 100 circles and rectangles for (int i = 0; i < 100; i++) { getAds().insert("shapes", new Circle(rnd.nextDouble())); getAds().insert("shapes", new Rectangle(rnd.nextDouble(), rnd.nextDouble())); } final String map = "function () { if(this['radius']) { emit('circle', {count:1}); return; } emit('rect', {count:1}); }"; final String reduce = "function (key, values) { var total = 0; for ( var i=0; i<values.length; i++ ) {total += values[i].count;} " + "return { count : total }; }"; final MapreduceResults<ResultEntity> mrRes = getDs().mapReduce(new MapReduceOptions<ResultEntity>() .outputType(OutputType.REPLACE) .query(getAds().find(Shape.class)) .map(map) .reduce(reduce) .resultType(ResultEntity.class)); Assert.assertEquals(2, mrRes.createQuery().count()); Assert.assertEquals(100, mrRes.createQuery().get().getValue().count, 0); final MapreduceResults<ResultEntity> inline = getDs().mapReduce(new MapReduceOptions<ResultEntity>() .outputType(OutputType.INLINE) .query(getAds().find(Shape.class)).map(map).reduce(reduce) .resultType(ResultEntity.class)); final Iterator<ResultEntity> iterator = inline.iterator(); Assert.assertEquals(2, count(iterator)); Assert.assertEquals(100, inline.iterator().next().getValue().count, 0); } @Test public void testCollation() { checkMinServerVersion(3.4); getDs().save(asList(new Book("The Banquet", "Dante", 2), new Book("Divine Comedy", "Dante", 1), new Book("Eclogues", "Dante", 2), new Book("The Odyssey", "Homer", 10), new Book("Iliad", "Homer", 10))); final String map = "function () { emit(this.author, 1); return; }"; final String reduce = "function (key, values) { return values.length }"; Query<Book> query = getAds().find(Book.class) .field("author").equal("dante"); MapReduceOptions<CountResult> options = new MapReduceOptions<CountResult>() .resultType(CountResult.class) .outputType(OutputType.INLINE) .query(query) .map(map) .reduce(reduce); Iterator<CountResult> iterator = getDs().mapReduce(options).getInlineResults(); Assert.assertEquals(0, count(iterator)); options .inputCollection(getMorphia().getMapper().getCollectionName(Book.class)) .collation(Collation.builder() .locale("en") .collationStrength(CollationStrength.SECONDARY) .build()); iterator = getDs().mapReduce(options).getInlineResults(); CountResult result = iterator.next(); Assert.assertEquals("Dante", result.getAuthor()); Assert.assertEquals(3D, result.getCount(), 0); } @Test public void testBypassDocumentValidation() { checkMinServerVersion(3.4); getDs().save(asList(new Book("The Banquet", "Dante", 2), new Book("Divine Comedy", "Dante", 1), new Book("Eclogues", "Dante", 2), new Book("The Odyssey", "Homer", 10), new Book("Iliad", "Homer", 10))); Document validator = Document.parse("{ count : { $gt : '10' } }"); ValidationOptions validationOptions = new ValidationOptions() .validator(validator) .validationLevel(ValidationLevel.STRICT) .validationAction(ValidationAction.ERROR); MongoDatabase database = getMongoClient().getDatabase(TEST_DB_NAME); database.getCollection("counts").drop(); database.createCollection("counts", new CreateCollectionOptions().validationOptions(validationOptions)); final String map = "function () { emit(this.author, 1); return; }"; final String reduce = "function (key, values) { return values.length }"; MapReduceOptions<CountResult> options = new MapReduceOptions<CountResult>() .query(getDs().find(Book.class)) .resultType(CountResult.class) .outputType(OutputType.REPLACE) .map(map) .reduce(reduce); try { getDs().mapReduce(options); fail("Document validation should have complained."); } catch (MongoCommandException e) { // expected } getDs().mapReduce(options.bypassDocumentValidation(true)); Assert.assertEquals(2, count(getDs().find(CountResult.class).iterator())); } @Entity("mr_results") private static class ResultEntity extends ResultBase<String, HasCount> { } public static class ResultBase<T, V> { @Id private T type; @Embedded private V value; public T getType() { return type; } public void setType(final T type) { this.type = type; } public V getValue() { return value; } public void setValue(final V value) { this.value = value; } } private static class HasCount { private double count; } @Entity("mr-results") private static class ResultEntity2 { @Id private String type; private double count; @PreLoad void preLoad(final BasicDBObject dbObj) { //pull all the fields from value field into the parent. dbObj.putAll((DBObject) dbObj.get("value")); } } }