diff --git a/pom.xml b/pom.xml
index ea80a3cb74..19b54876d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1548-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml
index ae0a5d6c8f..08d1e76ce9 100644
--- a/spring-data-mongodb-cross-store/pom.xml
+++ b/spring-data-mongodb-cross-store/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1548-SNAPSHOT
../pom.xml
@@ -48,7 +48,7 @@
org.springframework.data
spring-data-mongodb
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1548-SNAPSHOT
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index 2d02722262..5ab3647b24 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1548-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml
index ee5e3336db..0142c1b99a 100644
--- a/spring-data-mongodb-log4j/pom.xml
+++ b/spring-data-mongodb-log4j/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1548-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index 8072d3f665..8bdc1b020d 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -11,7 +11,7 @@
org.springframework.data
spring-data-mongodb-parent
- 1.10.0.BUILD-SNAPSHOT
+ 1.10.0.DATAMONGO-1548-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
index 2b5e873747..0c86caf1e7 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationExpressions.java
@@ -23,10 +23,14 @@
import java.util.List;
import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.data.domain.Range;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators.ArithmeticOperatorFactory;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.OtherwiseBuilder;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Cond.ThenBuilder;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder;
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Let.ExpressionVariable;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Reduce.PropertyExpression;
+import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Switch.CaseOperator;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.util.Assert;
@@ -262,6 +266,30 @@ public static IfNull.ThenBuilder ifNull(AggregationExpression expression) {
return IfNull.ifNull(expression);
}
+ /**
+ * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it
+ * finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and
+ * breaks out of the control flow.
+ *
+ * @param conditions must not be {@literal null}.
+ * @return
+ */
+ public static Switch switchCases(CaseOperator... conditions) {
+ return Switch.switchCases(conditions);
+ }
+
+ /**
+ * Creates new {@link AggregationExpression} that evaluates a series of {@link CaseOperator} expressions. When it
+ * finds an expression which evaluates to {@literal true}, {@code $switch} executes a specified expression and
+ * breaks out of the control flow.
+ *
+ * @param conditions must not be {@literal null}.
+ * @return
+ */
+ public static Switch switchCases(List conditions) {
+ return Switch.switchCases(conditions);
+ }
+
public static class ConditionalOperatorFactory {
private final String fieldReference;
@@ -1564,6 +1592,184 @@ public StrCaseCmp strCaseCmpValueOf(AggregationExpression expression) {
private StrCaseCmp createStrCaseCmp() {
return fieldReference != null ? StrCaseCmp.valueOf(fieldReference) : StrCaseCmp.valueOf(expression);
}
+
+ /**
+ * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+ * string for an occurrence of a given {@literal substring} and returns the UTF-8 byte index (zero-based) of the
+ * first occurrence.
+ *
+ * @param substring must not be {@literal null}.
+ * @return
+ */
+ public IndexOfBytes indexOf(String substring) {
+
+ Assert.notNull(substring, "Substring must not be null!");
+ return createIndexOfBytesSubstringBuilder().indexOf(substring);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+ * string for an occurrence of a substring contained in the given {@literal field reference} and returns the UTF-8
+ * byte index (zero-based) of the first occurrence.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public IndexOfBytes indexOf(Field fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return createIndexOfBytesSubstringBuilder().indexOf(fieldReference);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+ * string for an occurrence of a substring resulting from the given {@link AggregationExpression} and returns the
+ * UTF-8 byte index (zero-based) of the first occurrence.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public IndexOfBytes indexOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return createIndexOfBytesSubstringBuilder().indexOf(expression);
+ }
+
+ private IndexOfBytes.SubstringBuilder createIndexOfBytesSubstringBuilder() {
+ return fieldReference != null ? IndexOfBytes.valueOf(fieldReference) : IndexOfBytes.valueOf(expression);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+ * string for an occurrence of a given {@literal substring} and returns the UTF-8 code point index (zero-based) of
+ * the first occurrence.
+ *
+ * @param substring must not be {@literal null}.
+ * @return
+ */
+ public IndexOfCP indexOfCP(String substring) {
+
+ Assert.notNull(substring, "Substring must not be null!");
+ return createIndexOfCPSubstringBuilder().indexOf(substring);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+ * string for an occurrence of a substring contained in the given {@literal field reference} and returns the UTF-8
+ * code point index (zero-based) of the first occurrence.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public IndexOfCP indexOfCP(Field fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return createIndexOfCPSubstringBuilder().indexOf(fieldReference);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that takes the associated string representation and searches a
+ * string for an occurrence of a substring resulting from the given {@link AggregationExpression} and returns the
+ * UTF-8 code point index (zero-based) of the first occurrence.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public IndexOfCP indexOfCP(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return createIndexOfCPSubstringBuilder().indexOf(expression);
+ }
+
+ private IndexOfCP.SubstringBuilder createIndexOfCPSubstringBuilder() {
+ return fieldReference != null ? IndexOfCP.valueOf(fieldReference) : IndexOfCP.valueOf(expression);
+ }
+
+ /**
+ * Creates new {@link AggregationExpression} that divides the associated string representation into an array of
+ * substrings based on the given delimiter.
+ *
+ * @param delimiter must not be {@literal null}.
+ * @return
+ */
+ public Split split(String delimiter) {
+ return createSplit().split(delimiter);
+ }
+
+ /**
+ * Creates new {@link AggregationExpression} that divides the associated string representation into an array of
+ * substrings based on the delimiter resulting from the referenced field..
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public Split split(Field fieldReference) {
+ return createSplit().split(fieldReference);
+ }
+
+ /**
+ * Creates new {@link AggregationExpression} that divides the associated string representation into an array of
+ * substrings based on a delimiter resulting from the given {@link AggregationExpression}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public Split split(AggregationExpression expression) {
+ return createSplit().split(expression);
+ }
+
+ private Split createSplit() {
+ return fieldReference != null ? Split.valueOf(fieldReference) : Split.valueOf(expression);
+ }
+
+ /**
+ * Creates new {@link AggregationExpression} that returns the number of UTF-8 bytes in the associated string
+ * representation.
+ *
+ * @return
+ */
+ public StrLenBytes length() {
+ return fieldReference != null ? StrLenBytes.stringLengthOf(fieldReference)
+ : StrLenBytes.stringLengthOf(expression);
+ }
+
+ /**
+ * Creates new {@link AggregationExpression} that returns the number of UTF-8 code points in the associated string
+ * representation.
+ *
+ * @return
+ */
+ public StrLenCP lengthCP() {
+ return fieldReference != null ? StrLenCP.stringLengthOfCP(fieldReference)
+ : StrLenCP.stringLengthOfCP(expression);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a
+ * substring starting at a specified code point index position.
+ *
+ * @param codePointStart
+ * @return
+ */
+ public SubstrCP substringCP(int codePointStart) {
+ return substringCP(codePointStart, -1);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that takes the associated string representation and returns a
+ * substring starting at a specified code point index position including the specified number of code points.
+ *
+ * @param codePointStart
+ * @param nrOfCodePoints
+ * @return
+ */
+ public SubstrCP substringCP(int codePointStart, int nrOfCodePoints) {
+ return createSubstrCP().substringCP(codePointStart, nrOfCodePoints);
+ }
+
+ private SubstrCP createSubstrCP() {
+ return fieldReference != null ? SubstrCP.valueOf(fieldReference) : SubstrCP.valueOf(expression);
+ }
}
}
@@ -1731,6 +1937,101 @@ public Slice slice() {
return usesFieldRef() ? Slice.sliceArrayOf(fieldReference) : Slice.sliceArrayOf(expression);
}
+ /**
+ * Creates new {@link AggregationExpressions} that searches the associated array for an occurrence of a specified
+ * value and returns the array index (zero-based) of the first occurrence.
+ *
+ * @param value must not be {@literal null}.
+ * @return
+ */
+ public IndexOfArray indexOf(Object value) {
+ return usesFieldRef() ? IndexOfArray.arrayOf(fieldReference).indexOf(value)
+ : IndexOfArray.arrayOf(expression).indexOf(value);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that returns an array with the elements in reverse order.
+ *
+ * @return
+ */
+ public ReverseArray reverse() {
+ return usesFieldRef() ? ReverseArray.reverseArrayOf(fieldReference) : ReverseArray.reverseArrayOf(expression);
+ }
+
+ /**
+ * Start creating new {@link AggregationExpressions} that applies an {@link AggregationExpression} to each element
+ * in an array and combines them into a single value.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public ReduceInitialValueBuilder reduce(final AggregationExpression expression) {
+ return new ReduceInitialValueBuilder() {
+
+ @Override
+ public Reduce startingWith(Object initialValue) {
+ return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression))
+ .withInitialValue(initialValue).reduce(expression);
+ }
+ };
+ }
+
+ /**
+ * Start creating new {@link AggregationExpressions} that applies an {@link AggregationExpression} to each element
+ * in an array and combines them into a single value.
+ *
+ * @param expressions
+ * @return
+ */
+ public ReduceInitialValueBuilder reduce(final PropertyExpression... expressions) {
+
+ return new ReduceInitialValueBuilder() {
+
+ @Override
+ public Reduce startingWith(Object initialValue) {
+ return (usesFieldRef() ? Reduce.arrayOf(fieldReference) : Reduce.arrayOf(expression))
+ .withInitialValue(initialValue).reduce(expressions);
+ }
+ };
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that transposes an array of input arrays so that the first element
+ * of the output array would be an array containing, the first element of the first input array, the first element
+ * of the second input array, etc.
+ *
+ * @param arrays must not be {@literal null}.
+ * @return
+ */
+ public Zip zipWith(Object... arrays) {
+ return (usesFieldRef() ? Zip.arrayOf(fieldReference) : Zip.arrayOf(expression)).zip(arrays);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that returns a boolean indicating whether a specified value is in
+ * the associated array.
+ *
+ * @param value must not be {@literal null}.
+ * @return
+ */
+ public In containsValue(Object value) {
+ return (usesFieldRef() ? In.arrayOf(fieldReference) : In.arrayOf(expression)).containsValue(value);
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public interface ReduceInitialValueBuilder {
+
+ /**
+ * Define the initial cumulative value set before in is applied to the first element of the input array.
+ *
+ * @param initialValue must not be {@literal null}.
+ * @return
+ */
+ Reduce startingWith(Object initialValue);
+ }
+
private boolean usesFieldRef() {
return fieldReference != null;
}
@@ -1952,6 +2253,35 @@ public DateToString toString(String format) {
.toString(format);
}
+ /**
+ * Creates new {@link AggregationExpressions} that returns the weekday number in ISO 8601 format, ranging from 1
+ * (for Monday) to 7 (for Sunday).
+ *
+ * @return
+ */
+ public IsoDayOfWeek isoDayOfWeek() {
+ return usesFieldRef() ? IsoDayOfWeek.isoDayOfWeek(fieldReference) : IsoDayOfWeek.isoDayOfWeek(expression);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that returns the week number in ISO 8601 format, ranging from 1 to
+ * 53.
+ *
+ * @return
+ */
+ public IsoWeek isoWeek() {
+ return usesFieldRef() ? IsoWeek.isoWeekOf(fieldReference) : IsoWeek.isoWeekOf(expression);
+ }
+
+ /**
+ * Creates new {@link AggregationExpressions} that returns the year number in ISO 8601 format.
+ *
+ * @return
+ */
+ public IsoWeekYear isoWeekYear() {
+ return usesFieldRef() ? IsoWeekYear.isoWeekYearOf(fieldReference) : IsoWeekYear.isoWeekYearOf(expression);
+ }
+
private boolean usesFieldRef() {
return fieldReference != null;
}
@@ -2022,6 +2352,9 @@ protected AbstractAggregationExpression(Object value) {
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) {
return toDbObject(this.value, context);
@@ -2072,6 +2405,17 @@ private Object unpack(Object value, AggregationOperationContext context) {
return context.getReference((Field) value).toString();
}
+ if (value instanceof List) {
+
+ List sourceList = (List) value;
+ List mappedList = new ArrayList(sourceList.size());
+
+ for (Object item : sourceList) {
+ mappedList.add(unpack(item, context));
+ }
+ return mappedList;
+ }
+
return value;
}
@@ -2094,17 +2438,29 @@ protected List append(Object value) {
return Arrays.asList(this.value, value);
}
- protected Object append(String key, Object value) {
+ protected java.util.Map append(String key, Object value) {
- if (!(value instanceof java.util.Map)) {
+ if (!(this.value instanceof java.util.Map)) {
throw new IllegalArgumentException("o_O");
}
- java.util.Map clone = new LinkedHashMap((java.util.Map) value);
+ java.util.Map clone = new LinkedHashMap(
+ (java.util.Map) this.value);
clone.put(key, value);
return clone;
}
+ protected List values() {
+
+ if (value instanceof List) {
+ return new ArrayList((List) value);
+ }
+ if (value instanceof java.util.Map) {
+ return new ArrayList(((java.util.Map) value).values());
+ }
+ return new ArrayList(Arrays.asList(value));
+ }
+
protected abstract String getMongoMethod();
}
@@ -3442,6 +3798,10 @@ public static Trunc truncValueOf(Number value) {
}
}
+ // #########################################
+ // STRING OPERATORS
+ // #########################################
+
/**
* {@link AggregationExpression} for {@code $concat}.
*
@@ -3736,503 +4096,1633 @@ public StrCaseCmp strcasecmpValueOf(AggregationExpression expression) {
}
/**
- * {@link AggregationExpression} for {@code $arrayElementAt}.
+ * {@link AggregationExpression} for {@code $indexOfBytes}.
*
* @author Christoph Strobl
*/
- class ArrayElemAt extends AbstractAggregationExpression {
+ class IndexOfBytes extends AbstractAggregationExpression {
- private ArrayElemAt(List> value) {
+ private IndexOfBytes(List> value) {
super(value);
}
@Override
protected String getMongoMethod() {
- return "$arrayElemAt";
+ return "$indexOfBytes";
}
/**
- * Creates new {@link ArrayElemAt}.
+ * Start creating a new {@link IndexOfBytes}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
- public static ArrayElemAt arrayOf(String fieldReference) {
+ public static SubstringBuilder valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
- return new ArrayElemAt(asFields(fieldReference));
+ return new SubstringBuilder(Fields.field(fieldReference));
}
/**
- * Creates new {@link ArrayElemAt}.
+ * Start creating a new {@link IndexOfBytes}.
*
* @param expression must not be {@literal null}.
* @return
*/
- public static ArrayElemAt arrayOf(AggregationExpression expression) {
+ public static SubstringBuilder valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
- return new ArrayElemAt(Collections.singletonList(expression));
+ return new SubstringBuilder(expression);
}
- public ArrayElemAt elementAt(int index) {
- return new ArrayElemAt(append(index));
- }
+ /**
+ * Optionally define the substring search start and end position.
+ *
+ * @param range must not be {@literal null}.
+ * @return
+ */
+ public IndexOfBytes within(Range range) {
- public ArrayElemAt elementAt(AggregationExpression expression) {
+ Assert.notNull(range, "Range must not be null!");
- Assert.notNull(expression, "Expression must not be null!");
- return new ArrayElemAt(append(expression));
+ List rangeValues = new ArrayList(2);
+ rangeValues.add(range.getLowerBound());
+ if (range.getUpperBound() != null) {
+ rangeValues.add(range.getUpperBound());
+ }
+
+ return new IndexOfBytes(append(rangeValues));
}
- public ArrayElemAt elementAt(String arrayFieldReference) {
+ public static class SubstringBuilder {
- Assert.notNull(arrayFieldReference, "ArrayReference must not be null!");
- return new ArrayElemAt(append(Fields.field(arrayFieldReference)));
+ private final Object stringExpression;
+
+ private SubstringBuilder(Object stringExpression) {
+ this.stringExpression = stringExpression;
+ }
+
+ /**
+ * Creates a new {@link IndexOfBytes} given {@literal substring}.
+ *
+ * @param substring must not be {@literal null}.
+ * @return
+ */
+ public IndexOfBytes indexOf(String substring) {
+ return new IndexOfBytes(Arrays.asList(stringExpression, substring));
+ }
+
+ /**
+ * Creates a new {@link IndexOfBytes} given {@link AggregationExpression} that resolves to the substring.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public IndexOfBytes indexOf(AggregationExpression expression) {
+ return new IndexOfBytes(Arrays.asList(stringExpression, expression));
+ }
+
+ /**
+ * Creates a new {@link IndexOfBytes} given {@link Field} that resolves to the substring.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public IndexOfBytes indexOf(Field fieldReference) {
+ return new IndexOfBytes(Arrays.asList(stringExpression, fieldReference));
+ }
}
}
/**
- * {@link AggregationExpression} for {@code $concatArrays}.
+ * {@link AggregationExpression} for {@code $indexOfCP}.
*
* @author Christoph Strobl
*/
- class ConcatArrays extends AbstractAggregationExpression {
+ class IndexOfCP extends AbstractAggregationExpression {
- private ConcatArrays(List> value) {
+ private IndexOfCP(List> value) {
super(value);
}
@Override
protected String getMongoMethod() {
- return "$concatArrays";
+ return "$indexOfCP";
}
/**
- * Creates new {@link ConcatArrays}.
+ * Start creating a new {@link IndexOfCP}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
- public static ConcatArrays arrayOf(String fieldReference) {
+ public static SubstringBuilder valueOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
- return new ConcatArrays(asFields(fieldReference));
+ return new SubstringBuilder(Fields.field(fieldReference));
}
/**
- * Creates new {@link ConcatArrays}.
+ * Start creating a new {@link IndexOfCP}.
*
* @param expression must not be {@literal null}.
* @return
*/
- public static ConcatArrays arrayOf(AggregationExpression expression) {
+ public static SubstringBuilder valueOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
- return new ConcatArrays(Collections.singletonList(expression));
+ return new SubstringBuilder(expression);
}
- public ConcatArrays concat(String arrayFieldReference) {
+ /**
+ * Optionally define the substring search start and end position.
+ *
+ * @param range must not be {@literal null}.
+ * @return
+ */
+ public IndexOfCP within(Range range) {
+
+ Assert.notNull(range, "Range must not be null!");
+
+ List rangeValues = new ArrayList(2);
+ rangeValues.add(range.getLowerBound());
+ if (range.getUpperBound() != null) {
+ rangeValues.add(range.getUpperBound());
+ }
+
+ return new IndexOfCP(append(rangeValues));
+ }
+
+ public static class SubstringBuilder {
+
+ private final Object stringExpression;
+
+ private SubstringBuilder(Object stringExpression) {
+ this.stringExpression = stringExpression;
+ }
+
+ /**
+ * Creates a new {@link IndexOfCP} given {@literal substring}.
+ *
+ * @param substring must not be {@literal null}.
+ * @return
+ */
+ public IndexOfCP indexOf(String substring) {
+ return new IndexOfCP(Arrays.asList(stringExpression, substring));
+ }
+
+ /**
+ * Creates a new {@link IndexOfCP} given {@link AggregationExpression} that resolves to the substring.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public IndexOfCP indexOf(AggregationExpression expression) {
+ return new IndexOfCP(Arrays.asList(stringExpression, expression));
+ }
+
+ /**
+ * Creates a new {@link IndexOfCP} given {@link Field} that resolves to the substring.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public IndexOfCP indexOf(Field fieldReference) {
+ return new IndexOfCP(Arrays.asList(stringExpression, fieldReference));
+ }
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $split}.
+ *
+ * @author Christoph Strobl
+ */
+ class Split extends AbstractAggregationExpression {
+
+ private Split(List> values) {
+ super(values);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$split";
+ }
+
+ /**
+ * Start creating a new {@link Split}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static Split valueOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new Split(asFields(fieldReference));
+ }
+
+ /**
+ * Start creating a new {@link Split}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static Split valueOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new Split(Collections.singletonList(expression));
+ }
+
+ /**
+ * Use given {@link String} as delimiter.
+ *
+ * @param delimiter must not be {@literal null}.
+ * @return
+ */
+ public Split split(String delimiter) {
+
+ Assert.notNull(delimiter, "Delimiter must not be null!");
+ return new Split(append(delimiter));
+ }
+
+ /**
+ * Use value of referenced field as delimiter.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public Split split(Field fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new Split(append(fieldReference));
+ }
+
+ /**
+ * Use value resulting from {@link AggregationExpression} as delimiter.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public Split split(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new Split(append(expression));
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $strLenBytes}.
+ *
+ * @author Christoph Strobl
+ */
+ class StrLenBytes extends AbstractAggregationExpression {
+
+ private StrLenBytes(Object value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$strLenBytes";
+ }
+
+ /**
+ * Creates new {@link StrLenBytes}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static StrLenBytes stringLengthOf(String fieldReference) {
+ return new StrLenBytes(Fields.field(fieldReference));
+ }
+
+ /**
+ * Creates new {@link StrLenBytes}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static StrLenBytes stringLengthOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new StrLenBytes(expression);
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $strLenCP}.
+ *
+ * @author Christoph Strobl
+ */
+ class StrLenCP extends AbstractAggregationExpression {
+
+ private StrLenCP(Object value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$strLenCP";
+ }
+
+ /**
+ * Creates new {@link StrLenCP}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static StrLenCP stringLengthOfCP(String fieldReference) {
+ return new StrLenCP(Fields.field(fieldReference));
+ }
+
+ /**
+ * Creates new {@link StrLenCP}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static StrLenCP stringLengthOfCP(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new StrLenCP(expression);
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $substrCP}.
+ *
+ * @author Christoph Strobl
+ */
+ class SubstrCP extends AbstractAggregationExpression {
+
+ private SubstrCP(List> value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$substrCP";
+ }
+
+ /**
+ * Creates new {@link SubstrCP}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static SubstrCP valueOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new SubstrCP(asFields(fieldReference));
+ }
+
+ /**
+ * Creates new {@link SubstrCP}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static SubstrCP valueOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new SubstrCP(Collections.singletonList(expression));
+ }
+
+ public SubstrCP substringCP(int start) {
+ return substringCP(start, -1);
+ }
+
+ public SubstrCP substringCP(int start, int nrOfChars) {
+ return new SubstrCP(append(Arrays.asList(start, nrOfChars)));
+ }
+ }
+
+ // #########################################
+ // ARRAY OPERATORS
+ // #########################################
+
+ /**
+ * {@link AggregationExpression} for {@code $arrayElementAt}.
+ *
+ * @author Christoph Strobl
+ */
+ class ArrayElemAt extends AbstractAggregationExpression {
+
+ private ArrayElemAt(List> value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$arrayElemAt";
+ }
+
+ /**
+ * Creates new {@link ArrayElemAt}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static ArrayElemAt arrayOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new ArrayElemAt(asFields(fieldReference));
+ }
+
+ /**
+ * Creates new {@link ArrayElemAt}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static ArrayElemAt arrayOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new ArrayElemAt(Collections.singletonList(expression));
+ }
+
+ public ArrayElemAt elementAt(int index) {
+ return new ArrayElemAt(append(index));
+ }
+
+ public ArrayElemAt elementAt(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new ArrayElemAt(append(expression));
+ }
+
+ public ArrayElemAt elementAt(String arrayFieldReference) {
+
+ Assert.notNull(arrayFieldReference, "ArrayReference must not be null!");
+ return new ArrayElemAt(append(Fields.field(arrayFieldReference)));
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $concatArrays}.
+ *
+ * @author Christoph Strobl
+ */
+ class ConcatArrays extends AbstractAggregationExpression {
+
+ private ConcatArrays(List> value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$concatArrays";
+ }
+
+ /**
+ * Creates new {@link ConcatArrays}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static ConcatArrays arrayOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new ConcatArrays(asFields(fieldReference));
+ }
+
+ /**
+ * Creates new {@link ConcatArrays}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static ConcatArrays arrayOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new ConcatArrays(Collections.singletonList(expression));
+ }
+
+ public ConcatArrays concat(String arrayFieldReference) {
+
+ Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!");
+ return new ConcatArrays(append(Fields.field(arrayFieldReference)));
+ }
+
+ public ConcatArrays concat(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new ConcatArrays(append(expression));
+ }
+ }
+
+ /**
+ * {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the
+ * specified condition.
+ *
+ * @author Christoph Strobl
+ * @since 1.10
+ */
+ class Filter implements AggregationExpression {
+
+ private Object input;
+ private ExposedField as;
+ private Object condition;
+
+ private Filter() {
+ // used by builder
+ }
+
+ /**
+ * Set the {@literal field} to apply the {@code $filter} to.
+ *
+ * @param field must not be {@literal null}.
+ * @return never {@literal null}.
+ */
+ public static AsBuilder filter(String field) {
+
+ Assert.notNull(field, "Field must not be null!");
+ return filter(Fields.field(field));
+ }
+
+ /**
+ * Set the {@literal field} to apply the {@code $filter} to.
+ *
+ * @param field must not be {@literal null}.
+ * @return never {@literal null}.
+ */
+ public static AsBuilder filter(Field field) {
+
+ Assert.notNull(field, "Field must not be null!");
+ return new FilterExpressionBuilder().filter(field);
+ }
+
+ /**
+ * Set the {@literal values} to apply the {@code $filter} to.
+ *
+ * @param values must not be {@literal null}.
+ * @return
+ */
+ public static AsBuilder filter(List> values) {
+
+ Assert.notNull(values, "Values must not be null!");
+ return new FilterExpressionBuilder().filter(values);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
+ @Override
+ public DBObject toDbObject(final AggregationOperationContext context) {
+ return toFilter(ExposedFields.from(as), context);
+ }
+
+ private DBObject toFilter(ExposedFields exposedFields, AggregationOperationContext context) {
+
+ DBObject filterExpression = new BasicDBObject();
+ InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext(
+ exposedFields, context);
+
+ filterExpression.putAll(context.getMappedObject(new BasicDBObject("input", getMappedInput(context))));
+ filterExpression.put("as", as.getTarget());
+
+ filterExpression.putAll(context.getMappedObject(new BasicDBObject("cond", getMappedCondition(operationContext))));
+
+ return new BasicDBObject("$filter", filterExpression);
+ }
+
+ private Object getMappedInput(AggregationOperationContext context) {
+ return input instanceof Field ? context.getReference((Field) input).toString() : input;
+ }
+
+ private Object getMappedCondition(AggregationOperationContext context) {
+
+ if (!(condition instanceof AggregationExpression)) {
+ return condition;
+ }
+
+ NestedDelegatingExpressionAggregationOperationContext nea = new NestedDelegatingExpressionAggregationOperationContext(
+ context);
+ return ((AggregationExpression) condition).toDbObject(nea);
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public interface InputBuilder {
+
+ /**
+ * Set the {@literal values} to apply the {@code $filter} to.
+ *
+ * @param array must not be {@literal null}.
+ * @return
+ */
+ AsBuilder filter(List> array);
+
+ /**
+ * Set the {@literal field} holding an array to apply the {@code $filter} to.
+ *
+ * @param field must not be {@literal null}.
+ * @return
+ */
+ AsBuilder filter(Field field);
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public interface AsBuilder {
+
+ /**
+ * Set the {@literal variableName} for the elements in the input array.
+ *
+ * @param variableName must not be {@literal null}.
+ * @return
+ */
+ ConditionBuilder as(String variableName);
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public interface ConditionBuilder {
+
+ /**
+ * Set the {@link AggregationExpression} that determines whether to include the element in the resulting array.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ Filter by(AggregationExpression expression);
+
+ /**
+ * Set the {@literal expression} that determines whether to include the element in the resulting array.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ Filter by(String expression);
+
+ /**
+ * Set the {@literal expression} that determines whether to include the element in the resulting array.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ Filter by(DBObject expression);
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ static final class FilterExpressionBuilder implements InputBuilder, AsBuilder, ConditionBuilder {
+
+ private final Filter filter;
+
+ FilterExpressionBuilder() {
+ this.filter = new Filter();
+ }
+
+ public static InputBuilder newBuilder() {
+ return new FilterExpressionBuilder();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(java.util.List)
+ */
+ @Override
+ public AsBuilder filter(List> array) {
+
+ Assert.notNull(array, "Array must not be null!");
+ filter.input = new ArrayList(array);
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(org.springframework.data.mongodb.core.aggregation.Field)
+ */
+ @Override
+ public AsBuilder filter(Field field) {
+
+ Assert.notNull(field, "Field must not be null!");
+ filter.input = field;
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder#as(java.lang.String)
+ */
+ @Override
+ public ConditionBuilder as(String variableName) {
+
+ Assert.notNull(variableName, "Variable name must not be null!");
+ filter.as = new ExposedField(variableName, true);
+ return this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
+ */
+ @Override
+ public Filter by(AggregationExpression condition) {
+
+ Assert.notNull(condition, "Condition must not be null!");
+ filter.condition = condition;
+ return filter;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(java.lang.String)
+ */
+ @Override
+ public Filter by(String expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ filter.condition = expression;
+ return filter;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(com.mongodb.DBObject)
+ */
+ @Override
+ public Filter by(DBObject expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ filter.condition = expression;
+ return filter;
+ }
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $isArray}.
+ *
+ * @author Christoph Strobl
+ */
+ class IsArray extends AbstractAggregationExpression {
+
+ private IsArray(Object value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$isArray";
+ }
+
+ /**
+ * Creates new {@link IsArray}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static IsArray isArray(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new IsArray(Fields.field(fieldReference));
+ }
+
+ /**
+ * Creates new {@link IsArray}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static IsArray isArray(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new IsArray(expression);
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $size}.
+ *
+ * @author Christoph Strobl
+ */
+ class Size extends AbstractAggregationExpression {
+
+ private Size(Object value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$size";
+ }
+
+ /**
+ * Creates new {@link Size}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static Size lengthOfArray(String fieldReference) {
- Assert.notNull(arrayFieldReference, "ArrayFieldReference must not be null!");
- return new ConcatArrays(append(Fields.field(arrayFieldReference)));
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new Size(Fields.field(fieldReference));
}
- public ConcatArrays concat(AggregationExpression expression) {
+ /**
+ * Creates new {@link Size}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static Size lengthOfArray(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
- return new ConcatArrays(append(expression));
+ return new Size(expression);
}
}
/**
- * {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the
- * specified condition.
+ * {@link AggregationExpression} for {@code $slice}.
*
* @author Christoph Strobl
- * @since 1.10
*/
- class Filter implements AggregationExpression {
+ class Slice extends AbstractAggregationExpression {
- private Object input;
- private ExposedField as;
- private Object condition;
+ private Slice(List> value) {
+ super(value);
+ }
- private Filter() {
- // used by builder
+ @Override
+ protected String getMongoMethod() {
+ return "$slice";
}
/**
- * Set the {@literal field} to apply the {@code $filter} to.
+ * Creates new {@link Slice}.
*
- * @param field must not be {@literal null}.
- * @return never {@literal null}.
+ * @param fieldReference must not be {@literal null}.
+ * @return
*/
- public static AsBuilder filter(String field) {
+ public static Slice sliceArrayOf(String fieldReference) {
- Assert.notNull(field, "Field must not be null!");
- return filter(Fields.field(field));
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new Slice(asFields(fieldReference));
}
/**
- * Set the {@literal field} to apply the {@code $filter} to.
+ * Creates new {@link Slice}.
*
- * @param field must not be {@literal null}.
- * @return never {@literal null}.
+ * @param expression must not be {@literal null}.
+ * @return
*/
- public static AsBuilder filter(Field field) {
+ public static Slice sliceArrayOf(AggregationExpression expression) {
- Assert.notNull(field, "Field must not be null!");
- return new FilterExpressionBuilder().filter(field);
+ Assert.notNull(expression, "Expression must not be null!");
+ return new Slice(Collections.singletonList(expression));
+ }
+
+ public Slice itemCount(int nrElements) {
+ return new Slice(append(nrElements));
+ }
+
+ public SliceElementsBuilder offset(final int position) {
+
+ return new SliceElementsBuilder() {
+
+ @Override
+ public Slice itemCount(int nrElements) {
+ return new Slice(append(position)).itemCount(nrElements);
+ }
+ };
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public interface SliceElementsBuilder {
+
+ /**
+ * Set the number of elements given {@literal nrElements}.
+ *
+ * @param nrElements
+ * @return
+ */
+ Slice itemCount(int nrElements);
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $indexOfArray}.
+ *
+ * @author Christoph Strobl
+ */
+ class IndexOfArray extends AbstractAggregationExpression {
+
+ private IndexOfArray(List value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$indexOfArray";
+ }
+
+ /**
+ * Start creating new {@link IndexOfArray}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static IndexOfArrayBuilder arrayOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new IndexOfArrayBuilder(Fields.field(fieldReference));
+ }
+
+ /**
+ * Start creating new {@link IndexOfArray}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static IndexOfArrayBuilder arrayOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new IndexOfArrayBuilder(expression);
+ }
+
+ public IndexOfArray within(Range range) {
+
+ Assert.notNull(range, "Range must not be null!");
+
+ List rangeValues = new ArrayList(2);
+ rangeValues.add(range.getLowerBound());
+ if (range.getUpperBound() != null) {
+ rangeValues.add(range.getUpperBound());
+ }
+
+ return new IndexOfArray(append(rangeValues));
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public static class IndexOfArrayBuilder {
+
+ private final Object targetArray;
+
+ private IndexOfArrayBuilder(Object targetArray) {
+ this.targetArray = targetArray;
+ }
+
+ /**
+ * Set the {@literal value} to check for its index in the array.
+ *
+ * @param value must not be {@literal null}.
+ * @return
+ */
+ public IndexOfArray indexOf(Object value) {
+
+ Assert.notNull(value, "Value must not be null!");
+ return new IndexOfArray(Arrays.asList(targetArray, value));
+ }
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $range}.
+ *
+ * @author Christoph Strobl
+ */
+ class RangeOperator extends AbstractAggregationExpression {
+
+ private RangeOperator(List values) {
+ super(values);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$range";
+ }
+
+ /**
+ * Start creating new {@link RangeOperator}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static RangeOperatorBuilder rangeStartingAt(String fieldReference) {
+ return new RangeOperatorBuilder(Fields.field(fieldReference));
+ }
+
+ /**
+ * Start creating new {@link RangeOperator}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static RangeOperatorBuilder rangeStartingAt(AggregationExpression expression) {
+ return new RangeOperatorBuilder(expression);
+ }
+
+ /**
+ * Start creating new {@link RangeOperator}.
+ *
+ * @param value
+ * @return
+ */
+ public static RangeOperatorBuilder rangeStartingAt(long value) {
+ return new RangeOperatorBuilder(value);
+ }
+
+ public RangeOperator withStepSize(long stepSize) {
+ return new RangeOperator(append(stepSize));
+ }
+
+ public static class RangeOperatorBuilder {
+
+ private final Object startPoint;
+
+ private RangeOperatorBuilder(Object startPoint) {
+ this.startPoint = startPoint;
+ }
+
+ /**
+ * Creates new {@link RangeOperator}.
+ *
+ * @param index
+ * @return
+ */
+ public RangeOperator to(long index) {
+ return new RangeOperator(Arrays.asList(startPoint, index));
+ }
+
+ /**
+ * Creates new {@link RangeOperator}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public RangeOperator to(AggregationExpression expression) {
+ return new RangeOperator(Arrays.asList(startPoint, expression));
+ }
+
+ /**
+ * Creates new {@link RangeOperator}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public RangeOperator to(String fieldReference) {
+ return new RangeOperator(Arrays.asList(startPoint, Fields.field(fieldReference)));
+ }
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $reverseArray}.
+ *
+ * @author Christoph Strobl
+ */
+ class ReverseArray extends AbstractAggregationExpression {
+
+ private ReverseArray(Object value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$reverseArray";
+ }
+
+ /**
+ * Creates new {@link ReverseArray} given {@literal fieldReference}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static ReverseArray reverseArrayOf(String fieldReference) {
+ return new ReverseArray(Fields.field(fieldReference));
+ }
+
+ /**
+ * Creates new {@link ReverseArray} given {@link AggregationExpression}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static ReverseArray reverseArrayOf(AggregationExpression expression) {
+ return new ReverseArray(expression);
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $reduce}.
+ *
+ * @author Christoph Strobl
+ */
+ class Reduce implements AggregationExpression {
+
+ private final Object input;
+ private final Object initialValue;
+ private final List reduceExpressions;
+
+ private Reduce(Object input, Object initialValue, List reduceExpressions) {
+
+ this.input = input;
+ this.initialValue = initialValue;
+ this.reduceExpressions = reduceExpressions;
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
+ @Override
+ public DBObject toDbObject(AggregationOperationContext context) {
+
+ DBObject dbo = new BasicDBObject();
+
+ dbo.put("input", getMappedValue(input, context));
+ dbo.put("initialValue", getMappedValue(initialValue, context));
+
+ if (reduceExpressions.iterator().next() instanceof PropertyExpression) {
+
+ DBObject properties = new BasicDBObject();
+ for (AggregationExpression e : reduceExpressions) {
+ properties.putAll(e.toDbObject(context));
+ }
+ dbo.put("in", properties);
+ } else {
+ dbo.put("in", (reduceExpressions.iterator().next()).toDbObject(context));
+ }
+
+ return new BasicDBObject("$reduce", dbo);
+ }
+
+ private Object getMappedValue(Object value, AggregationOperationContext context) {
+
+ if (value instanceof DBObject) {
+ return value;
+ }
+ if (value instanceof AggregationExpression) {
+ return ((AggregationExpression) value).toDbObject(context);
+ } else if (value instanceof Field) {
+ return context.getReference(((Field) value)).toString();
+ } else {
+ return context.getMappedObject(new BasicDBObject("###val###", value)).get("###val###");
+ }
}
/**
- * Set the {@literal values} to apply the {@code $filter} to.
+ * Start creating new {@link Reduce}.
*
- * @param values must not be {@literal null}.
+ * @param fieldReference must not be {@literal null}.
* @return
*/
- public static AsBuilder filter(List> values) {
-
- Assert.notNull(values, "Values must not be null!");
- return new FilterExpressionBuilder().filter(values);
- }
+ public static InitialValueBuilder arrayOf(final String fieldReference) {
- /*
- * (non-Javadoc)
- * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
- */
- @Override
- public DBObject toDbObject(final AggregationOperationContext context) {
- return toFilter(ExposedFields.from(as), context);
- }
+ Assert.notNull(fieldReference, "FieldReference must not be null");
- private DBObject toFilter(ExposedFields exposedFields, AggregationOperationContext context) {
+ return new InitialValueBuilder() {
- DBObject filterExpression = new BasicDBObject();
- InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext(
- exposedFields, context);
+ @Override
+ public ReduceBuilder withInitialValue(final Object initialValue) {
- filterExpression.putAll(context.getMappedObject(new BasicDBObject("input", getMappedInput(context))));
- filterExpression.put("as", as.getTarget());
+ Assert.notNull(initialValue, "Initial value must not be null");
- filterExpression.putAll(context.getMappedObject(new BasicDBObject("cond", getMappedCondition(operationContext))));
+ return new ReduceBuilder() {
- return new BasicDBObject("$filter", filterExpression);
- }
+ @Override
+ public Reduce reduce(AggregationExpression expression) {
- private Object getMappedInput(AggregationOperationContext context) {
- return input instanceof Field ? context.getReference((Field) input).toString() : input;
- }
+ Assert.notNull(expression, "AggregationExpression must not be null");
+ return new Reduce(Fields.field(fieldReference), initialValue, Collections.singletonList(expression));
+ }
- private Object getMappedCondition(AggregationOperationContext context) {
+ @Override
+ public Reduce reduce(PropertyExpression... expressions) {
- if (!(condition instanceof AggregationExpression)) {
- return condition;
- }
+ Assert.notNull(expressions, "PropertyExpressions must not be null");
- NestedDelegatingExpressionAggregationOperationContext nea = new NestedDelegatingExpressionAggregationOperationContext(
- context);
- return ((AggregationExpression) condition).toDbObject(nea);
+ return new Reduce(Fields.field(fieldReference), initialValue,
+ Arrays. asList(expressions));
+ }
+ };
+ }
+ };
}
/**
- * @author Christoph Strobl
+ * Start creating new {@link Reduce}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
*/
- public interface InputBuilder {
+ public static InitialValueBuilder arrayOf(final AggregationExpression expression) {
- /**
- * Set the {@literal values} to apply the {@code $filter} to.
- *
- * @param array must not be {@literal null}.
- * @return
- */
- AsBuilder filter(List> array);
+ Assert.notNull(expression, "AggregationExpression must not be null");
- /**
- * Set the {@literal field} holding an array to apply the {@code $filter} to.
- *
- * @param field must not be {@literal null}.
- * @return
- */
- AsBuilder filter(Field field);
+ return new InitialValueBuilder() {
+
+ @Override
+ public ReduceBuilder withInitialValue(final Object initialValue) {
+
+ Assert.notNull(initialValue, "Initial value must not be null");
+
+ return new ReduceBuilder() {
+
+ @Override
+ public Reduce reduce(AggregationExpression expression) {
+
+ Assert.notNull(expression, "AggregationExpression must not be null");
+ return new Reduce(expression, initialValue, Collections.singletonList(expression));
+ }
+
+ @Override
+ public Reduce reduce(PropertyExpression... expressions) {
+
+ Assert.notNull(expressions, "PropertyExpressions must not be null");
+ return new Reduce(expression, initialValue, Arrays. asList(expressions));
+ }
+ };
+ }
+ };
}
/**
* @author Christoph Strobl
*/
- public interface AsBuilder {
+ public interface InitialValueBuilder {
/**
- * Set the {@literal variableName} for the elements in the input array.
+ * Define the initial cumulative value set before in is applied to the first element of the input array.
*
- * @param variableName must not be {@literal null}.
+ * @param initialValue must not be {@literal null}.
* @return
*/
- ConditionBuilder as(String variableName);
+ ReduceBuilder withInitialValue(Object initialValue);
}
/**
* @author Christoph Strobl
*/
- public interface ConditionBuilder {
-
- /**
- * Set the {@link AggregationExpression} that determines whether to include the element in the resulting array.
- *
- * @param expression must not be {@literal null}.
- * @return
- */
- Filter by(AggregationExpression expression);
+ public interface ReduceBuilder {
/**
- * Set the {@literal expression} that determines whether to include the element in the resulting array.
+ * Define the {@link AggregationExpression} to apply to each element in the input array in left-to-right order.
+ *
+ * NOTE: During evaluation of the in expression the variable references {@link Variable#THIS} and
+ * {@link Variable#VALUE} are available.
*
* @param expression must not be {@literal null}.
* @return
*/
- Filter by(String expression);
+ Reduce reduce(AggregationExpression expression);
/**
- * Set the {@literal expression} that determines whether to include the element in the resulting array.
+ * Define the {@link PropertyExpression}s to apply to each element in the input array in left-to-right order.
+ *
+ * NOTE: During evaluation of the in expression the variable references {@link Variable#THIS} and
+ * {@link Variable#VALUE} are available.
*
* @param expression must not be {@literal null}.
* @return
*/
- Filter by(DBObject expression);
+ Reduce reduce(PropertyExpression... expressions);
}
/**
* @author Christoph Strobl
*/
- static final class FilterExpressionBuilder implements InputBuilder, AsBuilder, ConditionBuilder {
+ public static class PropertyExpression implements AggregationExpression {
- private final Filter filter;
+ private final String propertyName;
+ private final AggregationExpression aggregationExpression;
- FilterExpressionBuilder() {
- this.filter = new Filter();
- }
+ protected PropertyExpression(String propertyName, AggregationExpression aggregationExpression) {
- public static InputBuilder newBuilder() {
- return new FilterExpressionBuilder();
+ Assert.notNull(propertyName, "Property name must not be null!");
+ Assert.notNull(aggregationExpression, "AggregationExpression must not be null!");
+
+ this.propertyName = propertyName;
+ this.aggregationExpression = aggregationExpression;
}
- /*
- * (non-Javadoc)
- * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(java.util.List)
+ /**
+ * Define a result property for an {@link AggregationExpression} used in {@link Reduce}.
+ *
+ * @param name must not be {@literal null}.
+ * @return
*/
- @Override
- public AsBuilder filter(List> array) {
+ public static AsBuilder property(final String name) {
- Assert.notNull(array, "Array must not be null!");
- filter.input = new ArrayList(array);
- return this;
+ return new AsBuilder() {
+
+ @Override
+ public PropertyExpression definedAs(AggregationExpression expression) {
+ return new PropertyExpression(name, expression);
+ }
+ };
}
- /*
- * (non-Javadoc)
- * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(org.springframework.data.mongodb.core.aggregation.Field)
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
*/
@Override
- public AsBuilder filter(Field field) {
-
- Assert.notNull(field, "Field must not be null!");
- filter.input = field;
- return this;
+ public DBObject toDbObject(AggregationOperationContext context) {
+ return new BasicDBObject(propertyName, aggregationExpression.toDbObject(context));
}
- /*
- * (non-Javadoc)
- * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder#as(java.lang.String)
+ /**
+ * @author Christoph Strobl
*/
- @Override
- public ConditionBuilder as(String variableName) {
+ interface AsBuilder {
- Assert.notNull(variableName, "Variable name must not be null!");
- filter.as = new ExposedField(variableName, true);
- return this;
+ /**
+ * Set the {@link AggregationExpression} resulting in the properties value.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ PropertyExpression definedAs(AggregationExpression expression);
}
+ }
- /*
- * (non-Javadoc)
- * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
- */
- @Override
- public Filter by(AggregationExpression condition) {
+ public enum Variable implements Field {
- Assert.notNull(condition, "Condition must not be null!");
- filter.condition = condition;
- return filter;
- }
+ THIS {
+ @Override
+ public String getName() {
+ return "$$this";
+ }
- /*
- * (non-Javadoc)
- * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(java.lang.String)
- */
- @Override
- public Filter by(String expression) {
+ @Override
+ public String getTarget() {
+ return "$$this";
+ }
- Assert.notNull(expression, "Expression must not be null!");
- filter.condition = expression;
- return filter;
- }
+ @Override
+ public boolean isAliased() {
+ return false;
+ }
- /*
- * (non-Javadoc)
- * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(com.mongodb.DBObject)
+ @Override
+ public String toString() {
+ return getName();
+ }
+ },
+
+ VALUE {
+ @Override
+ public String getName() {
+ return "$$value";
+ }
+
+ @Override
+ public String getTarget() {
+ return "$$value";
+ }
+
+ @Override
+ public boolean isAliased() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ };
+
+ /**
+ * Create a {@link Field} reference to a given {@literal property} prefixed with the {@link Variable} identifier.
+ * eg. {@code $$value.product}
+ *
+ * @param property must not be {@literal null}.
+ * @return
*/
- @Override
- public Filter by(DBObject expression) {
+ public Field referringTo(final String property) {
- Assert.notNull(expression, "Expression must not be null!");
- filter.condition = expression;
- return filter;
+ return new Field() {
+ @Override
+ public String getName() {
+ return Variable.this.getName() + "." + property;
+ }
+
+ @Override
+ public String getTarget() {
+ return Variable.this.getTarget() + "." + property;
+ }
+
+ @Override
+ public boolean isAliased() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ };
}
}
}
/**
- * {@link AggregationExpression} for {@code $isArray}.
+ * {@link AggregationExpression} for {@code $zip}.
*
* @author Christoph Strobl
*/
- class IsArray extends AbstractAggregationExpression {
+ class Zip extends AbstractAggregationExpression {
- private IsArray(Object value) {
+ protected Zip(java.util.Map value) {
super(value);
}
@Override
protected String getMongoMethod() {
- return "$isArray";
+ return "$zip";
}
/**
- * Creates new {@link IsArray}.
+ * Start creating new {@link Zip}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
- public static IsArray isArray(String fieldReference) {
+ public static ZipBuilder arrayOf(String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
- return new IsArray(Fields.field(fieldReference));
+ return new ZipBuilder(Fields.field(fieldReference));
}
/**
- * Creates new {@link IsArray}.
+ * Start creating new {@link Zip}.
*
* @param expression must not be {@literal null}.
* @return
*/
- public static IsArray isArray(AggregationExpression expression) {
+ public static ZipBuilder arrayOf(AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
- return new IsArray(expression);
+ return new ZipBuilder(expression);
}
- }
- /**
- * {@link AggregationExpression} for {@code $size}.
- *
- * @author Christoph Strobl
- */
- class Size extends AbstractAggregationExpression {
+ /**
+ * Create new {@link Zip} and set the {@code useLongestLength} property to {@literal true}.
+ *
+ * @return
+ */
+ public Zip useLongestLength() {
+ return new Zip(append("useLongestLength", true));
+ }
- private Size(Object value) {
- super(value);
+ /**
+ * Optionally provide a default value.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public Zip defaultTo(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new Zip(append("defaults", Fields.field(fieldReference)));
}
- @Override
- protected String getMongoMethod() {
- return "$size";
+ /**
+ * Optionally provide a default value.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public Zip defaultTo(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new Zip(append("defaults", expression));
}
/**
- * Creates new {@link Size}.
+ * Optionally provide a default value.
*
- * @param fieldReference must not be {@literal null}.
+ * @param array must not be {@literal null}.
* @return
*/
- public static Size lengthOfArray(String fieldReference) {
+ public Zip defaultTo(Object[] array) {
+
+ Assert.notNull(array, "Array must not be null!");
+ return new Zip(append("defaults", array));
+ }
+
+ public static class ZipBuilder {
+
+ private final List sourceArrays;
+
+ private ZipBuilder(Object sourceArray) {
+
+ this.sourceArrays = new ArrayList();
+ this.sourceArrays.add(sourceArray);
+ }
+
+ /**
+ * Creates new {@link Zip} that transposes an array of input arrays so that the first element of the output array
+ * would be an array containing, the first element of the first input array, the first element of the second input
+ * array, etc.
+ *
+ * @param arrays arrays to zip the referenced one with. must not be {@literal null}.
+ * @return
+ */
+ public Zip zip(Object... arrays) {
- Assert.notNull(fieldReference, "FieldReference must not be null!");
- return new Size(Fields.field(fieldReference));
- }
+ Assert.notNull(arrays, "Arrays must not be null!");
+ for (Object value : arrays) {
- /**
- * Creates new {@link Size}.
- *
- * @param expression must not be {@literal null}.
- * @return
- */
- public static Size lengthOfArray(AggregationExpression expression) {
+ if (value instanceof String) {
+ sourceArrays.add(Fields.field((String) value));
+ } else {
+ sourceArrays.add(value);
+ }
+ }
- Assert.notNull(expression, "Expression must not be null!");
- return new Size(expression);
+ return new Zip(Collections. singletonMap("inputs", sourceArrays));
+ }
}
}
/**
- * {@link AggregationExpression} for {@code $slice}.
+ * {@link AggregationExpression} for {@code $in}.
*
* @author Christoph Strobl
*/
- class Slice extends AbstractAggregationExpression {
+ class In extends AbstractAggregationExpression {
- private Slice(List> value) {
- super(value);
+ private In(List values) {
+ super(values);
}
@Override
protected String getMongoMethod() {
- return "$slice";
+ return "$in";
}
/**
- * Creates new {@link Slice}.
+ * Start creating {@link In}.
*
* @param fieldReference must not be {@literal null}.
* @return
*/
- public static Slice sliceArrayOf(String fieldReference) {
+ public static InBuilder arrayOf(final String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
- return new Slice(asFields(fieldReference));
+
+ return new InBuilder() {
+
+ @Override
+ public In containsValue(Object value) {
+
+ Assert.notNull(value, "Value must not be null!");
+ return new In(Arrays.asList(value, Fields.field(fieldReference)));
+ }
+ };
}
/**
- * Creates new {@link Slice}.
+ * Start creating {@link In}.
*
* @param expression must not be {@literal null}.
* @return
*/
- public static Slice sliceArrayOf(AggregationExpression expression) {
+ public static InBuilder arrayOf(final AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
- return new Slice(Collections.singletonList(expression));
- }
-
- public Slice itemCount(int nrElements) {
- return new Slice(append(nrElements));
- }
- public SliceElementsBuilder offset(final int position) {
-
- return new SliceElementsBuilder() {
+ return new InBuilder() {
@Override
- public Slice itemCount(int nrElements) {
- return new Slice(append(position)).itemCount(nrElements);
+ public In containsValue(Object value) {
+
+ Assert.notNull(value, "Value must not be null!");
+ return new In(Arrays.asList(value, expression));
}
};
}
- public interface SliceElementsBuilder {
- Slice itemCount(int nrElements);
+ /**
+ * @author Christoph Strobl
+ */
+ public interface InBuilder {
+
+ /**
+ * Set the {@literal value} to check for existence in the array.
+ *
+ * @param value must not be {@literal value}.
+ * @return
+ */
+ In containsValue(Object value);
}
}
+ // ############
+ // LITERAL OPERATORS
+ // ############
+
/**
* {@link AggregationExpression} for {@code $literal}.
*
@@ -4697,7 +6187,9 @@ protected String getMongoMethod() {
public static FormatBuilder dateOf(final String fieldReference) {
Assert.notNull(fieldReference, "FieldReference must not be null!");
+
return new FormatBuilder() {
+
@Override
public DateToString toString(String format) {
@@ -4716,12 +6208,13 @@ public DateToString toString(String format) {
public static FormatBuilder dateOf(final AggregationExpression expression) {
Assert.notNull(expression, "Expression must not be null!");
+
return new FormatBuilder() {
+
@Override
public DateToString toString(String format) {
Assert.notNull(format, "Format must not be null!");
-
return new DateToString(argumentMap(expression, format));
}
};
@@ -4747,6 +6240,129 @@ public interface FormatBuilder {
}
}
+ /**
+ * {@link AggregationExpression} for {@code $isoDayOfWeek}.
+ *
+ * @author Christoph Strobl
+ */
+ class IsoDayOfWeek extends AbstractAggregationExpression {
+
+ private IsoDayOfWeek(Object value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$isoDayOfWeek";
+ }
+
+ /**
+ * Creates new {@link IsoDayOfWeek}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static IsoDayOfWeek isoDayOfWeek(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new IsoDayOfWeek(Fields.field(fieldReference));
+ }
+
+ /**
+ * Creates new {@link IsoDayOfWeek}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static IsoDayOfWeek isoDayOfWeek(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new IsoDayOfWeek(expression);
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $isoWeek}.
+ *
+ * @author Christoph Strobl
+ */
+ class IsoWeek extends AbstractAggregationExpression {
+
+ private IsoWeek(Object value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$isoWeek";
+ }
+
+ /**
+ * Creates new {@link IsoWeek}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static IsoWeek isoWeekOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new IsoWeek(Fields.field(fieldReference));
+ }
+
+ /**
+ * Creates new {@link IsoWeek}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static IsoWeek isoWeekOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new IsoWeek(expression);
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $isoWeekYear}.
+ *
+ * @author Christoph Strobl
+ */
+ class IsoWeekYear extends AbstractAggregationExpression {
+
+ private IsoWeekYear(Object value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$isoWeekYear";
+ }
+
+ /**
+ * Creates new {@link IsoWeekYear}.
+ *
+ * @param fieldReference must not be {@literal null}.
+ * @return
+ */
+ public static IsoWeekYear isoWeekYearOf(String fieldReference) {
+
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+ return new IsoWeekYear(Fields.field(fieldReference));
+ }
+
+ /**
+ * Creates new {@link Millisecond}.
+ *
+ * @param expression must not be {@literal null}.
+ * @return
+ */
+ public static IsoWeekYear isoWeekYearOf(AggregationExpression expression) {
+
+ Assert.notNull(expression, "Expression must not be null!");
+ return new IsoWeekYear(expression);
+ }
+ }
+
/**
* {@link AggregationExpression} for {@code $sum}.
*
@@ -4813,6 +6429,9 @@ public Sum and(AggregationExpression expression) {
return new Sum(append(expression));
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
@Override
public DBObject toDbObject(Object value, AggregationOperationContext context) {
@@ -4892,6 +6511,9 @@ public Avg and(AggregationExpression expression) {
return new Avg(append(expression));
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
@Override
public DBObject toDbObject(Object value, AggregationOperationContext context) {
@@ -4971,6 +6593,9 @@ public Max and(AggregationExpression expression) {
return new Max(append(expression));
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
@Override
public DBObject toDbObject(Object value, AggregationOperationContext context) {
@@ -5050,6 +6675,9 @@ public Min and(AggregationExpression expression) {
return new Min(append(expression));
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
@Override
public DBObject toDbObject(Object value, AggregationOperationContext context) {
@@ -5129,6 +6757,9 @@ public StdDevPop and(AggregationExpression expression) {
return new StdDevPop(append(expression));
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
@Override
public DBObject toDbObject(Object value, AggregationOperationContext context) {
@@ -5208,6 +6839,9 @@ public StdDevSamp and(AggregationExpression expression) {
return new StdDevSamp(append(expression));
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.AbstractAggregationExpression#toDbObject(java.lang.Object, org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
@Override
public DBObject toDbObject(Object value, AggregationOperationContext context) {
@@ -5962,15 +7596,21 @@ private Map(Object sourceArray, String itemVariableName, AggregationExpression f
*/
static AsBuilder itemsOf(final String fieldReference) {
+ Assert.notNull(fieldReference, "FieldReference must not be null!");
+
return new AsBuilder() {
@Override
public FunctionBuilder as(final String variableName) {
+ Assert.notNull(variableName, "VariableName must not be null!");
+
return new FunctionBuilder() {
@Override
public Map andApply(final AggregationExpression expression) {
+
+ Assert.notNull(expression, "AggregationExpression must not be null!");
return new Map(Fields.field(fieldReference), variableName, expression);
}
};
@@ -5988,15 +7628,21 @@ public Map andApply(final AggregationExpression expression) {
*/
public static AsBuilder itemsOf(final AggregationExpression source) {
+ Assert.notNull(source, "AggregationExpression must not be null!");
+
return new AsBuilder() {
@Override
public FunctionBuilder as(final String variableName) {
+ Assert.notNull(variableName, "VariableName must not be null!");
+
return new FunctionBuilder() {
@Override
public Map andApply(final AggregationExpression expression) {
+
+ Assert.notNull(expression, "AggregationExpression must not be null!");
return new Map(source, variableName, expression);
}
};
@@ -6004,6 +7650,9 @@ public Map andApply(final AggregationExpression expression) {
};
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
@Override
public DBObject toDbObject(final AggregationOperationContext context) {
return toMap(ExposedFields.synthetic(Fields.fields(itemVariableName)), context);
@@ -6076,8 +7725,8 @@ private IfNull(Object condition, Object value) {
/**
* Creates new {@link IfNull}.
*
- * @param fieldReference the field to check for a {@literal null} value, field reference must not be
- * {@literal null}.
+ * @param fieldReference the field to check for a {@literal null} value, field reference must not be {@literal null}
+ * .
* @return
*/
public static ThenBuilder ifNull(String fieldReference) {
@@ -6725,6 +8374,7 @@ public static LetBuilder define(final Collection variables)
Assert.notNull(variables, "Variables must not be null!");
return new LetBuilder() {
+
@Override
public Let andApply(final AggregationExpression expression) {
@@ -6745,6 +8395,7 @@ public static LetBuilder define(final ExpressionVariable... variables) {
Assert.notNull(variables, "Variables must not be null!");
return new LetBuilder() {
+
@Override
public Let andApply(final AggregationExpression expression) {
@@ -6765,6 +8416,9 @@ public interface LetBuilder {
Let andApply(AggregationExpression expression);
}
+ /* (non-Javadoc)
+ * @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDbObject(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
+ */
@Override
public DBObject toDbObject(final AggregationOperationContext context) {
return toLet(ExposedFields.synthetic(Fields.fields(getVariableNames())), context);
@@ -6864,4 +8518,139 @@ public ExpressionVariable forExpression(DBObject expressionObject) {
}
}
}
+
+ /**
+ * {@link AggregationExpression} for {@code $switch}.
+ *
+ * @author Christoph Strobl
+ */
+ class Switch extends AbstractAggregationExpression {
+
+ private Switch(java.util.Map values) {
+ super(values);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$switch";
+ }
+
+ /**
+ * Creates new {@link Switch}.
+ *
+ * @param conditions must not be {@literal null}.
+ */
+ public static Switch switchCases(CaseOperator... conditions) {
+
+ Assert.notNull(conditions, "Conditions must not be null!");
+ return switchCases(Arrays.asList(conditions));
+ }
+
+ /**
+ * Creates new {@link Switch}.
+ *
+ * @param conditions must not be {@literal null}.
+ */
+ public static Switch switchCases(List conditions) {
+
+ Assert.notNull(conditions, "Conditions must not be null!");
+ return new Switch(Collections. singletonMap("branches", new ArrayList(conditions)));
+ }
+
+ public Switch defaultTo(Object value) {
+ return new Switch(append("default", value));
+ }
+
+ /**
+ * Encapsulates the aggregation framework case document inside a {@code $switch}-operation.
+ */
+ public static class CaseOperator implements AggregationExpression {
+
+ private final AggregationExpression when;
+ private final Object then;
+
+ private CaseOperator(AggregationExpression when, Object then) {
+
+ this.when = when;
+ this.then = then;
+ }
+
+ public static ThenBuilder when(final AggregationExpression condition) {
+
+ Assert.notNull(condition, "Condition must not be null!");
+
+ return new ThenBuilder() {
+
+ @Override
+ public CaseOperator then(Object value) {
+
+ Assert.notNull(value, "Value must not be null!");
+ return new CaseOperator(condition, 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) {
+
+ DBObject dbo = new BasicDBObject("case", when.toDbObject(context));
+
+ if (then instanceof AggregationExpression) {
+ dbo.put("then", ((AggregationExpression) then).toDbObject(context));
+ } else if (then instanceof Field) {
+ dbo.put("then", context.getReference((Field) then).toString());
+ } else {
+ dbo.put("then", then);
+ }
+
+ return dbo;
+ }
+
+ /**
+ * @author Christoph Strobl
+ */
+ public interface ThenBuilder {
+
+ /**
+ * Set the then {@literal value}.
+ *
+ * @param value must not be {@literal null}.
+ * @return
+ */
+ CaseOperator then(Object value);
+ }
+ }
+ }
+
+ /**
+ * {@link AggregationExpression} for {@code $type}.
+ *
+ * @author Christoph Strobl
+ */
+ class Type extends AbstractAggregationExpression {
+
+ private Type(Object value) {
+ super(value);
+ }
+
+ @Override
+ protected String getMongoMethod() {
+ return "$type";
+ }
+
+ /**
+ * Creates new {@link Type}.
+ *
+ * @param field must not be {@literal null}.
+ * @return
+ */
+ public static Type typeOf(String field) {
+
+ Assert.notNull(field, "Field must not be null!");
+ return new Type(Fields.field(field));
+ }
+ }
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java
index f9033743db..ce51f40624 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ExposedFields.java
@@ -22,6 +22,7 @@
import java.util.List;
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
+import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
import org.springframework.util.Assert;
import org.springframework.util.CompositeIterator;
import org.springframework.util.ObjectUtils;
@@ -406,6 +407,11 @@ public Object getReferenceValue() {
*/
@Override
public String toString() {
+
+ if(getRaw().startsWith("$")) {
+ return getRaw();
+ }
+
return String.format("$%s", getRaw());
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java
index 183d526520..2ba33412a4 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Fields.java
@@ -187,13 +187,13 @@ public Iterator iterator() {
}
/**
- *
* @return
* @since 1.10
*/
public List asList() {
return Collections.unmodifiableList(fields);
}
+
/**
* Value object to encapsulate a field in an aggregation operation.
*
@@ -201,6 +201,7 @@ public List asList() {
*/
static class AggregationField implements Field {
+ private final String raw;
private final String name;
private final String target;
@@ -225,6 +226,7 @@ public AggregationField(String name) {
*/
public AggregationField(String name, String target) {
+ raw = name;
String nameToSet = cleanUp(name);
String targetToSet = cleanUp(target);
@@ -266,6 +268,11 @@ public String getName() {
* @see org.springframework.data.mongodb.core.aggregation.Field#getAlias()
*/
public String getTarget() {
+
+ if (isLocalVar()) {
+ return this.getRaw();
+ }
+
return StringUtils.hasText(this.target) ? this.target : this.name;
}
@@ -278,6 +285,22 @@ public boolean isAliased() {
return !getName().equals(getTarget());
}
+ /**
+ * @return {@literal true} in case the field name starts with {@code $$}.
+ * @since 1.10
+ */
+ public boolean isLocalVar() {
+ return raw.startsWith("$$") && !raw.startsWith("$$$");
+ }
+
+ /**
+ * @return
+ * @since 1.10
+ */
+ public String getRaw() {
+ return raw;
+ }
+
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java
index d381020b5c..587f0b327e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java
@@ -491,8 +491,10 @@ protected Object convert(AggregationExpressionTransformationContext(5L, 9L)))
+ .as("byteLocation").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, isBsonObject().containing("$project.byteLocation.$indexOfBytes.[2]", 5L)
+ .containing("$project.byteLocation.$indexOfBytes.[3]", 9L));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderIndexOfCPCorrectly() {
+
+ DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo")).as("cpLocation")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project: { cpLocation: { $indexOfCP: [ \"$item\", \"foo\" ] } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderIndexOfCPWithRangeCorrectly() {
+
+ DBObject agg = project().and(StringOperators.valueOf("item").indexOfCP("foo").within(new Range(5L, 9L)))
+ .as("cpLocation").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, isBsonObject().containing("$project.cpLocation.$indexOfCP.[2]", 5L)
+ .containing("$project.cpLocation.$indexOfCP.[3]", 9L));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderSplitCorrectly() {
+
+ DBObject agg = project().and(StringOperators.valueOf("city").split(", ")).as("city_state")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { city_state : { $split: [\"$city\", \", \"] }} }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderStrLenBytesCorrectly() {
+
+ DBObject agg = project().and(StringOperators.valueOf("name").length()).as("length")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenBytes: \"$name\" } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderStrLenCPCorrectly() {
+
+ DBObject agg = project().and(StringOperators.valueOf("name").lengthCP()).as("length")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { \"length\": { $strLenCP: \"$name\" } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderSubstrCPCorrectly() {
+
+ DBObject agg = project().and(StringOperators.valueOf("quarter").substringCP(0, 2)).as("yearSubstring")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearSubstring: { $substrCP: [ \"$quarter\", 0, 2 ] } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderIndexOfArrayCorrectly() {
+
+ DBObject agg = project().and(ArrayOperators.arrayOf("items").indexOf(2)).as("index")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { index: { $indexOfArray: [ \"$items\", 2 ] } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderRangeCorrectly() {
+
+ DBObject agg = project().and(RangeOperator.rangeStartingAt(0L).to("distance").withStepSize(25L)).as("rest_stops")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, isBsonObject().containing("$project.rest_stops.$range.[0]", 0L)
+ .containing("$project.rest_stops.$range.[1]", "$distance").containing("$project.rest_stops.$range.[2]", 25L));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderReverseArrayCorrectly() {
+
+ DBObject agg = project().and(ArrayOperators.arrayOf("favorites").reverse()).as("reverseFavorites")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { reverseFavorites: { $reverseArray: \"$favorites\" } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderReduceWithSimpleObjectCorrectly() {
+
+ DBObject agg = project()
+ .and(ArrayOperators.arrayOf("probabilityArr")
+ .reduce(ArithmeticOperators.valueOf("$$value").multiplyBy("$$this")).startingWith(1))
+ .as("results").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse(
+ "{ $project : { \"results\": { $reduce: { input: \"$probabilityArr\", initialValue: 1, in: { $multiply: [ \"$$value\", \"$$this\" ] } } } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderReduceWithComplexObjectCorrectly() {
+
+ PropertyExpression sum = PropertyExpression.property("sum").definedAs(
+ ArithmeticOperators.valueOf(Variable.VALUE.referringTo("sum").getName()).add(Variable.THIS.getName()));
+ PropertyExpression product = PropertyExpression.property("product").definedAs(ArithmeticOperators
+ .valueOf(Variable.VALUE.referringTo("product").getName()).multiplyBy(Variable.THIS.getName()));
+
+ DBObject agg = project()
+ .and(ArrayOperators.arrayOf("probabilityArr").reduce(sum, product)
+ .startingWith(new BasicDBObjectBuilder().add("sum", 5).add("product", 2).get()))
+ .as("results").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse(
+ "{ $project : { \"results\": { $reduce: { input: \"$probabilityArr\", initialValue: { \"sum\" : 5 , \"product\" : 2} , in: { \"sum\": { $add : [\"$$value.sum\", \"$$this\"] }, \"product\": { $multiply: [ \"$$value.product\", \"$$this\" ] } } } } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderZipCorrectly() {
+
+ AggregationExpression elemAt0 = ArrayOperators.arrayOf("matrix").elementAt(0);
+ AggregationExpression elemAt1 = ArrayOperators.arrayOf("matrix").elementAt(1);
+ AggregationExpression elemAt2 = ArrayOperators.arrayOf("matrix").elementAt(2);
+
+ DBObject agg = project().and(
+ ArrayOperators.arrayOf(elemAt0).zipWith(elemAt1, elemAt2).useLongestLength().defaultTo(new Object[] { 1, 2 }))
+ .as("transposed").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse(
+ "{ $project : { transposed: { $zip: { inputs: [ { $arrayElemAt: [ \"$matrix\", 0 ] }, { $arrayElemAt: [ \"$matrix\", 1 ] }, { $arrayElemAt: [ \"$matrix\", 2 ] } ], useLongestLength : true, defaults: [1,2] } } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderInCorrectly() {
+
+ DBObject agg = project().and(ArrayOperators.arrayOf("in_stock").containsValue("bananas")).as("has_bananas")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { has_bananas : { $in : [\"bananas\", \"$in_stock\" ] } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderIsoDayOfWeekCorrectly() {
+
+ DBObject agg = project().and(DateOperators.dateOf("birthday").isoDayOfWeek()).as("dayOfWeek")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { dayOfWeek: { $isoDayOfWeek: \"$birthday\" } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderIsoWeekCorrectly() {
+
+ DBObject agg = project().and(DateOperators.dateOf("date").isoWeek()).as("weekNumber")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { weekNumber: { $isoWeek: \"$date\" } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderIsoWeekYearCorrectly() {
+
+ DBObject agg = project().and(DateOperators.dateOf("date").isoWeekYear()).as("yearNumber")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { yearNumber: { $isoWeekYear: \"$date\" } } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderSwitchCorrectly() {
+
+ String expected = "$switch:\n" + //
+ "{\n" + //
+ " branches: [\n" + //
+ " {\n" + //
+ " case: { $gte : [ { $avg : \"$scores\" }, 90 ] },\n" + //
+ " then: \"Doing great!\"\n" + //
+ " },\n" + //
+ " {\n" + //
+ " case: { $and : [ { $gte : [ { $avg : \"$scores\" }, 80 ] },\n" + //
+ " { $lt : [ { $avg : \"$scores\" }, 90 ] } ] },\n" + //
+ " then: \"Doing pretty well.\"\n" + //
+ " },\n" + //
+ " {\n" + //
+ " case: { $lt : [ { $avg : \"$scores\" }, 80 ] },\n" + //
+ " then: \"Needs improvement.\"\n" + //
+ " }\n" + //
+ " ],\n" + //
+ " default: \"No scores found.\"\n" + //
+ " }\n" + //
+ "}";
+
+ CaseOperator cond1 = CaseOperator.when(Gte.valueOf(Avg.avgOf("scores")).greaterThanEqualToValue(90))
+ .then("Doing great!");
+ CaseOperator cond2 = CaseOperator.when(And.and(Gte.valueOf(Avg.avgOf("scores")).greaterThanEqualToValue(80),
+ Lt.valueOf(Avg.avgOf("scores")).lessThanValue(90))).then("Doing pretty well.");
+ CaseOperator cond3 = CaseOperator.when(Lt.valueOf(Avg.avgOf("scores")).lessThanValue(80))
+ .then("Needs improvement.");
+
+ DBObject agg = project().and(ConditionalOperators.switchCases(cond1, cond2, cond3).defaultTo("No scores found."))
+ .as("summary").toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { summary: {" + expected + "} } }")));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldTypeCorrectly() {
+
+ DBObject agg = project().and(Type.typeOf("a")).as("a")
+ .toDBObject(Aggregation.DEFAULT_CONTEXT);
+
+ assertThat(agg, Matchers.is(JSON.parse("{ $project : { a: { $type: \"$a\" } } }")));
+ }
+
private static DBObject exctractOperation(String field, DBObject fromProjectClause) {
return (DBObject) fromProjectClause.get(field);
}
+
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
index 770145a80a..013d6189e7 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
@@ -745,13 +745,13 @@ public void shouldRenderMethodReferenceNodeMin() {
assertThat(transform("min(a, b)"), is("{ \"$min\" : [ \"$a\" , \"$b\"]}"));
}
-
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodePush() {
- assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"), is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}"));
+ assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"),
+ is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}"));
}
/**
@@ -884,6 +884,129 @@ public void shouldRenderComplexNotCorrectly() {
assertThat(transform("!(foo > 10)"), is("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}"));
}
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodReferenceIndexOfBytes() {
+ assertThat(transform("indexOfBytes(item, 'foo')"), is("{ \"$indexOfBytes\" : [ \"$item\" , \"foo\"]}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodReferenceIndexOfCP() {
+ assertThat(transform("indexOfCP(item, 'foo')"), is("{ \"$indexOfCP\" : [ \"$item\" , \"foo\"]}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodReferenceSplit() {
+ assertThat(transform("split(item, ',')"), is("{ \"$split\" : [ \"$item\" , \",\"]}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodReferenceStrLenBytes() {
+ assertThat(transform("strLenBytes(item)"), is("{ \"$strLenBytes\" : \"$item\"}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodReferenceStrLenCP() {
+ assertThat(transform("strLenCP(item)"), is("{ \"$strLenCP\" : \"$item\"}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodSubstrCP() {
+ assertThat(transform("substrCP(item, 0, 5)"), is("{ \"$substrCP\" : [ \"$item\" , 0 , 5]}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodReferenceReverseArray() {
+ assertThat(transform("reverseArray(array)"), is("{ \"$reverseArray\" : \"$array\"}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodReferenceReduce() {
+ assertThat(transform("reduce(field, '', {'$concat':new String[]{'$$value','$$this'}})"), is(
+ "{ \"$reduce\" : { \"input\" : \"$field\" , \"initialValue\" : \"\" , \"in\" : { \"$concat\" : [ \"$$value\" , \"$$this\"]}}}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodReferenceZip() {
+ assertThat(transform("zip(new String[]{'$array1', '$array2'})"),
+ is("{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"]}}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodReferenceZipWithOptionalArgs() {
+ assertThat(transform("zip(new String[]{'$array1', '$array2'}, true, new int[]{1,2})"), is(
+ "{ \"$zip\" : { \"inputs\" : [ \"$array1\" , \"$array2\"] , \"useLongestLength\" : true , \"defaults\" : [ 1 , 2]}}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodIn() {
+ assertThat(transform("in('item', array)"), is("{ \"$in\" : [ \"item\" , \"$array\"]}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodRefereneIsoDayOfWeek() {
+ assertThat(transform("isoDayOfWeek(date)"), is("{ \"$isoDayOfWeek\" : \"$date\"}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodRefereneIsoWeek() {
+ assertThat(transform("isoWeek(date)"), is("{ \"$isoWeek\" : \"$date\"}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodRefereneIsoWeekYear() {
+ assertThat(transform("isoWeekYear(date)"), is("{ \"$isoWeekYear\" : \"$date\"}"));
+ }
+
+ /**
+ * @see DATAMONGO-1548
+ */
+ @Test
+ public void shouldRenderMethodRefereneType() {
+ assertThat(transform("type(a)"), is("{ \"$type\" : \"$a\"}"));
+ }
+
private String transform(String expression, Object... params) {
Object result = transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
return result == null ? null : result.toString();
diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc
index 0b9b03fc0c..d6ff11f630 100644
--- a/src/main/asciidoc/reference/mongodb.adoc
+++ b/src/main/asciidoc/reference/mongodb.adoc
@@ -1686,26 +1686,28 @@ At the time of this writing we provide support for the following Aggregation Ope
| abs, add (*via plus), ceil, divide, exp, floor, ln, log, log10, mod, multiply, pow, sqrt, subtract (*via minus), trunc
| String Aggregation Operators
-| concat, substr, toLower, toUpper, stcasecmp
+| concat, substr, toLower, toUpper, stcasecmp, indexOfBytes, indexOfCP, split, strLenBytes, strLenCP, substrCP,
| Comparison Aggregation Operators
| eq (*via: is), gt, gte, lt, lte, ne
| Array Aggregation Operators
-| arrayElementAt, concatArrays, filter, isArray, size, slice
+| arrayElementAt, concatArrays, filter, in, indexOfArray, isArray, range, reverseArray, reduce, size, slice, zip
| Literal Operators
| literal
| Date Aggregation Operators
-| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString
+| dayOfYear, dayOfMonth, dayOfWeek, year, month, week, hour, minute, second, millisecond, dateToString, isoDayOfWeek, isoWeek, isoWeekYear
| Variable Operators
| map
-
| Conditional Aggregation Operators
-| cond, ifNull
+| cond, ifNull, switch
+
+| Type Aggregation Operators
+| type
|===