Add support for $shift aggregation Operator.

Closes: #3727
Original pull request: #3741.
This commit is contained in:
Christoph Strobl
2021-07-23 11:33:07 +02:00
committed by Mark Paluch
parent 1a86761e2e
commit 510028a834
4 changed files with 153 additions and 3 deletions

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.mongodb.core.aggregation;
import java.util.Collections;
import org.bson.Document;
/**
@@ -45,6 +47,26 @@ public class DocumentOperators {
return new DenseRank();
}
/**
* Take the field referenced by given {@literal fieldReference}.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link DocumentOperatorsFactory}.
*/
public static DocumentOperatorsFactory valueOf(String fieldReference) {
return new DocumentOperatorsFactory(fieldReference);
}
/**
* Take the value resulting from the given {@link AggregationExpression}.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link DocumentOperatorsFactory}.
*/
public static DocumentOperatorsFactory valueOf(AggregationExpression expression) {
return new DocumentOperatorsFactory(expression);
}
/**
* Obtain the current document position.
*
@@ -55,6 +77,35 @@ public class DocumentOperators {
return new DocumentNumber();
}
/**
* @author Christoph Strobl
*/
public static class DocumentOperatorsFactory {
private Object target;
public DocumentOperatorsFactory(Object target) {
this.target = target;
}
/**
* Creates new {@link AggregationExpression} that applies the expression to a document at specified position
* relative to the current document.
*
* @param by the value to add to the current position.
* @return new instance of {@link Shift}.
*/
public Shift shift(int by) {
Shift shift = usesExpression() ? Shift.shift((AggregationExpression) target) : Shift.shift(target.toString());
return shift.by(by);
}
private boolean usesExpression() {
return target instanceof AggregationExpression;
}
}
/**
* {@link Rank} resolves the current document position (the rank) relative to other documents. If multiple documents
* occupy the same rank, {@literal $rank} places the document with the subsequent value at a rank with a gap.
@@ -72,8 +123,8 @@ public class DocumentOperators {
/**
* {@link DenseRank} resolves the current document position (the rank) relative to other documents. If multiple
* documents occupy the same rank, {@literal $denseRank} places the document with the subsequent value at the next rank without
* any gaps.
* documents occupy the same rank, {@literal $denseRank} places the document with the subsequent value at the next
* rank without any gaps.
*
* @author Christoph Strobl
* @since 3.3
@@ -99,4 +150,73 @@ public class DocumentOperators {
return new Document("$documentNumber", new Document());
}
}
/**
* Shift applies an expression to a document in a specified position relative to the current document.
*
* @author Christoph Strobl
* @since 3.3
*/
public static class Shift extends AbstractAggregationExpression {
private Shift(Object value) {
super(value);
}
/**
* Specifies the field to evaluate and return.
*
* @param fieldReference must not be {@literal null}.
* @return new instance of {@link Shift}.
*/
public static Shift shift(String fieldReference) {
return new Shift(Collections.singletonMap("output", Fields.field(fieldReference)));
}
/**
* Specifies the {@link AggregationExpression expression} to evaluate and return.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Shift}.
*/
public static Shift shift(AggregationExpression expression) {
return new Shift(Collections.singletonMap("output", expression));
}
/**
* Shift the document position relative to the current. Use a positive value for follow up documents (eg. 1 for the
* next) or a negative value for the predecessor documents (eg. -1 for the previous).
*
* @param shiftBy value to add to the current position.
* @return new instance of {@link Shift}.
*/
public Shift by(int shiftBy) {
return new Shift(append("by", shiftBy));
}
/**
* Define the default value if the target document is out of range.
*
* @param value must not be {@literal null}.
* @return new instance of {@link Shift}.
*/
public Shift defaultTo(Object value) {
return new Shift(append("default", value));
}
/**
* Define the {@link AggregationExpression expression} to evaluate if the target document is out of range.
*
* @param expression must not be {@literal null}.
* @return new instance of {@link Shift}.
*/
public Shift defaultToValueOf(AggregationExpression expression) {
return defaultTo(expression);
}
@Override
protected String getMongoMethod() {
return "$shift";
}
}
}

View File

@@ -72,6 +72,7 @@ public class MethodReferenceNode extends ExpressionNode {
map.put("rank", emptyRef().forOperator("$rank"));
map.put("denseRank", emptyRef().forOperator("$denseRank"));
map.put("documentNumber", emptyRef().forOperator("$documentNumber"));
map.put("shift", mapArgRef().forOperator("$shift").mappingParametersTo("output", "by", "default"));
// ARITHMETIC OPERATORS
map.put("abs", singleArgRef().forOperator("$abs"));

View File

@@ -39,6 +39,21 @@ class DocumentOperatorsUnitTests {
@Test // GH-3717
void rendersDocumentNumber() {
assertThat(documentNumber().toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(new Document("$documentNumber", new Document()));
assertThat(documentNumber().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(new Document("$documentNumber", new Document()));
}
@Test // GH-3727
void rendersShift() {
assertThat(valueOf("quantity").shift(1).toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $shift: { output: \"$quantity\", by: 1 } }"));
}
@Test // GH-3727
void rendersShiftWithDefault() {
assertThat(valueOf("quantity").shift(1).defaultTo("Not available").toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(Document.parse("{ $shift: { output: \"$quantity\", by: 1, default: \"Not available\" } }"));
}
}

View File

@@ -971,6 +971,20 @@ public class SpelExpressionTransformerUnitTests {
assertThat(transform("documentNumber()")).isEqualTo(Document.parse("{ $documentNumber : {} }"));
}
@Test // GH-3727
void rendersShift() {
assertThat(transform("shift(quantity, 1)"))
.isEqualTo(Document.parse("{ $shift: { output: \"$quantity\", by: 1 } }"));
}
@Test // GH-3727
void rendersShiftWithDefault() {
assertThat(transform("shift(quantity, 1, 'Not available')"))
.isEqualTo(Document.parse("{ $shift: { output: \"$quantity\", by: 1, default: \"Not available\" } }"));
}
private Object transform(String expression, Object... params) {
Object result = transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
return result == null ? null : (!(result instanceof org.bson.Document) ? result.toString() : result);