DATAMONGO-861 - Add support for $cond and $ifNull operators in aggregation operations.
We now support $cond and $ifNull operators for projection and grouping operations. ConditionalOperator and IfNullOperators are AggregationExpressions that can be applied to transform or generate values during aggregation.
TypedAggregation<InventoryItem> agg = newAggregation(InventoryItem.class,
project().and("discount")
.transform(ConditionalOperator.newBuilder().when(Criteria.where("qty").gte(250))
.then(30)
.otherwise(20))
.and(ifNull("description", "Unspecified")).as("description")
);
corresponds to
{ "$project": { "discount": { "$cond": { "if": { "$gte": [ "$qty", 250 ] },
"then": 30, "else": 20 } },
"description": { "$ifNull": [ "$description", "Unspecified"] }
}
}
Original Pull Request: #385
This commit is contained in:
committed by
Christoph Strobl
parent
116dda63c2
commit
ace01e4e6d
@@ -372,6 +372,68 @@ public class Aggregation {
|
||||
return new LookupOperation(from, localField, foreignField, as);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link IfNullOperator} for the given {@code field} and {@code replacement} value.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public static IfNullOperator ifNull(String field, Object replacement) {
|
||||
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link IfNullOperator} for the given {@link Field} and {@link Field} to obtain a value from.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public static IfNullOperator ifNull(Field field, Field replacement) {
|
||||
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link IfNullOperator} for the given {@link Field} and {@code replacement} value.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @param replacement must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public static IfNullOperator ifNull(Field field, Object replacement) {
|
||||
return IfNullOperator.newBuilder().ifNull(field).thenReplaceWith(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConditionalOperator} for the given {@link Field} that holds a {@literal boolean} value.
|
||||
*
|
||||
* @param booleanField must not be {@literal null}.
|
||||
* @param then must not be {@literal null}.
|
||||
* @param otherwise must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public static ConditionalOperator conditional(Field booleanField, Object then, Object otherwise) {
|
||||
return ConditionalOperator.newBuilder().when(booleanField).then(then).otherwise(otherwise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConditionalOperator} for the given {@link Criteria}.
|
||||
*
|
||||
* @param criteria must not be {@literal null}.
|
||||
* @param then must not be {@literal null}.
|
||||
* @param otherwise must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public static ConditionalOperator conditional(Criteria criteria, Object then, Object otherwise) {
|
||||
return ConditionalOperator.newBuilder().when(criteria).then(then).otherwise(otherwise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Fields} instance for the given field names.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* Copyright 2016 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.aggregation;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework {@code $cond} operator. A {@link ConditionalOperator} allows nested conditions
|
||||
* {@code if-then[if-then-else]-else} using {@link Field}, {@link CriteriaDefinition} or a {@link DBObject custom}
|
||||
* condition. Replacement values can be either {@link Field field references}, values of simple MongoDB types or values
|
||||
* that can be converted to a simple MongoDB type.
|
||||
*
|
||||
* @see http://docs.mongodb.com/manual/reference/operator/aggregation/cond/
|
||||
* @author Mark Paluch
|
||||
* @since 1.10
|
||||
*/
|
||||
public class ConditionalOperator implements AggregationExpression {
|
||||
|
||||
private final Object condition;
|
||||
private final Object thenValue;
|
||||
private final Object otherwiseValue;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConditionalOperator} for a given {@link Field} and {@code then}/{@code otherwise} values.
|
||||
*
|
||||
* @param condition must not be {@literal null}.
|
||||
* @param thenValue must not be {@literal null}.
|
||||
* @param otherwiseValue must not be {@literal null}.
|
||||
*/
|
||||
public ConditionalOperator(Field condition, Object thenValue, Object otherwiseValue) {
|
||||
this((Object) condition, thenValue, otherwiseValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConditionalOperator} for a given {@link CriteriaDefinition} and {@code then}/{@code otherwise}
|
||||
* values.
|
||||
*
|
||||
* @param condition must not be {@literal null}.
|
||||
* @param thenValue must not be {@literal null}.
|
||||
* @param otherwiseValue must not be {@literal null}.
|
||||
*/
|
||||
public ConditionalOperator(CriteriaDefinition condition, Object thenValue, Object otherwiseValue) {
|
||||
this((Object) condition, thenValue, otherwiseValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConditionalOperator} for a given {@link DBObject criteria} and {@code then}/{@code otherwise}
|
||||
* values.
|
||||
*
|
||||
* @param condition must not be {@literal null}.
|
||||
* @param thenValue must not be {@literal null}.
|
||||
* @param otherwiseValue must not be {@literal null}.
|
||||
*/
|
||||
public ConditionalOperator(DBObject condition, Object thenValue, Object otherwiseValue) {
|
||||
this((Object) condition, thenValue, otherwiseValue);
|
||||
}
|
||||
|
||||
private ConditionalOperator(Object condition, Object thenValue, Object otherwiseValue) {
|
||||
|
||||
Assert.notNull(condition, "Condition must not be null!");
|
||||
Assert.notNull(thenValue, "'Then value' must not be null!");
|
||||
Assert.notNull(otherwiseValue, "'Otherwise value' must not be null!");
|
||||
|
||||
assertNotBuilder(condition, "Condition");
|
||||
assertNotBuilder(thenValue, "'Then value'");
|
||||
assertNotBuilder(otherwiseValue, "'Otherwise value'");
|
||||
|
||||
this.condition = condition;
|
||||
this.thenValue = thenValue;
|
||||
this.otherwiseValue = otherwiseValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDbObject(AggregationOperationContext context) {
|
||||
|
||||
BasicDBObject condObject = new BasicDBObject();
|
||||
|
||||
condObject.append("if", resolveCriteria(context, condition));
|
||||
condObject.append("then", resolveValue(context, thenValue));
|
||||
condObject.append("else", resolveValue(context, otherwiseValue));
|
||||
|
||||
return new BasicDBObject("$cond", condObject);
|
||||
}
|
||||
|
||||
private Object resolveValue(AggregationOperationContext context, Object value) {
|
||||
|
||||
if (value instanceof DBObject || value instanceof Field) {
|
||||
return resolve(context, value);
|
||||
}
|
||||
|
||||
if (value instanceof ConditionalOperator) {
|
||||
return ((ConditionalOperator) value).toDbObject(context);
|
||||
}
|
||||
|
||||
DBObject toMap = context.getMappedObject(new BasicDBObject("$set", value));
|
||||
return toMap.get("$set");
|
||||
}
|
||||
|
||||
private Object resolveCriteria(AggregationOperationContext context, Object value) {
|
||||
|
||||
if (value instanceof DBObject || value instanceof Field) {
|
||||
return resolve(context, value);
|
||||
}
|
||||
|
||||
if (value instanceof CriteriaDefinition) {
|
||||
|
||||
DBObject mappedObject = context.getMappedObject(((CriteriaDefinition) value).getCriteriaObject());
|
||||
BasicDBList clauses = new BasicDBList();
|
||||
|
||||
clauses.addAll(getClauses(context, mappedObject));
|
||||
|
||||
if (clauses.size() == 1) {
|
||||
return clauses.get(0);
|
||||
}
|
||||
|
||||
return clauses;
|
||||
}
|
||||
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("Invalid value in condition. Supported: DBObject, Field references, Criteria, got: %s", value));
|
||||
}
|
||||
|
||||
private BasicDBList getClauses(AggregationOperationContext context, DBObject mappedObject) {
|
||||
|
||||
BasicDBList clauses = new BasicDBList();
|
||||
|
||||
for (String key : mappedObject.keySet()) {
|
||||
|
||||
Object predicate = mappedObject.get(key);
|
||||
clauses.addAll(getClauses(context, key, predicate));
|
||||
}
|
||||
|
||||
return clauses;
|
||||
}
|
||||
|
||||
private BasicDBList getClauses(AggregationOperationContext context, String key, Object predicate) {
|
||||
|
||||
BasicDBList clauses = new BasicDBList();
|
||||
|
||||
if (predicate instanceof BasicDBList) {
|
||||
|
||||
BasicDBList args = new BasicDBList();
|
||||
for (Object clause : (BasicDBList) predicate) {
|
||||
args.addAll(getClauses(context, (BasicDBObject) clause));
|
||||
}
|
||||
|
||||
clauses.add(new BasicDBObject(key, args));
|
||||
|
||||
} else if (predicate instanceof DBObject) {
|
||||
|
||||
DBObject nested = (DBObject) predicate;
|
||||
|
||||
for (String s : nested.keySet()) {
|
||||
|
||||
if (!isKeyword(s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BasicDBList args = new BasicDBList();
|
||||
args.add("$" + key);
|
||||
args.add(nested.get(s));
|
||||
clauses.add(new BasicDBObject(s, args));
|
||||
}
|
||||
|
||||
} else if (!isKeyword(key)) {
|
||||
|
||||
BasicDBList args = new BasicDBList();
|
||||
args.add("$" + key);
|
||||
args.add(predicate);
|
||||
clauses.add(new BasicDBObject("$eq", args));
|
||||
}
|
||||
|
||||
return clauses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link String} is a MongoDB keyword.
|
||||
*
|
||||
* @param candidate
|
||||
* @return
|
||||
*/
|
||||
private boolean isKeyword(String candidate) {
|
||||
return candidate.startsWith("$");
|
||||
}
|
||||
|
||||
private Object resolve(AggregationOperationContext context, Object value) {
|
||||
|
||||
if (value instanceof DBObject) {
|
||||
return context.getMappedObject((DBObject) value);
|
||||
}
|
||||
|
||||
return context.getReference((Field) value).toString();
|
||||
}
|
||||
|
||||
private void assertNotBuilder(Object toCheck, String name) {
|
||||
Assert.isTrue(!ClassUtils.isAssignableValue(ConditionalExpressionBuilder.class, toCheck),
|
||||
String.format("%s must not be of type %s", name, ConditionalExpressionBuilder.class.getSimpleName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a builder that allows fluent creation of {@link ConditionalOperator}.
|
||||
*
|
||||
* @return a new {@link ConditionalExpressionBuilder}.
|
||||
*/
|
||||
public static ConditionalExpressionBuilder newBuilder() {
|
||||
return ConditionalExpressionBuilder.newBuilder();
|
||||
}
|
||||
|
||||
public static interface WhenBuilder {
|
||||
|
||||
/**
|
||||
* @param booleanExpression expression that yields in a boolean result, must not be {@literal null}.
|
||||
* @return the {@link ThenBuilder}
|
||||
*/
|
||||
ThenBuilder when(DBObject booleanExpression);
|
||||
|
||||
/**
|
||||
* @param booleanField reference to a field holding a boolean value, must not be {@literal null}.
|
||||
* @return the {@link ThenBuilder}
|
||||
*/
|
||||
ThenBuilder when(Field booleanField);
|
||||
|
||||
/**
|
||||
* @param booleanField name of a field holding a boolean value, must not be {@literal null}.
|
||||
* @return the {@link ThenBuilder}
|
||||
*/
|
||||
ThenBuilder when(String booleanField);
|
||||
|
||||
/**
|
||||
* @param criteria criteria to evaluate, must not be {@literal null}.
|
||||
* @return the {@link ThenBuilder}
|
||||
*/
|
||||
ThenBuilder when(CriteriaDefinition criteria);
|
||||
}
|
||||
|
||||
public static interface ThenBuilder {
|
||||
|
||||
/**
|
||||
* @param value the value to be used if the condition evaluates {@literal true}. Can be a {@link DBObject}, a value
|
||||
* that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not
|
||||
* be {@literal null}.
|
||||
* @return the {@link OtherwiseBuilder}
|
||||
*/
|
||||
OtherwiseBuilder then(Object value);
|
||||
}
|
||||
|
||||
public static interface OtherwiseBuilder {
|
||||
|
||||
/**
|
||||
* @param value the value to be used if the condition evaluates {@literal false}. Can be a {@link DBObject}, a value
|
||||
* that is supported by MongoDB or a value that can be converted to a MongoDB representation but must not
|
||||
* be {@literal null}.
|
||||
* @return the {@link ConditionalOperator}
|
||||
*/
|
||||
ConditionalOperator otherwise(Object value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for fluent {@link ConditionalOperator} creation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.10
|
||||
*/
|
||||
public static final class ConditionalExpressionBuilder implements WhenBuilder, ThenBuilder, OtherwiseBuilder {
|
||||
|
||||
private Object condition;
|
||||
private Object thenValue;
|
||||
|
||||
private ConditionalExpressionBuilder() {}
|
||||
|
||||
/**
|
||||
* Creates a new builder for {@link ConditionalOperator}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public static ConditionalExpressionBuilder newBuilder() {
|
||||
return new ConditionalExpressionBuilder();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(com.mongodb.DBObject)
|
||||
*/
|
||||
@Override
|
||||
public ConditionalExpressionBuilder when(DBObject booleanExpression) {
|
||||
|
||||
Assert.notNull(booleanExpression, "'Boolean expression' must not be null!");
|
||||
|
||||
this.condition = booleanExpression;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.query.CriteriaDefinition)
|
||||
*/
|
||||
@Override
|
||||
public ThenBuilder when(CriteriaDefinition criteria) {
|
||||
|
||||
Assert.notNull(criteria, "Criteria must not be null!");
|
||||
|
||||
this.condition = criteria;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(org.springframework.data.mongodb.core.aggregation.Field)
|
||||
*/
|
||||
@Override
|
||||
public ThenBuilder when(Field booleanField) {
|
||||
|
||||
Assert.notNull(booleanField, "Boolean field must not be null!");
|
||||
|
||||
this.condition = booleanField;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.WhenBuilder#when(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public ThenBuilder when(String booleanField) {
|
||||
|
||||
Assert.hasText(booleanField, "Boolean field name must not be null or empty!");
|
||||
|
||||
this.condition = Fields.field(booleanField);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.ThenBuilder#then(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public OtherwiseBuilder then(Object thenValue) {
|
||||
|
||||
Assert.notNull(thenValue, "'Then-value' must not be null!");
|
||||
|
||||
this.thenValue = thenValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ConditionalOperator.OtherwiseBuilder#otherwise(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public ConditionalOperator otherwise(Object otherwiseValue) {
|
||||
|
||||
Assert.notNull(otherwiseValue, "'Otherwise-value' must not be null!");
|
||||
|
||||
return new ConditionalOperator(condition, thenValue, otherwiseValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2016 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.aggregation;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework {@code $ifNull} operator. Replacement values can be either {@link Field field
|
||||
* references}, values of simple MongoDB types or values that can be converted to a simple MongoDB type.
|
||||
*
|
||||
* @see http://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/
|
||||
* @author Mark Paluch
|
||||
* @since 1.10
|
||||
*/
|
||||
public class IfNullOperator implements AggregationExpression {
|
||||
|
||||
private final Field field;
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates a new {@link IfNullOperator} for the given {@link Field} and replacement {@code value}.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @param value must not be {@literal null}.
|
||||
*/
|
||||
public IfNullOperator(Field field, Object value) {
|
||||
|
||||
Assert.notNull(field, "Field must not be null!");
|
||||
Assert.notNull(value, "'Replacement-value' must not be null!");
|
||||
|
||||
this.field = field;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public DBObject toDbObject(AggregationOperationContext context) {
|
||||
|
||||
BasicDBList list = new BasicDBList();
|
||||
|
||||
list.add(context.getReference(field).toString());
|
||||
|
||||
if (value instanceof Field) {
|
||||
list.add(context.getReference((Field) value).toString());
|
||||
} else {
|
||||
|
||||
DBObject toMap = context.getMappedObject(new BasicDBObject("$set", value));
|
||||
list.add(toMap.get("$set"));
|
||||
}
|
||||
|
||||
return new BasicDBObject("$ifNull", list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a builder that allows fluent creation of {@link IfNullOperator}.
|
||||
*
|
||||
* @return a new {@link IfNullBuilder}.
|
||||
*/
|
||||
public static IfNullBuilder newBuilder() {
|
||||
return IfNullOperatorBuilder.newBuilder();
|
||||
}
|
||||
|
||||
public static interface IfNullBuilder {
|
||||
|
||||
/**
|
||||
* @param field the field to check for a {@literal null} value, field reference must not be {@literal null}.
|
||||
* @return the {@link ThenBuilder}
|
||||
*/
|
||||
ThenBuilder ifNull(Field field);
|
||||
|
||||
/**
|
||||
* @param field the field to check for a {@literal null} value, field name must not be {@literal null} or empty.
|
||||
* @return the {@link ThenBuilder}
|
||||
*/
|
||||
ThenBuilder ifNull(String field);
|
||||
}
|
||||
|
||||
public static interface ThenBuilder {
|
||||
|
||||
/**
|
||||
* @param field the field holding the replacement value, must not be {@literal null}.
|
||||
* @return the {@link IfNullOperator}
|
||||
*/
|
||||
IfNullOperator thenReplaceWith(Field field);
|
||||
|
||||
/**
|
||||
* @param value the value to be used if the {@code $ifNull }condition evaluates {@literal true}. Can be a
|
||||
* {@link DBObject}, a value that is supported by MongoDB or a value that can be converted to a MongoDB
|
||||
* representation but must not be {@literal null}.
|
||||
* @return the {@link IfNullOperator}
|
||||
*/
|
||||
IfNullOperator thenReplaceWith(Object value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for fluent {@link IfNullOperator} creation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.10
|
||||
*/
|
||||
public static final class IfNullOperatorBuilder implements IfNullBuilder, ThenBuilder {
|
||||
|
||||
private Field field;
|
||||
|
||||
private IfNullOperatorBuilder() {}
|
||||
|
||||
/**
|
||||
* Creates a new builder for {@link IfNullOperator}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public static IfNullOperatorBuilder newBuilder() {
|
||||
return new IfNullOperatorBuilder();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(org.springframework.data.mongodb.core.aggregation.Field)
|
||||
*/
|
||||
|
||||
public ThenBuilder ifNull(Field field) {
|
||||
|
||||
Assert.notNull(field, "Field must not be null!");
|
||||
|
||||
this.field = field;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.IfNullBuilder#ifNull(java.lang.String)
|
||||
*/
|
||||
public ThenBuilder ifNull(String name) {
|
||||
|
||||
Assert.hasText(name, "Field name must not be null or empty!");
|
||||
|
||||
this.field = Fields.field(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(org.springframework.data.mongodb.core.aggregation.Field)
|
||||
*/
|
||||
@Override
|
||||
public IfNullOperator thenReplaceWith(Field replacementField) {
|
||||
|
||||
Assert.notNull(replacementField, "Replacement field must not be null!");
|
||||
|
||||
return new IfNullOperator(this.field, replacementField);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.IfNullOperator.ThenReplaceBuilder#thenReplaceWith(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public IfNullOperator thenReplaceWith(Object value) {
|
||||
|
||||
Assert.notNull(value, "'Replacement-value' must not be null!");
|
||||
|
||||
return new IfNullOperator(this.field, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,8 +104,8 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
*/
|
||||
private ProjectionOperation andReplaceLastOneWith(Projection projection) {
|
||||
|
||||
List<Projection> projections = this.projections.isEmpty() ? Collections.<Projection> emptyList() : this.projections
|
||||
.subList(0, this.projections.size() - 1);
|
||||
List<Projection> projections = this.projections.isEmpty() ? Collections.<Projection> emptyList()
|
||||
: this.projections.subList(0, this.projections.size() - 1);
|
||||
return new ProjectionOperation(projections, Arrays.asList(projection));
|
||||
}
|
||||
|
||||
@@ -240,6 +240,24 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
* @return
|
||||
*/
|
||||
public abstract ProjectionOperation as(String alias);
|
||||
|
||||
/**
|
||||
* Apply a conditional projection using {@link ConditionalOperator}.
|
||||
*
|
||||
* @param conditional must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public abstract ProjectionOperation transform(ConditionalOperator conditional);
|
||||
|
||||
/**
|
||||
* Apply a conditional value replacement for {@literal null} values using {@link IfNullOperator}.
|
||||
*
|
||||
* @param ifNull must not be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
* @since 1.10
|
||||
*/
|
||||
public abstract ProjectionOperation transform(IfNullOperator ifNull);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,7 +388,8 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
* @param operation must not be {@literal null}.
|
||||
* @param previousProjection the previous operation projection, may be {@literal null}.
|
||||
*/
|
||||
public ProjectionOperationBuilder(String name, ProjectionOperation operation, OperationProjection previousProjection) {
|
||||
public ProjectionOperationBuilder(String name, ProjectionOperation operation,
|
||||
OperationProjection previousProjection) {
|
||||
super(name, operation);
|
||||
|
||||
this.name = name;
|
||||
@@ -419,7 +438,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
/**
|
||||
* Allows to specify an alias for the previous projection operation.
|
||||
*
|
||||
* @param string
|
||||
* @param alias
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@@ -436,6 +455,24 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
return this.operation.and(new FieldProjection(Fields.field(alias, name), null));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#transform(org.springframework.data.mongodb.core.aggregation.ConditionalOperator)
|
||||
*/
|
||||
@Override
|
||||
public ProjectionOperation transform(ConditionalOperator conditional) {
|
||||
return this.operation.and(new ExpressionProjection(Fields.field(name), conditional));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#transform(org.springframework.data.mongodb.core.aggregation.IfNullOperator)
|
||||
*/
|
||||
@Override
|
||||
public ProjectionOperation transform(IfNullOperator ifNull) {
|
||||
return this.operation.and(new ExpressionProjection(Fields.field(name), ifNull));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an {@code $add} expression that adds the given number to the previously mentioned field.
|
||||
*
|
||||
@@ -764,7 +801,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
|
||||
this.values = Arrays.asList(values);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.Projection#toDBObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.data.domain.Sort.Direction.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
|
||||
@@ -55,6 +56,7 @@ import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.Venue;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry;
|
||||
import org.springframework.data.mongodb.core.index.GeospatialIndex;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
@@ -131,6 +133,8 @@ public class AggregationTests {
|
||||
mongoTemplate.dropCollection(Reservation.class);
|
||||
mongoTemplate.dropCollection(Venue.class);
|
||||
mongoTemplate.dropCollection(MeterData.class);
|
||||
mongoTemplate.dropCollection(LineItem.class);
|
||||
mongoTemplate.dropCollection(InventoryItem.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -484,6 +488,250 @@ public class AggregationTests {
|
||||
assertThat(stateStats.totalPopulation, is(29760021));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
* @see https://docs.mongodb.com/manual/reference/operator/aggregation/cond/#example
|
||||
*/
|
||||
@Test
|
||||
public void aggregationUsingConditionalProjectionToCalculateDiscount() {
|
||||
|
||||
/*
|
||||
db.inventory.aggregate(
|
||||
[
|
||||
{
|
||||
$project:
|
||||
{
|
||||
item: 1,
|
||||
discount:
|
||||
{
|
||||
$cond: { if: { $gte: [ "$qty", 250 ] }, then: 30, else: 20 }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
*/
|
||||
|
||||
mongoTemplate.insert(new InventoryItem(1, "abc1", 300));
|
||||
mongoTemplate.insert(new InventoryItem(2, "abc2", 200));
|
||||
mongoTemplate.insert(new InventoryItem(3, "xyz1", 250));
|
||||
|
||||
TypedAggregation<InventoryItem> aggregation = newAggregation(InventoryItem.class, //
|
||||
project("item") //
|
||||
.and("discount")//
|
||||
.transform(ConditionalOperator.newBuilder().when(Criteria.where("qty").gte(250)) //
|
||||
.then(30) //
|
||||
.otherwise(20)));
|
||||
|
||||
assertThat(aggregation.toString(), is(notNullValue()));
|
||||
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(aggregation, DBObject.class);
|
||||
assertThat(result.getMappedResults().size(), is(3));
|
||||
|
||||
DBObject first = result.getMappedResults().get(0);
|
||||
assertThat(first.get("_id"), is((Object) 1));
|
||||
assertThat(first.get("discount"), is((Object) 30));
|
||||
|
||||
DBObject second = result.getMappedResults().get(1);
|
||||
assertThat(second.get("_id"), is((Object) 2));
|
||||
assertThat(second.get("discount"), is((Object) 20));
|
||||
|
||||
DBObject third = result.getMappedResults().get(2);
|
||||
assertThat(third.get("_id"), is((Object) 3));
|
||||
assertThat(third.get("discount"), is((Object) 30));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
* @see https://docs.mongodb.com/manual/reference/operator/aggregation/ifNull/#example
|
||||
*/
|
||||
@Test
|
||||
public void aggregationUsingIfNullToProjectSaneDefaults() {
|
||||
|
||||
/*
|
||||
db.inventory.aggregate(
|
||||
[
|
||||
{
|
||||
$project: {
|
||||
item: 1,
|
||||
description: { $ifNull: [ "$description", "Unspecified" ] }
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
*/
|
||||
|
||||
mongoTemplate.insert(new InventoryItem(1, "abc1", "product 1", 300));
|
||||
mongoTemplate.insert(new InventoryItem(2, "abc2", 200));
|
||||
mongoTemplate.insert(new InventoryItem(3, "xyz1", 250));
|
||||
|
||||
TypedAggregation<InventoryItem> aggregation = newAggregation(InventoryItem.class, //
|
||||
project("item") //
|
||||
.and(ifNull("description", "Unspecified")) //
|
||||
.as("description")//
|
||||
);
|
||||
|
||||
assertThat(aggregation.toString(), is(notNullValue()));
|
||||
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(aggregation, DBObject.class);
|
||||
assertThat(result.getMappedResults().size(), is(3));
|
||||
|
||||
DBObject first = result.getMappedResults().get(0);
|
||||
assertThat(first.get("_id"), is((Object) 1));
|
||||
assertThat(first.get("description"), is((Object) "product 1"));
|
||||
|
||||
DBObject second = result.getMappedResults().get(1);
|
||||
assertThat(second.get("_id"), is((Object) 2));
|
||||
assertThat(second.get("description"), is((Object) "Unspecified"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void aggregationUsingConditionalProjection() {
|
||||
|
||||
TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class, //
|
||||
project() //
|
||||
.and("largePopulation")//
|
||||
.transform(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(20000)) //
|
||||
.then(true) //
|
||||
.otherwise(false)) //
|
||||
.and("population").as("population"));
|
||||
|
||||
assertThat(aggregation, is(notNullValue()));
|
||||
assertThat(aggregation.toString(), is(notNullValue()));
|
||||
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(aggregation, DBObject.class);
|
||||
assertThat(result.getMappedResults().size(), is(29467));
|
||||
|
||||
DBObject firstZipInfoStats = result.getMappedResults().get(0);
|
||||
assertThat(firstZipInfoStats.get("largePopulation"), is((Object) false));
|
||||
assertThat(firstZipInfoStats.get("population"), is((Object) 6055));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void aggregationUsingNestedConditionalProjection() {
|
||||
|
||||
TypedAggregation<ZipInfo> aggregation = newAggregation(ZipInfo.class, //
|
||||
project() //
|
||||
.and("size")//
|
||||
.transform(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(20000)) //
|
||||
.then(ConditionalOperator.newBuilder().when(Criteria.where("population").gte(200000)).then("huge")
|
||||
.otherwise("small")) //
|
||||
.otherwise("small")) //
|
||||
.and("population").as("population"));
|
||||
|
||||
assertThat(aggregation, is(notNullValue()));
|
||||
assertThat(aggregation.toString(), is(notNullValue()));
|
||||
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(aggregation, DBObject.class);
|
||||
assertThat(result.getMappedResults().size(), is(29467));
|
||||
|
||||
DBObject firstZipInfoStats = result.getMappedResults().get(0);
|
||||
assertThat(firstZipInfoStats.get("size"), is((Object) "small"));
|
||||
assertThat(firstZipInfoStats.get("population"), is((Object) 6055));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void aggregationUsingIfNullProjection() {
|
||||
|
||||
mongoTemplate.insert(new LineItem("id", "caption", 0));
|
||||
mongoTemplate.insert(new LineItem("idonly", null, 0));
|
||||
|
||||
TypedAggregation<LineItem> aggregation = newAggregation(LineItem.class, //
|
||||
project("id") //
|
||||
.and("caption")//
|
||||
.transform(ifNull(field("caption"), "unknown")),
|
||||
sort(ASC, "id"));
|
||||
|
||||
assertThat(aggregation.toString(), is(notNullValue()));
|
||||
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(aggregation, DBObject.class);
|
||||
assertThat(result.getMappedResults().size(), is(2));
|
||||
|
||||
DBObject id = result.getMappedResults().get(0);
|
||||
assertThat((String) id.get("caption"), is(equalTo("caption")));
|
||||
|
||||
DBObject idonly = result.getMappedResults().get(1);
|
||||
assertThat((String) idonly.get("caption"), is(equalTo("unknown")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void aggregationUsingIfNullReplaceWithFieldReferenceProjection() {
|
||||
|
||||
mongoTemplate.insert(new LineItem("id", "caption", 0));
|
||||
mongoTemplate.insert(new LineItem("idonly", null, 0));
|
||||
|
||||
TypedAggregation<LineItem> aggregation = newAggregation(LineItem.class, //
|
||||
project("id") //
|
||||
.and("caption")//
|
||||
.transform(ifNull(field("caption"), field("id"))),
|
||||
sort(ASC, "id"));
|
||||
|
||||
assertThat(aggregation.toString(), is(notNullValue()));
|
||||
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(aggregation, DBObject.class);
|
||||
assertThat(result.getMappedResults().size(), is(2));
|
||||
|
||||
DBObject id = result.getMappedResults().get(0);
|
||||
assertThat((String) id.get("caption"), is(equalTo("caption")));
|
||||
|
||||
DBObject idonly = result.getMappedResults().get(1);
|
||||
assertThat((String) idonly.get("caption"), is(equalTo("idonly")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void shouldAllowGroupingUsingConditionalExpressions() {
|
||||
|
||||
mongoTemplate.dropCollection(CarPerson.class);
|
||||
|
||||
CarPerson person1 = new CarPerson("first1", "last1", new CarDescriptor.Entry("MAKE1", "MODEL1", 2000),
|
||||
new CarDescriptor.Entry("MAKE1", "MODEL2", 2001));
|
||||
|
||||
CarPerson person2 = new CarPerson("first2", "last2", new CarDescriptor.Entry("MAKE3", "MODEL4", 2014));
|
||||
CarPerson person3 = new CarPerson("first3", "last3", new CarDescriptor.Entry("MAKE2", "MODEL5", 2015));
|
||||
|
||||
mongoTemplate.save(person1);
|
||||
mongoTemplate.save(person2);
|
||||
mongoTemplate.save(person3);
|
||||
|
||||
TypedAggregation<CarPerson> agg = Aggregation.newAggregation(CarPerson.class,
|
||||
unwind("descriptors.carDescriptor.entries"), //
|
||||
project() //
|
||||
.and(new ConditionalOperator(Criteria.where("descriptors.carDescriptor.entries.make").is("MAKE1"), "good",
|
||||
"meh"))
|
||||
.as("make") //
|
||||
.and("descriptors.carDescriptor.entries.model").as("model") //
|
||||
.and("descriptors.carDescriptor.entries.year").as("year"), //
|
||||
group("make").avg(new ConditionalOperator(Criteria.where("year").gte(2012), 1, 9000)).as("score"),
|
||||
sort(ASC, "make"));
|
||||
|
||||
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, DBObject.class);
|
||||
|
||||
assertThat(result.getMappedResults(), hasSize(2));
|
||||
|
||||
DBObject meh = result.getMappedResults().get(0);
|
||||
assertThat((String) meh.get("_id"), is(equalTo("meh")));
|
||||
assertThat(((Number) meh.get("score")).longValue(), is(equalTo(1L)));
|
||||
|
||||
DBObject good = result.getMappedResults().get(1);
|
||||
assertThat((String) good.get("_id"), is(equalTo("good")));
|
||||
assertThat(((Number) good.get("score")).longValue(), is(equalTo(9000L)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://docs.mongodb.org/manual/tutorial/aggregation-examples/#return-the-five-most-common-likes
|
||||
*/
|
||||
@@ -1460,4 +1708,31 @@ public class AggregationTests {
|
||||
this.dateValue = dateValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Document(collection = "inventory")
|
||||
static class InventoryItem {
|
||||
|
||||
int id;
|
||||
String item;
|
||||
String description;
|
||||
int qty;
|
||||
|
||||
public InventoryItem() {}
|
||||
|
||||
public InventoryItem(int id, String item, int qty) {
|
||||
this.id = id;
|
||||
this.item = item;
|
||||
this.qty = qty;
|
||||
}
|
||||
|
||||
public InventoryItem(int id, String item, String description, int qty) {
|
||||
this.id = id;
|
||||
this.item = item;
|
||||
this.description = description;
|
||||
this.qty = qty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.DBObjectTestUtils.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
|
||||
@@ -29,7 +30,10 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.test.util.BasicDbListBuilder;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.BasicDBObjectBuilder;
|
||||
import com.mongodb.DBObject;
|
||||
@@ -243,6 +247,25 @@ public class AggregationUnitTests {
|
||||
assertThat(fields.get("foosum"), is((Object) new BasicDBObject("$sum", "$foo")));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void conditionExpressionBasedFieldsShouldBeReferencableInFollowingOperations() {
|
||||
|
||||
DBObject agg = newAggregation( //
|
||||
project("a"), //
|
||||
group("a").first(conditional(Criteria.where("a").gte(42), "answer", "no-answer")).as("foosum") //
|
||||
).toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
DBObject secondProjection = ((List<DBObject>) agg.get("pipeline")).get(1);
|
||||
DBObject fields = getAsDBObject(secondProjection, "$group");
|
||||
assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first"));
|
||||
assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.then", "answer"));
|
||||
assertThat(getAsDBObject(fields, "foosum"), isBsonObject().containing("$first.$cond.else", "no-answer"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-908
|
||||
*/
|
||||
@@ -379,6 +402,160 @@ public class AggregationUnitTests {
|
||||
assertThat(getAsDBObject(group, "count"), is(new BasicDBObjectBuilder().add("$sum", "$tags_count").get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderProjectionConditionalExpressionCorrectly() {
|
||||
|
||||
DBObject agg = Aggregation.newAggregation(//
|
||||
project().and(ConditionalOperator.newBuilder() //
|
||||
.when("isYellow") //
|
||||
.then("bright") //
|
||||
.otherwise("dark")).as("color"))
|
||||
.toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject project = extractPipelineElement(agg, 0, "$project");
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if", "$isYellow") //
|
||||
.append("then", "bright") //
|
||||
.append("else", "dark");
|
||||
|
||||
assertThat(getAsDBObject(project, "color"), isBsonObject().containing("$cond", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderProjectionConditionalCorrectly() {
|
||||
|
||||
DBObject agg = Aggregation.newAggregation(//
|
||||
project().and("color")
|
||||
.transform(ConditionalOperator.newBuilder() //
|
||||
.when("isYellow") //
|
||||
.then("bright") //
|
||||
.otherwise("dark")))
|
||||
.toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject project = extractPipelineElement(agg, 0, "$project");
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if", "$isYellow") //
|
||||
.append("then", "bright") //
|
||||
.append("else", "dark");
|
||||
|
||||
assertThat(getAsDBObject(project, "color"), isBsonObject().containing("$cond", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderProjectionConditionalWithCriteriaCorrectly() {
|
||||
|
||||
DBObject agg = Aggregation
|
||||
.newAggregation(project()//
|
||||
.and("color")//
|
||||
.transform(conditional(Criteria.where("key").gt(5), "bright", "dark"))) //
|
||||
.toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject project = extractPipelineElement(agg, 0, "$project");
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if", new BasicDBObject("$gt", new BasicDbListBuilder().add("$key").add(5).get())) //
|
||||
.append("then", "bright") //
|
||||
.append("else", "dark");
|
||||
|
||||
assertThat(getAsDBObject(project, "color"), isBsonObject().containing("$cond", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void referencingProjectionAliasesShouldRenderProjectionConditionalWithFieldReferenceCorrectly() {
|
||||
|
||||
DBObject agg = Aggregation
|
||||
.newAggregation(//
|
||||
project().and("color").as("chroma"),
|
||||
project().and("luminosity") //
|
||||
.transform(conditional(field("chroma"), "bright", "dark"))) //
|
||||
.toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject project = extractPipelineElement(agg, 1, "$project");
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if", "$chroma") //
|
||||
.append("then", "bright") //
|
||||
.append("else", "dark");
|
||||
|
||||
assertThat(getAsDBObject(project, "luminosity"), isBsonObject().containing("$cond", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void referencingProjectionAliasesShouldRenderProjectionConditionalWithCriteriaReferenceCorrectly() {
|
||||
|
||||
DBObject agg = Aggregation
|
||||
.newAggregation(//
|
||||
project().and("color").as("chroma"),
|
||||
project().and("luminosity") //
|
||||
.transform(conditional(Criteria.where("chroma").is(100), "bright", "dark"))) //
|
||||
.toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject project = extractPipelineElement(agg, 1, "$project");
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if", new BasicDBObject("$eq", new BasicDbListBuilder().add("$chroma").add(100).get())) //
|
||||
.append("then", "bright") //
|
||||
.append("else", "dark");
|
||||
|
||||
assertThat(getAsDBObject(project, "luminosity"), isBsonObject().containing("$cond", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderProjectionIfNullWithFieldReferenceCorrectly() {
|
||||
|
||||
DBObject agg = Aggregation
|
||||
.newAggregation(//
|
||||
project().and("color"), //
|
||||
project().and("luminosity") //
|
||||
.transform(ifNull(field("chroma"), "unknown"))) //
|
||||
.toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject project = extractPipelineElement(agg, 1, "$project");
|
||||
BasicDBList expectedCondition = new BasicDbListBuilder() //
|
||||
.add("$chroma") //
|
||||
.add("unknown")//
|
||||
.get();
|
||||
|
||||
assertThat(getAsDBObject(project, "luminosity"), isBsonObject().containing("$ifNull", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void shouldRenderProjectionIfNullWithFallbackFieldReferenceCorrectly() {
|
||||
|
||||
DBObject agg = Aggregation
|
||||
.newAggregation(//
|
||||
project("fallback").and("color").as("chroma"),
|
||||
project().and("luminosity") //
|
||||
.transform(ifNull(field("chroma"), field("fallback")))) //
|
||||
.toDbObject("foo", Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject project = extractPipelineElement(agg, 1, "$project");
|
||||
BasicDBList expectedCondition = new BasicDbListBuilder() //
|
||||
.add("$chroma") //
|
||||
.add("$fallback")//
|
||||
.get();
|
||||
|
||||
assertThat(getAsDBObject(project, "luminosity"), isBsonObject().containing("$ifNull", expectedCondition));
|
||||
}
|
||||
|
||||
private DBObject extractPipelineElement(DBObject agg, int index, String operation) {
|
||||
|
||||
List<DBObject> pipeline = (List<DBObject>) agg.get("pipeline");
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2016 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.aggregation;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.ConditionalOperator.*;
|
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.test.util.BasicDbListBuilder;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ConditionalOperator}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class ConditionalOperatorUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldRejectNullCondition() {
|
||||
new ConditionalOperator((Field) null, "", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldRejectThenValue() {
|
||||
new ConditionalOperator(Fields.field("field"), null, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldRejectOtherwiseValue() {
|
||||
new ConditionalOperator(Fields.field("field"), "", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void builderRejectsEmptyFieldName() {
|
||||
newBuilder().when("");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void builderRejectsNullFieldName() {
|
||||
newBuilder().when((DBObject) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void builderRejectsNullCriteriaName() {
|
||||
newBuilder().when((Criteria) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void builderRejectsBuilderAsThenValue() {
|
||||
newBuilder().when("isYellow").then(newBuilder().when("field").then("then-value")).otherwise("otherwise");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void simpleBuilderShouldRenderCorrectly() {
|
||||
|
||||
ConditionalOperator operator = newBuilder().when("isYellow").then("bright").otherwise("dark");
|
||||
DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if", "$isYellow") //
|
||||
.append("then", "bright") //
|
||||
.append("else", "dark");
|
||||
|
||||
assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void simpleCriteriaShouldRenderCorrectly() {
|
||||
|
||||
ConditionalOperator operator = newBuilder().when(Criteria.where("luminosity").gte(100)).then("bright")
|
||||
.otherwise("dark");
|
||||
DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if", new BasicDBObject("$gte", new BasicDbListBuilder().add("$luminosity").add(100).get())) //
|
||||
.append("then", "bright") //
|
||||
.append("else", "dark");
|
||||
|
||||
assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void andCriteriaShouldRenderCorrectly() {
|
||||
|
||||
ConditionalOperator operator = newBuilder() //
|
||||
.when(Criteria.where("luminosity").gte(100) //
|
||||
.andOperator(Criteria.where("hue").is(50), //
|
||||
Criteria.where("saturation").lt(11)))
|
||||
.then("bright").otherwise("dark");
|
||||
|
||||
DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
BasicDBObject luminosity = new BasicDBObject("$gte", new BasicDbListBuilder().add("$luminosity").add(100).get());
|
||||
BasicDBObject hue = new BasicDBObject("$eq", new BasicDbListBuilder().add("$hue").add(50).get());
|
||||
BasicDBObject saturation = new BasicDBObject("$lt", new BasicDbListBuilder().add("$saturation").add(11).get());
|
||||
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if",
|
||||
new BasicDbListBuilder().add(luminosity)
|
||||
.add(new BasicDBObject("$and", new BasicDbListBuilder().add(hue).add(saturation).get())).get()) //
|
||||
.append("then", "bright") //
|
||||
.append("else", "dark");
|
||||
|
||||
assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void twoArgsCriteriaShouldRenderCorrectly() {
|
||||
|
||||
Criteria criteria = Criteria.where("luminosity").gte(100) //
|
||||
.and("saturation").and("chroma").is(200);
|
||||
ConditionalOperator operator = newBuilder().when(criteria).then("bright").otherwise("dark");
|
||||
|
||||
DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
BasicDBObject gte = new BasicDBObject("$gte", new BasicDbListBuilder().add("$luminosity").add(100).get());
|
||||
BasicDBObject is = new BasicDBObject("$eq", new BasicDbListBuilder().add("$chroma").add(200).get());
|
||||
|
||||
DBObject expectedCondition = new BasicDBObject() //
|
||||
.append("if", new BasicDbListBuilder().add(gte).add(is).get()) //
|
||||
.append("then", "bright") //
|
||||
.append("else", "dark");
|
||||
|
||||
assertThat(dbObject, isBsonObject().containing("$cond", expectedCondition));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void nestedCriteriaShouldRenderCorrectly() {
|
||||
|
||||
ConditionalOperator operator = newBuilder() //
|
||||
.when(Criteria.where("luminosity").gte(100)) //
|
||||
.then(newBuilder() //
|
||||
.when(Criteria.where("luminosity").gte(200)) //
|
||||
.then("verybright") //
|
||||
.otherwise("not-so-bright")) //
|
||||
.otherwise(newBuilder() //
|
||||
.when(Criteria.where("luminosity").lt(50)) //
|
||||
.then("very-dark") //
|
||||
.otherwise("not-so-dark"));
|
||||
|
||||
DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
DBObject trueCondition = new BasicDBObject() //
|
||||
.append("if", new BasicDBObject("$gte", new BasicDbListBuilder().add("$luminosity").add(200).get())) //
|
||||
.append("then", "verybright") //
|
||||
.append("else", "not-so-bright");
|
||||
|
||||
DBObject falseCondition = new BasicDBObject() //
|
||||
.append("if", new BasicDBObject("$lt", new BasicDbListBuilder().add("$luminosity").add(50).get())) //
|
||||
.append("then", "very-dark") //
|
||||
.append("else", "not-so-dark");
|
||||
|
||||
assertThat(dbObject, isBsonObject().containing("$cond.then.$cond", trueCondition));
|
||||
assertThat(dbObject, isBsonObject().containing("$cond.else.$cond", falseCondition));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2016 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.aggregation;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mongodb.test.util.BasicDbListBuilder;
|
||||
|
||||
import com.mongodb.BasicDBList;
|
||||
import com.mongodb.DBObject;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link IfNullOperator}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class IfNullOperatorUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldRejectNullCondition() {
|
||||
new IfNullOperator(null, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void shouldRejectThenValue() {
|
||||
new IfNullOperator(Fields.field("aa"), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void simpleIfNullShouldRenderCorrectly() {
|
||||
|
||||
IfNullOperator operator = IfNullOperator.newBuilder() //
|
||||
.ifNull("optional") //
|
||||
.thenReplaceWith("a more sophisticated value");
|
||||
|
||||
DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
BasicDBList expected = new BasicDbListBuilder() //
|
||||
.add("$optional") //
|
||||
.add("a more sophisticated value")//
|
||||
.get();
|
||||
|
||||
assertThat(dbObject, isBsonObject().containing("$ifNull", expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void fieldReplacementIfNullShouldRenderCorrectly() {
|
||||
|
||||
IfNullOperator operator = IfNullOperator.newBuilder() //
|
||||
.ifNull(Fields.field("optional")) //
|
||||
.thenReplaceWith(Fields.field("never-null"));
|
||||
|
||||
DBObject dbObject = operator.toDbObject(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
BasicDBList expected = new BasicDbListBuilder() //
|
||||
.add("$optional") //
|
||||
.add("$never-null")//
|
||||
.get();
|
||||
|
||||
assertThat(dbObject, isBsonObject().containing("$ifNull", expected));
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core.aggregation;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
|
||||
import static org.springframework.data.mongodb.test.util.IsBsonObject.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -89,7 +91,7 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
|
||||
AggregationOperationContext context = getContext(Foo.class);
|
||||
|
||||
Field field = Fields.field("bar.name");
|
||||
Field field = field("bar.name");
|
||||
|
||||
assertThat(context.getReference("bar.name"), is(notNullValue()));
|
||||
assertThat(context.getReference(field), is(notNullValue()));
|
||||
@@ -103,7 +105,7 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
public void aliasesIdFieldCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(Foo.class);
|
||||
assertThat(context.getReference("id"), is(new FieldReference(new ExposedField(Fields.field("id", "_id"), true))));
|
||||
assertThat(context.getReference("id"), is(new FieldReference(new ExposedField(field("id", "_id"), true))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,6 +281,58 @@ public class TypeBasedAggregationOperationContextUnitTests {
|
||||
agg.toDbObject("meterData", context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void rendersAggregationConditionalInTypedAggregationContextCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(FooPerson.class);
|
||||
TypedAggregation<FooPerson> agg = newAggregation(FooPerson.class,
|
||||
project("name") //
|
||||
.and("age") //
|
||||
.transform(conditional(Criteria.where("age.value").lt(10), new Age(0), field("age"))) //
|
||||
);
|
||||
|
||||
DBObject dbo = agg.toDbObject("person", context);
|
||||
|
||||
DBObject projection = getPipelineElementFromAggregationAt(dbo, 0);
|
||||
assertThat(projection.containsField("$project"), is(true));
|
||||
|
||||
DBObject project = getValue(projection, "$project");
|
||||
DBObject age = getValue(project, "age");
|
||||
|
||||
assertThat((DBObject) getValue(age, "$cond"), isBsonObject().containing("then.value", 0));
|
||||
assertThat((DBObject) getValue(age, "$cond"), isBsonObject().containing("then._class", Age.class.getName()));
|
||||
assertThat((DBObject) getValue(age, "$cond"), isBsonObject().containing("else", "$age"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAMONGO-861
|
||||
*/
|
||||
@Test
|
||||
public void rendersAggregationIfNullInTypedAggregationContextCorrectly() {
|
||||
|
||||
AggregationOperationContext context = getContext(FooPerson.class);
|
||||
TypedAggregation<FooPerson> agg = newAggregation(FooPerson.class,
|
||||
project("name") //
|
||||
.and("age") //
|
||||
.transform(ifNull("age", new Age(0))) //
|
||||
);
|
||||
|
||||
DBObject dbo = agg.toDbObject("person", context);
|
||||
|
||||
DBObject projection = getPipelineElementFromAggregationAt(dbo, 0);
|
||||
assertThat(projection.containsField("$project"), is(true));
|
||||
|
||||
DBObject project = getValue(projection, "$project");
|
||||
DBObject age = getValue(project, "age");
|
||||
|
||||
assertThat(age, isBsonObject().containing("$ifNull.[0]", "$age"));
|
||||
assertThat(age, isBsonObject().containing("$ifNull.[1].value", 0));
|
||||
assertThat(age, isBsonObject().containing("$ifNull.[1]._class", Age.class.getName()));
|
||||
}
|
||||
|
||||
@Document(collection = "person")
|
||||
public static class FooPerson {
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
[[new-features.1-10-0]]
|
||||
== What's new in Spring Data MongoDB 1.10
|
||||
* Support for `$min`, `$max` and `$slice` operators via `Update`.
|
||||
* Support for `$cond` and `$ifNull` operators via `Aggregation`.
|
||||
|
||||
[[new-features.1-9-0]]
|
||||
== What's new in Spring Data MongoDB 1.9
|
||||
|
||||
@@ -1688,6 +1688,9 @@ At the time of this writing we provide support for the following Aggregation Ope
|
||||
| Array Aggregation Operators
|
||||
| size, slice
|
||||
|
||||
| Conditional Aggregation Operators
|
||||
| cond, ifNull
|
||||
|
||||
|===
|
||||
|
||||
Note that the aggregation operations not listed here are currently not supported by Spring Data MongoDB. Comparison aggregation operators are expressed as `Criteria` expressions.
|
||||
@@ -1985,6 +1988,50 @@ List<DBObject> resultList = result.getMappedResults();
|
||||
|
||||
Note that we can also refer to other fields of the document within the SpEL expression.
|
||||
|
||||
[[mongo.aggregation.examples.example6]]
|
||||
.Aggregation Framework Example 6
|
||||
|
||||
This example uses conditional projection. It's derived from the https://docs.mongodb.com/manual/reference/operator/aggregation/cond/[$cond reference documentation].
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class InventoryItem {
|
||||
|
||||
@Id int id;
|
||||
String item;
|
||||
String description;
|
||||
int qty;
|
||||
}
|
||||
|
||||
public class InventoryItemProjection {
|
||||
|
||||
@Id int id;
|
||||
String item;
|
||||
String description;
|
||||
int qty;
|
||||
int discount
|
||||
}
|
||||
----
|
||||
|
||||
[source,java]
|
||||
----
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
|
||||
TypedAggregation<InventoryItem> agg = newAggregation(InventoryItem.class,
|
||||
project("item").and("discount")
|
||||
.transform(ConditionalOperator.newBuilder().when(Criteria.where("qty").gte(250))
|
||||
.then(30)
|
||||
.otherwise(20))
|
||||
.and(ifNull("description", "Unspecified")).as("description")
|
||||
);
|
||||
|
||||
AggregationResults<InventoryItemProjection> result = mongoTemplate.aggregate(agg, "inventory", InventoryItemProjection.class);
|
||||
List<InventoryItemProjection> stateStatsList = result.getMappedResults();
|
||||
----
|
||||
|
||||
* This one-step aggregation uses a projection operation with the `inventory` collection. We project the `discount` field using a conditional transformation for all inventory items that have a `qty` greater or equal to `250`. An second conditional projection is performed for the `description` field. We apply the description `Unspecified` to all items that either do not have a `description` field of items that have a `null` description.
|
||||
|
||||
|
||||
[[mongo.custom-converters]]
|
||||
== Overriding default mapping with custom converters
|
||||
|
||||
|
||||
Reference in New Issue
Block a user