/*
* Copyright (c) 2008-2014 MongoDB, Inc.
*
* 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 com.mongodb.acceptancetest.querying;
import com.mongodb.Function;
import com.mongodb.client.DatabaseTestCase;
import com.mongodb.client.MongoIterable;
import org.bson.Document;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.junit.Assert.assertThat;
public class MapReduceAcceptanceTest extends DatabaseTestCase {
private void insertLabelData() {
collection.insertMany(asList(new Document("_id", 0).append("labels", asList("a", "b")),
new Document("_id", 1).append("labels", asList("a", "b")),
new Document("_id", 2).append("labels", asList("b", "c")),
new Document("_id", 3).append("labels", asList("c", "d")),
new Document("_id", 4).append("labels", asList("a", "b")),
new Document("_id", 5).append("labels", asList("b", "c")),
new Document("_id", 6).append("labels", asList("b", "c")),
new Document("_id", 7).append("labels", asList("a", "b")),
new Document("_id", 8).append("labels", asList("b", "c")),
new Document("_id", 9).append("labels", asList("c", "d"))));
}
@Test
public void shouldReturnCountOfAllArrayValuesUsingSimpleMapReduce() {
//given
insertLabelData();
//when
// perform Map Reduce on all data
MongoIterable<Document> results = collection.mapReduce(" function(){ "
+ " for ( var i=0; i < this.labels.length; i++ ){ "
+ " emit( this.labels[i] , 1 ); "
+ " }"
+ "}",
" function(key,values){ "
+ " var sum=0; "
+ " for( var i=0; i < values.length; i++ ) "
+ " sum += values[i]; "
+ " return sum;"
+ "}");
//then
List<Document> resultList = results.into(new ArrayList<Document>());
assertThat("There are four distinct labels, a b c d", resultList.size(), is(4));
assertThat("There are four 'a's in the data", resultList, hasItem(new Document("_id", "a").append("value", 4.0)));
assertThat("There are eight 'b's in the data", resultList, hasItem(new Document("_id", "b").append("value", 8.0)));
assertThat("There are six 'c's in the data", resultList, hasItem(new Document("_id", "c").append("value", 6.0)));
assertThat("There are two 'd's in the data", resultList, hasItem(new Document("_id", "d").append("value", 2.0)));
}
@Test
public void shouldPerformMapReduceOnALimitedSetOfData() {
//given
insertLabelData();
//when
MongoIterable<Document> results = collection.mapReduce(" function(){ "
+ " for ( var i=0; i < this.labels.length; i++ ){ "
+ " emit( this.labels[i] , 1 ); "
+ " }"
+ "}",
" function(key,values){ "
+ " var sum=0; "
+ " for( var i=0; i < values.length; i++ ) "
+ " sum += values[i]; "
+ " return sum;"
+ "}")
.filter(new Document("_id", new Document("$gt", 1))) //find all IDs greater than 1
.sort(new Document("_id", 1)) // sort by ID
.limit(6); // limit to 6 of the remaining results
// will perform Map Reduce on _ids 2-7
//then
List<Document> resultList = results.into(new ArrayList<Document>());
assertThat("There are four distinct labels, a b c d", resultList.size(), is(4));
assertThat("There are two 'a's in the data", resultList, hasItem(new Document("_id", "a").append("value", 2.0)));
assertThat("There are five 'b's in the data", resultList, hasItem(new Document("_id", "b").append("value", 5.0)));
assertThat("There are four 'c's in the data", resultList, hasItem(new Document("_id", "c").append("value", 4.0)));
assertThat("There is one 'd's in the data", resultList, hasItem(new Document("_id", "d").append("value", 1.0)));
}
@Test
public void shouldMapIterableOfDocumentsIntoAnObjectOfYourChoice() {
//given
insertLabelData();
//when
MongoIterable<Document> results = collection.mapReduce(" function(){ "
+ " for ( var i=0; i < this.labels.length; i++ ){ "
+ " emit( this.labels[i] , 1 ); "
+ " }"
+ "}",
" function(key,values){ "
+ " var sum=0; "
+ " for( var i=0; i < values.length; i++ ) "
+ " sum += values[i]; "
+ " return sum;"
+ "}")
.filter(new Document("_id", new Document("$gt", 1))) //find all IDs greater than 1
.sort(new Document("_id", 1)) // sort by ID
.limit(6); // limit to 6 of the remaining results
// will perform Map Reduce on _ids 2-7
//transforms Documents into LabelCount objects
List<LabelCount> labelCounts = results.map(new Function<Document, LabelCount>() {
@Override
public LabelCount apply(final Document document) {
return new LabelCount(document.getString("_id"), document.getDouble("value"));
}
})
.into(new ArrayList<LabelCount>());
//then
assertThat("Transformed results should still be the same size as original results", labelCounts.size(), is(4));
Collections.sort(labelCounts);
assertThat("Should be LabelCount ordered by count ascending", labelCounts, contains(new LabelCount("d", 1),
new LabelCount("a", 2),
new LabelCount("c", 4),
new LabelCount("b", 5)));
}
private void insertCustomerData() {
// see http://docs.mongodb.org/manual/core/map-reduce/
collection.insertMany(asList(new Document("cust_id", "A123").append("amount", 500).append("status", "A"),
new Document("cust_id", "A123").append("amount", 250).append("status", "A"),
new Document("cust_id", "B212").append("amount", 200).append("status", "A"),
new Document("cust_id", "A123").append("amount", 300).append("status", "D")));
}
@Test
public void shouldSumFilteredAmounts() {
// Given
insertCustomerData();
// When
//find all orders with status "A"
MongoIterable<Document> results = collection.mapReduce(" function(){ "
+ " emit( this.cust_id, this.amount ); "
+ "}",
" function(key,values){ "
+ " return Array.sum(values);"
+ "}"
).filter(new Document("status", "A"));
// Then
List<Document> totalForOrdersWithStatusAPerCustomer = results.into(new ArrayList<Document>());
assertThat(totalForOrdersWithStatusAPerCustomer.size(), is(2));
assertThat(totalForOrdersWithStatusAPerCustomer, contains(new Document("_id", "A123").append("value", 750.0),
new Document("_id", "B212").append("value", 200.0)));
}
@Test
public void shouldInsertMapReduceResultsIntoACollectionWhenOutputTypeIsNotInline() {
//given
insertLabelData();
//when
// perform Map Reduce on all data
MongoIterable<Document> results = collection.mapReduce(" function(){ "
+ " for ( var i=0; i < this.labels.length; i++ ){ "
+ " emit( this.labels[i] , 1 ); "
+ " }"
+ "}",
" function(key,values){ "
+ " var sum=0; "
+ " for( var i=0; i < values.length; i++ ) "
+ " sum += values[i]; "
+ " return sum;"
+ "}").collectionName(getCollectionName() + "-output");
//then
List<Document> resultList = results.into(new ArrayList<Document>());
assertThat("There are four distinct labels, a b c d", resultList.size(), is(4));
assertThat("There are four 'a's in the data", resultList, hasItem(new Document("_id", "a").append("value", 4.0)));
assertThat("There are eight 'b's in the data", resultList, hasItem(new Document("_id", "b").append("value", 8.0)));
assertThat("There are six 'c's in the data", resultList, hasItem(new Document("_id", "c").append("value", 6.0)));
assertThat("There are two 'd's in the data", resultList, hasItem(new Document("_id", "d").append("value", 2.0)));
}
static class LabelCount implements Comparable<LabelCount> {
private final String tag;
private final double count;
LabelCount(final String tag, final double count) {
this.tag = tag;
this.count = count;
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LabelCount labelCount = (LabelCount) o;
if (Double.compare(labelCount.count, count) != 0) {
return false;
}
if (!tag.equals(labelCount.tag)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result;
long temp;
result = tag.hashCode();
temp = Double.doubleToLongBits(count);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
// So Collections.sort works. Sorts by count.
@Override
public int compareTo(final LabelCount labelCount) {
int cmp = Double.compare(count, labelCount.count);
if (cmp != 0) {
return cmp;
}
return tag.compareTo(labelCount.tag);
}
@Override
public String toString() {
return "TagCount{"
+ "tag='" + tag + '\''
+ ", count=" + count
+ '}';
}
}
}