Skip to content

Commit 510028a

Browse files
christophstroblmp911de
authored andcommitted
Add support for $shift aggregation Operator.
Closes: #3727 Original pull request: #3741.
1 parent 1a86761 commit 510028a

File tree

4 files changed

+153
-3
lines changed

4 files changed

+153
-3
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/DocumentOperators.java

+122-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.mongodb.core.aggregation;
1717

18+
import java.util.Collections;
19+
1820
import org.bson.Document;
1921

2022
/**
@@ -45,6 +47,26 @@ public static DenseRank denseRank() {
4547
return new DenseRank();
4648
}
4749

50+
/**
51+
* Take the field referenced by given {@literal fieldReference}.
52+
*
53+
* @param fieldReference must not be {@literal null}.
54+
* @return new instance of {@link DocumentOperatorsFactory}.
55+
*/
56+
public static DocumentOperatorsFactory valueOf(String fieldReference) {
57+
return new DocumentOperatorsFactory(fieldReference);
58+
}
59+
60+
/**
61+
* Take the value resulting from the given {@link AggregationExpression}.
62+
*
63+
* @param expression must not be {@literal null}.
64+
* @return new instance of {@link DocumentOperatorsFactory}.
65+
*/
66+
public static DocumentOperatorsFactory valueOf(AggregationExpression expression) {
67+
return new DocumentOperatorsFactory(expression);
68+
}
69+
4870
/**
4971
* Obtain the current document position.
5072
*
@@ -55,6 +77,35 @@ public static DocumentNumber documentNumber() {
5577
return new DocumentNumber();
5678
}
5779

80+
/**
81+
* @author Christoph Strobl
82+
*/
83+
public static class DocumentOperatorsFactory {
84+
85+
private Object target;
86+
87+
public DocumentOperatorsFactory(Object target) {
88+
this.target = target;
89+
}
90+
91+
/**
92+
* Creates new {@link AggregationExpression} that applies the expression to a document at specified position
93+
* relative to the current document.
94+
*
95+
* @param by the value to add to the current position.
96+
* @return new instance of {@link Shift}.
97+
*/
98+
public Shift shift(int by) {
99+
100+
Shift shift = usesExpression() ? Shift.shift((AggregationExpression) target) : Shift.shift(target.toString());
101+
return shift.by(by);
102+
}
103+
104+
private boolean usesExpression() {
105+
return target instanceof AggregationExpression;
106+
}
107+
}
108+
58109
/**
59110
* {@link Rank} resolves the current document position (the rank) relative to other documents. If multiple documents
60111
* occupy the same rank, {@literal $rank} places the document with the subsequent value at a rank with a gap.
@@ -72,8 +123,8 @@ public Document toDocument(AggregationOperationContext context) {
72123

73124
/**
74125
* {@link DenseRank} resolves the current document position (the rank) relative to other documents. If multiple
75-
* documents occupy the same rank, {@literal $denseRank} places the document with the subsequent value at the next rank without
76-
* any gaps.
126+
* documents occupy the same rank, {@literal $denseRank} places the document with the subsequent value at the next
127+
* rank without any gaps.
77128
*
78129
* @author Christoph Strobl
79130
* @since 3.3
@@ -99,4 +150,73 @@ public Document toDocument(AggregationOperationContext context) {
99150
return new Document("$documentNumber", new Document());
100151
}
101152
}
153+
154+
/**
155+
* Shift applies an expression to a document in a specified position relative to the current document.
156+
*
157+
* @author Christoph Strobl
158+
* @since 3.3
159+
*/
160+
public static class Shift extends AbstractAggregationExpression {
161+
162+
private Shift(Object value) {
163+
super(value);
164+
}
165+
166+
/**
167+
* Specifies the field to evaluate and return.
168+
*
169+
* @param fieldReference must not be {@literal null}.
170+
* @return new instance of {@link Shift}.
171+
*/
172+
public static Shift shift(String fieldReference) {
173+
return new Shift(Collections.singletonMap("output", Fields.field(fieldReference)));
174+
}
175+
176+
/**
177+
* Specifies the {@link AggregationExpression expression} to evaluate and return.
178+
*
179+
* @param expression must not be {@literal null}.
180+
* @return new instance of {@link Shift}.
181+
*/
182+
public static Shift shift(AggregationExpression expression) {
183+
return new Shift(Collections.singletonMap("output", expression));
184+
}
185+
186+
/**
187+
* Shift the document position relative to the current. Use a positive value for follow up documents (eg. 1 for the
188+
* next) or a negative value for the predecessor documents (eg. -1 for the previous).
189+
*
190+
* @param shiftBy value to add to the current position.
191+
* @return new instance of {@link Shift}.
192+
*/
193+
public Shift by(int shiftBy) {
194+
return new Shift(append("by", shiftBy));
195+
}
196+
197+
/**
198+
* Define the default value if the target document is out of range.
199+
*
200+
* @param value must not be {@literal null}.
201+
* @return new instance of {@link Shift}.
202+
*/
203+
public Shift defaultTo(Object value) {
204+
return new Shift(append("default", value));
205+
}
206+
207+
/**
208+
* Define the {@link AggregationExpression expression} to evaluate if the target document is out of range.
209+
*
210+
* @param expression must not be {@literal null}.
211+
* @return new instance of {@link Shift}.
212+
*/
213+
public Shift defaultToValueOf(AggregationExpression expression) {
214+
return defaultTo(expression);
215+
}
216+
217+
@Override
218+
protected String getMongoMethod() {
219+
return "$shift";
220+
}
221+
}
102222
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public class MethodReferenceNode extends ExpressionNode {
7272
map.put("rank", emptyRef().forOperator("$rank"));
7373
map.put("denseRank", emptyRef().forOperator("$denseRank"));
7474
map.put("documentNumber", emptyRef().forOperator("$documentNumber"));
75+
map.put("shift", mapArgRef().forOperator("$shift").mappingParametersTo("output", "by", "default"));
7576

7677
// ARITHMETIC OPERATORS
7778
map.put("abs", singleArgRef().forOperator("$abs"));

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/DocumentOperatorsUnitTests.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,21 @@ void rendersDenseRank() {
3939

4040
@Test // GH-3717
4141
void rendersDocumentNumber() {
42-
assertThat(documentNumber().toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(new Document("$documentNumber", new Document()));
42+
assertThat(documentNumber().toDocument(Aggregation.DEFAULT_CONTEXT))
43+
.isEqualTo(new Document("$documentNumber", new Document()));
44+
}
45+
46+
@Test // GH-3727
47+
void rendersShift() {
48+
49+
assertThat(valueOf("quantity").shift(1).toDocument(Aggregation.DEFAULT_CONTEXT))
50+
.isEqualTo(Document.parse("{ $shift: { output: \"$quantity\", by: 1 } }"));
51+
}
52+
53+
@Test // GH-3727
54+
void rendersShiftWithDefault() {
55+
56+
assertThat(valueOf("quantity").shift(1).defaultTo("Not available").toDocument(Aggregation.DEFAULT_CONTEXT))
57+
.isEqualTo(Document.parse("{ $shift: { output: \"$quantity\", by: 1, default: \"Not available\" } }"));
4358
}
4459
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java

+14
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,20 @@ void shouldRenderDocumentNumber() {
971971
assertThat(transform("documentNumber()")).isEqualTo(Document.parse("{ $documentNumber : {} }"));
972972
}
973973

974+
@Test // GH-3727
975+
void rendersShift() {
976+
977+
assertThat(transform("shift(quantity, 1)"))
978+
.isEqualTo(Document.parse("{ $shift: { output: \"$quantity\", by: 1 } }"));
979+
}
980+
981+
@Test // GH-3727
982+
void rendersShiftWithDefault() {
983+
984+
assertThat(transform("shift(quantity, 1, 'Not available')"))
985+
.isEqualTo(Document.parse("{ $shift: { output: \"$quantity\", by: 1, default: \"Not available\" } }"));
986+
}
987+
974988
private Object transform(String expression, Object... params) {
975989
Object result = transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);
976990
return result == null ? null : (!(result instanceof org.bson.Document) ? result.toString() : result);

0 commit comments

Comments
 (0)