diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/EvaluationOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/EvaluationOperators.java index 2fa1cf545..e2eb49c82 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/EvaluationOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/EvaluationOperators.java @@ -16,7 +16,6 @@ package org.springframework.data.mongodb.core.aggregation; import org.bson.Document; - import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.util.Assert; @@ -24,6 +23,7 @@ import org.springframework.util.Assert; * Gateway to {@literal evaluation operators} such as {@literal $expr}. * * @author Divya Srivastava + * @author Christoph Strobl * @since 3.3 */ public class EvaluationOperators { @@ -86,6 +86,15 @@ public class EvaluationOperators { return usesFieldRef() ? Expr.valueOf(fieldReference) : Expr.valueOf(expression); } + /** + * Creates new {@link AggregationExpression} that is a valid aggregation expression. + * + * @return new instance of {@link Expr}. + */ + public LastObservationCarriedForward locf() { + return usesFieldRef() ? LastObservationCarriedForward.locfValueOf(fieldReference) : LastObservationCarriedForward.locfValueOf(expression); + } + /** * Allows the use of aggregation expressions within the query language. */ @@ -152,4 +161,45 @@ public class EvaluationOperators { } } + /** + * Sets {@literal null} and missing values to the last non-null value. + * + * @since 4.0 + */ + public static class LastObservationCarriedForward extends AbstractAggregationExpression { + + private LastObservationCarriedForward(Object value) { + super(value); + } + + @Override + protected String getMongoMethod() { + return "$locf"; + } + + /** + * Creates new {@link EvaluationOperatorFactory.Expr}. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link EvaluationOperatorFactory.Expr}. + */ + public static LastObservationCarriedForward locfValueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null"); + return new LastObservationCarriedForward(Fields.field(fieldReference)); + } + + /** + * Creates new {@link LastObservationCarriedForward}. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link LastObservationCarriedForward}. + */ + public static LastObservationCarriedForward locfValueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null"); + return new LastObservationCarriedForward(expression); + } + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java index cc572ab5d..ce6d6b88e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java @@ -251,6 +251,9 @@ public class MethodReferenceNode extends ExpressionNode { map.put("toString", singleArgRef().forOperator("$toString")); map.put("degreesToRadians", singleArgRef().forOperator("$degreesToRadians")); + // expression operators + map.put("locf", singleArgRef().forOperator("$locf")); + FUNCTIONS = Collections.unmodifiableMap(map); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/EvaluationOperatorsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/EvaluationOperatorsUnitTests.java index f547ccaf5..352b45ab2 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/EvaluationOperatorsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/EvaluationOperatorsUnitTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; * Unit tests for {@link EvaluationOperators}. * * @author Mark Paluch + * @author Christoph Strobl */ class EvaluationOperatorsUnitTests { @@ -32,4 +33,11 @@ class EvaluationOperatorsUnitTests { assertThat(EvaluationOperators.valueOf("foo").expr().toDocument(Aggregation.DEFAULT_CONTEXT)) .isEqualTo("{ $expr: \"$foo\" }"); } + + @Test // GH-4139 + void shouldRenderLocfCorrectly() { + + assertThat(EvaluationOperators.valueOf("foo").locf().toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo("{ $locf: \"$foo\" }"); + } } 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 94b9cf935..7efa19cf4 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 @@ -1250,6 +1250,11 @@ public class SpelExpressionTransformerUnitTests { void shouldTsSecond() { assertThat(transform("tsSecond(saleTimestamp)")).isEqualTo("{ $tsSecond: \"$saleTimestamp\" }"); } + + @Test // GH-4139 + void shouldRenderLocf() { + assertThat(transform("locf(price)")).isEqualTo("{ $locf: \"$price\" }"); + } private Document transform(String expression, Object... params) { return (Document) transformer.transform(expression, Aggregation.DEFAULT_CONTEXT, params);