DATAMONGO-586 - Further enhancements to Aggregation API.
Fixed parameter names in comments. Add static factory method. Implement basic aggregation operation join point. Implement match operation. Extracted ReferenceUtil. Created starting point of $group operation with _id field definition and $addToSet fields.
This commit is contained in:
committed by
Oliver Gierke
parent
4d65aa7207
commit
b7b61405f9
@@ -19,8 +19,6 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.geo.GeoResult;
|
||||
import org.springframework.data.mongodb.core.geo.GeoResults;
|
||||
@@ -316,6 +314,19 @@ public interface MongoOperations {
|
||||
*/
|
||||
<T> AggregationResults<T> aggregate(String inputCollectionName, AggregationPipeline pipeline, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Execute an aggregation operation. The raw results will be mapped to the given entity class.
|
||||
*
|
||||
* @param inputCollectionName the collection there the aggregation operation will read from, must not be
|
||||
* {@literal null} or empty.
|
||||
* @param entityClass The parameterized type of the returned list, must not be {@literal null}.
|
||||
* @param operations The aggregation operations, must not be {@literal null}.
|
||||
* @return The results of the aggregation operation.
|
||||
* @since 1.3
|
||||
*/
|
||||
<T> AggregationResults<T> aggregate(String inputCollectionName, Class<T> entityClass,
|
||||
AggregationOperation... operations);
|
||||
|
||||
/**
|
||||
* Execute a map-reduce operation. The map-reduce operation will be formed with an output type of INLINE
|
||||
*
|
||||
|
||||
@@ -53,6 +53,7 @@ import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.BeanWrapper;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.aggregation.operation.AggregationOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
@@ -1238,6 +1239,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||
return new AggregationResults<T>(mappedResults, commandResult);
|
||||
}
|
||||
|
||||
public <T> AggregationResults<T> aggregate(String inputCollectionName, Class<T> entityClass, AggregationOperation... operations) {
|
||||
return aggregate(inputCollectionName, new AggregationPipeline(operations), entityClass);
|
||||
}
|
||||
|
||||
|
||||
protected String replaceWithResourceIfNecessary(String function) {
|
||||
|
||||
String func = function;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.springframework.data.mongodb.core.aggregation.operation;
|
||||
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Represents one single operation in an aggregation pipeline
|
||||
*
|
||||
* @author Sebastian Herold
|
||||
* @since 1.3
|
||||
*/
|
||||
public interface AggregationOperation {
|
||||
|
||||
/**
|
||||
* Gets the {@link DBObject} behind this operation
|
||||
*
|
||||
* @return the DBObject
|
||||
*/
|
||||
DBObject getDBObject();
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.aggregation.operation.AggregationOperation;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -39,6 +40,18 @@ public class AggregationPipeline {
|
||||
|
||||
private final List<DBObject> operations = new ArrayList<DBObject>();
|
||||
|
||||
public AggregationPipeline() {
|
||||
}
|
||||
|
||||
public AggregationPipeline(AggregationOperation... operations) {
|
||||
Assert.notNull(operations, "Operations are missing");
|
||||
|
||||
for (AggregationOperation operation : operations) {
|
||||
Assert.notNull(operation, "Operation is not allowed to be null");
|
||||
this.operations.add(operation.getDBObject());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a projection operation to the pipeline.
|
||||
*
|
||||
@@ -81,7 +94,7 @@ public class AggregationPipeline {
|
||||
/**
|
||||
* Adds a group operation to the pipeline.
|
||||
*
|
||||
* @param projection JSON string holding the group, must not be {@literal null} or empty.
|
||||
* @param group JSON string holding the group, must not be {@literal null} or empty.
|
||||
* @return The pipeline.
|
||||
*/
|
||||
public AggregationPipeline group(String group) {
|
||||
@@ -118,7 +131,7 @@ public class AggregationPipeline {
|
||||
/**
|
||||
* Adds a match operation to the pipeline that is basically a query on the collections.
|
||||
*
|
||||
* @param projection JSON string holding the criteria, must not be {@literal null} or empty.
|
||||
* @param match JSON string holding the criteria, must not be {@literal null} or empty.
|
||||
* @return The pipeline.
|
||||
*/
|
||||
public AggregationPipeline match(String match) {
|
||||
@@ -161,6 +174,14 @@ public class AggregationPipeline {
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates an empty pipeline
|
||||
* @return the new pipeline
|
||||
*/
|
||||
public static AggregationPipeline pipeline() {
|
||||
return new AggregationPipeline();
|
||||
}
|
||||
|
||||
private AggregationPipeline addDocumentOperation(String opName, String operation) {
|
||||
|
||||
Assert.hasText(operation, "Missing operation name!");
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
package org.springframework.data.mongodb.core.aggregation.operation;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
import org.springframework.data.mongodb.core.aggregation.ReferenceUtil;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/group/#stage._S_group">
|
||||
* <code>$group</code>-operation
|
||||
* </a>
|
||||
*
|
||||
* @author Sebastian Herold
|
||||
* @since 1.3
|
||||
*/
|
||||
public class GroupOperation implements AggregationOperation {
|
||||
|
||||
private static final String ID_KEY = "_id";
|
||||
private final Object id;
|
||||
private final Map<String, DBObject> fields = new HashMap<String, DBObject>();
|
||||
|
||||
public GroupOperation(Object id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public DBObject getDBObject() {
|
||||
DBObject projection = new BasicDBObject(ID_KEY, id);
|
||||
for (Entry<String, DBObject> entry : fields.entrySet()) {
|
||||
projection.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return new BasicDBObject("$group", projection);
|
||||
}
|
||||
|
||||
public GroupOperation addField(String key, DBObject value) {
|
||||
Assert.hasText(key, "Key is empty");
|
||||
Assert.notNull(value, "Value is null");
|
||||
|
||||
String trimmedKey = key.trim();
|
||||
if (ID_KEY.equals(trimmedKey)) {
|
||||
throw new IllegalArgumentException("_id field can only be set in constructor");
|
||||
}
|
||||
|
||||
fields.put(key, value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_addToSet">$addToSet operation</a>.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$addToSet: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation addToSet(String name, String field) {
|
||||
return addOperation("$addToSet", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_first">$first operation</a>.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$first: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation first(String name, String field) {
|
||||
return addOperation("$first", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_last">$last operation</a>.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$last: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation last(String name, String field) {
|
||||
return addOperation("$last", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_max">$max operation</a>.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$max: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation max(String name, String field) {
|
||||
return addOperation("$max", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_min">$min operation</a>.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$min: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation min(String name, String field) {
|
||||
return addOperation("$min", name, field);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_avg">$avg operation</a>.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$avg: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation avg(String name, String field) {
|
||||
return addOperation("$avg", name, field);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_push">$push operation</a>.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$push: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation push(String name, String field) {
|
||||
return addOperation("$push", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_sum">$sum operation</a>
|
||||
* with a constant value.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$sum: increment}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @param increment increment for each item
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation count(String name, double increment) {
|
||||
return addField(name, new BasicDBObject("$sum", increment));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_sum">$sum operation</a>
|
||||
* count every item.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$sum: 1}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation count(String name) {
|
||||
return count(name, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a field with the
|
||||
* <a href="http://docs.mongodb.org/manual/reference/aggregation/addToSet/#grp._S_sum">$sum operation</a>.
|
||||
* <pre>
|
||||
* {$group: {
|
||||
* _id: "$id_field",
|
||||
* name: {$sum: "$field"}
|
||||
* }}
|
||||
* </pre>
|
||||
* @param name key of the field
|
||||
* @param field reference to a field of the document
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public GroupOperation sum(String name, String field) {
|
||||
return addOperation("$sum", name, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a <code>$group</code> operation with <code>_id</code> referencing to a field of the document. The
|
||||
* returned db object equals to <pre>{_id: "$field"}</pre>
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
public static GroupOperation group(String field) {
|
||||
return new GroupOperation(ReferenceUtil.safeReference(field));
|
||||
}
|
||||
|
||||
protected GroupOperation addOperation(String operation, String name, String field) {
|
||||
return addField(name, new BasicDBObject(operation, ReferenceUtil.safeReference(field)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a <code>$group</code> operation with a id that consists of multiple fields.
|
||||
*
|
||||
* Using {@link IdField#idField(String)} or {@link IdField#idField(String, String)} you can easily create
|
||||
* complex id fields like:
|
||||
* <pre>
|
||||
*
|
||||
* group(idField("path"), idField("pageView", "page.views"), idField("field3"))
|
||||
*
|
||||
* </pre>
|
||||
* which would result in:
|
||||
* <pre>
|
||||
*
|
||||
* {$group: {_id: {path: "$path", pageView: "$page.views", field3: "$field3"}}}
|
||||
*
|
||||
* </pre>
|
||||
* @param idFields
|
||||
* @return
|
||||
*/
|
||||
public static GroupOperation group(IdField... idFields) {
|
||||
Assert.notNull(idFields, "Combined id is null");
|
||||
|
||||
BasicDBObject id = new BasicDBObject();
|
||||
for (IdField idField : idFields) {
|
||||
id.put(idField.getKey(), idField.getValue());
|
||||
}
|
||||
|
||||
return new GroupOperation(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single field in a complex id of a <code>$group</code> operation.
|
||||
*
|
||||
* For example:
|
||||
* <pre>
|
||||
* {$group: {_id: {key: "$value"}}}
|
||||
* </pre>
|
||||
*/
|
||||
public static class IdField {
|
||||
|
||||
private final String key;
|
||||
private final String value;
|
||||
|
||||
public IdField(String key, String value) {
|
||||
Assert.hasText(key, "Key is empty");
|
||||
Assert.hasText(value, "Value is empty");
|
||||
|
||||
this.key = ReferenceUtil.safeNonReference(key);
|
||||
this.value = ReferenceUtil.safeReference(value);
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an id field with the name of the referenced field:
|
||||
* <pre>
|
||||
* _id: {field: "$field"}
|
||||
* </pre>
|
||||
* @param field reference to a field of the document
|
||||
* @return the id field
|
||||
*/
|
||||
public static IdField idField(String field) {
|
||||
return new IdField(field, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an id field with key and reference
|
||||
* <pre>
|
||||
* _id: {key: "$field"}
|
||||
* </pre>
|
||||
* @param key the key
|
||||
* @param field reference to a field of the document
|
||||
* @return the id field
|
||||
*/
|
||||
public static IdField idField(String key, String field) {
|
||||
return new IdField(key, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.springframework.data.mongodb.core.aggregation.operation;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
/**
|
||||
* Encapsulates the <code>$match</code>-operation
|
||||
*
|
||||
* @author Sebastian Herold
|
||||
* @since 1.3
|
||||
*/
|
||||
public class MatchOperation implements AggregationOperation {
|
||||
|
||||
private final DBObject criteria;
|
||||
|
||||
public MatchOperation(Criteria criteria) {
|
||||
this.criteria = criteria.getCriteriaObject();
|
||||
}
|
||||
|
||||
public DBObject getDBObject() {
|
||||
return new BasicDBObject("$match", criteria);
|
||||
}
|
||||
|
||||
public static MatchOperation match(Criteria criteria) {
|
||||
return new MatchOperation(criteria);
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,7 @@ import com.mongodb.DBObject;
|
||||
*/
|
||||
public class Projection {
|
||||
|
||||
private static final String REFERENCE_PREFIX = "$";
|
||||
|
||||
/** Stack of key names. Size is 0 or 1. */
|
||||
/** Stack of key names. Size is 0 or 1. */
|
||||
private final Stack<String> reference = new Stack<String>();
|
||||
private final DBObject document = new BasicDBObject();
|
||||
|
||||
@@ -105,7 +103,7 @@ public class Projection {
|
||||
Assert.hasText(key, "Missing key");
|
||||
|
||||
try {
|
||||
document.put(key, rightHandSide(safeReference(reference.pop())));
|
||||
document.put(key, rightHandSide(ReferenceUtil.safeReference(reference.pop())));
|
||||
} catch (EmptyStackException e) {
|
||||
throw new InvalidDataAccessApiUsageException("Invalid use of as()", e);
|
||||
}
|
||||
@@ -125,7 +123,7 @@ public class Projection {
|
||||
|
||||
Assert.notNull(n, "Missing number");
|
||||
|
||||
rightHandExpression = createArrayObject(op, safeReference(reference.peek()), n);
|
||||
rightHandExpression = createArrayObject(op, ReferenceUtil.safeReference(reference.peek()), n);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -134,7 +132,7 @@ public class Projection {
|
||||
List<Object> list = new ArrayList<Object>();
|
||||
Collections.addAll(list, items);
|
||||
|
||||
return new BasicDBObject(safeReference(op), list);
|
||||
return new BasicDBObject(ReferenceUtil.safeReference(op), list);
|
||||
}
|
||||
|
||||
private void safePop() {
|
||||
@@ -144,18 +142,7 @@ public class Projection {
|
||||
}
|
||||
}
|
||||
|
||||
private String safeReference(String key) {
|
||||
|
||||
Assert.hasText(key);
|
||||
|
||||
if (!key.startsWith(REFERENCE_PREFIX)) {
|
||||
return REFERENCE_PREFIX + key;
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
private Object rightHandSide(Object defaultValue) {
|
||||
private Object rightHandSide(Object defaultValue) {
|
||||
Object value = rightHandExpression != null ? rightHandExpression : defaultValue;
|
||||
rightHandExpression = null;
|
||||
return value;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.data.mongodb.core.aggregation;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility class for mongo db reference operator <code>$</code>
|
||||
*/
|
||||
public class ReferenceUtil {
|
||||
|
||||
public static final String REFERENCE_PREFIX = "$";
|
||||
|
||||
/**
|
||||
* Ensures that the returned string begins with {@link #REFERENCE_PREFIX $}
|
||||
*
|
||||
* @param key reference key with or without {@link #REFERENCE_PREFIX $} at the beginning
|
||||
* @return key that definitely begins with {@link #REFERENCE_PREFIX $}
|
||||
*/
|
||||
public static String safeReference(String key) {
|
||||
|
||||
Assert.hasText(key);
|
||||
|
||||
if (!key.startsWith(REFERENCE_PREFIX)) {
|
||||
return REFERENCE_PREFIX + key;
|
||||
} else {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the returned string does not start with {@link #REFERENCE_PREFIX $}
|
||||
*
|
||||
* @param field reference key with or without {@link #REFERENCE_PREFIX $} at the beginning
|
||||
* @return key that definitely does not begin with {@link #REFERENCE_PREFIX $}
|
||||
*/
|
||||
public static String safeNonReference(String field) {
|
||||
|
||||
Assert.hasText(field);
|
||||
|
||||
if (field.startsWith(REFERENCE_PREFIX)) {
|
||||
return field.substring(REFERENCE_PREFIX.length());
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user