Skip to content

Add support for $rank, $denseRank, $documentNumber & $shift aggregation operators. #3741

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3715-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3715-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3715-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>3.3.0-SNAPSHOT</version>
<version>3.3.0-GH-3715-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;

import java.util.Collections;

import org.bson.Document;

/**
* Gateway to {@literal document expressions} such as {@literal $rank, $documentNumber, etc.}
*
* @author Christoph Strobl
* @since 3.3
*/
public class DocumentOperators {

/**
* Obtain the document position (including gaps) relative to others (rank).
*
* @return new instance of {@link Rank}.
* @since 3.3
*/
public static Rank rank() {
return new Rank();
}

/**
* Obtain the document position (without gaps) relative to others (rank).
*
* @return new instance of {@link DenseRank}.
* @since 3.3
*/
public static DenseRank denseRank() {
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.
*
* @return new instance of {@link DocumentNumber}.
* @since 3.3
*/
public static DocumentNumber documentNumber() {
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.
*
* @author Christoph Strobl
* @since 3.3
*/
public static class Rank implements AggregationExpression {

@Override
public Document toDocument(AggregationOperationContext context) {
return new Document("$rank", new Document());
}
}

/**
* {@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.
*
* @author Christoph Strobl
* @since 3.3
*/
public static class DenseRank implements AggregationExpression {

@Override
public Document toDocument(AggregationOperationContext context) {
return new Document("$denseRank", new Document());
}
}

/**
* {@link DocumentNumber} resolves the current document position.
*
* @author Christoph Strobl
* @since 3.3
*/
public static class DocumentNumber implements AggregationExpression {

@Override
public Document toDocument(AggregationOperationContext context) {
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";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,10 @@ protected Object convert(AggregationExpressionTransformationContext<MethodRefere
dbo.put(methodReference.getArgumentMap()[i++], transform(child, context));
}
args = dbo;
} else {
} else if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.EMPTY_DOCUMENT)) {
args = new Document();
}
else {

List<Object> argList = new ArrayList<Object>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ public class MethodReferenceNode extends ExpressionNode {
map.put("lte", arrayArgRef().forOperator("$lte"));
map.put("ne", arrayArgRef().forOperator("$ne"));

// DOCUMENT OPERATORS
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"));
map.put("add", arrayArgRef().forOperator("$add"));
Expand Down Expand Up @@ -305,6 +311,16 @@ static AggregationMethodReference mapArgRef() {
return new AggregationMethodReference(null, ArgumentType.MAP, null);
}

/**
* Create a new {@link AggregationMethodReference} for a {@link ArgumentType#EMPTY_DOCUMENT} argument.
*
* @return never {@literal null}.
* @since 3.3
*/
static AggregationMethodReference emptyRef() {
return new AggregationMethodReference(null, ArgumentType.EMPTY_DOCUMENT, null);
}

/**
* Create a new {@link AggregationMethodReference} for a given {@literal aggregationExpressionOperator} reusing
* previously set arguments.
Expand Down Expand Up @@ -340,7 +356,7 @@ AggregationMethodReference mappingParametersTo(String... aggregationExpressionPr
* @since 1.10
*/
public enum ArgumentType {
SINGLE, ARRAY, MAP
SINGLE, ARRAY, MAP, EMPTY_DOCUMENT
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.aggregation;

import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.aggregation.DocumentOperators.*;

import org.bson.Document;
import org.junit.jupiter.api.Test;

/**
* @author Christoph Strobl
*/
class DocumentOperatorsUnitTests {

@Test // GH-3715
void rendersRank() {
assertThat(rank().toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(new Document("$rank", new Document()));
}

@Test // GH-3715
void rendersDenseRank() {
assertThat(denseRank().toDocument(Aggregation.DEFAULT_CONTEXT))
.isEqualTo(new Document("$denseRank", new Document()));
}

@Test // GH-3717
void rendersDocumentNumber() {
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\" } }"));
}
}
Loading