diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java index 55964bab9..614489692 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java @@ -498,17 +498,18 @@ public class Aggregation { public static MatchOperation match(CriteriaDefinition criteria) { return new MatchOperation(criteria); } - + /** - * Creates a new {@link MatchOperation} + * Creates a new {@link MatchOperation} using the given {@link AggregationExpression}. * + * @param expression must not be {@literal null}. * @return new instance of {@link MatchOperation}. - * @since 1.10 + * @since 3.3 */ - public static MatchOperation match() { - return new MatchOperation(); + public static MatchOperation match(AggregationExpression expression) { + return new MatchOperation(expression); } - + /** * Creates a new {@link GeoNearOperation} instance from the given {@link NearQuery} and the {@code distanceField}. The * {@code distanceField} defines output field that contains the calculated distance. 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 0fb8e25fa..181bab5ef 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 @@ -1,9 +1,33 @@ +/* + * 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 org.bson.Document; + +import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.util.Assert; +/** + * Gateway to {@literal evaluation operators} such as {@literal $expr}. + * + * @author Divya Srivastava + * @since 3.3 + */ public class EvaluationOperators { - + /** * Take the value resulting from the given fieldReference. * @@ -13,7 +37,7 @@ public class EvaluationOperators { public static EvaluationOperatorFactory valueOf(String fieldReference) { return new EvaluationOperatorFactory(fieldReference); } - + /** * Take the value resulting from the given {@link AggregationExpression}. * @@ -23,12 +47,12 @@ public class EvaluationOperators { public static EvaluationOperatorFactory valueOf(AggregationExpression expression) { return new EvaluationOperatorFactory(expression); } - + public static class EvaluationOperatorFactory { - + private final String fieldReference; private final AggregationExpression expression; - + /** * Creates new {@link EvaluationOperatorFactory} for given {@literal fieldReference}. * @@ -41,7 +65,6 @@ public class EvaluationOperators { this.expression = null; } - /** * Creates new {@link EvaluationOperatorFactory} for given {@link AggregationExpression}. * @@ -53,7 +76,7 @@ public class EvaluationOperators { this.fieldReference = null; this.expression = expression; } - + /** * Creates new {@link AggregationExpression} that is a valid aggregation expression. * @@ -62,8 +85,10 @@ public class EvaluationOperators { public Expr expr() { return usesFieldRef() ? Expr.valueOf(fieldReference) : Expr.valueOf(expression); } - - + + /** + * Allows the use of aggregation expressions within the query language. + */ public static class Expr extends AbstractAggregationExpression { private Expr(Object value) { @@ -99,8 +124,29 @@ public class EvaluationOperators { return new Expr(expression); } + /** + * Creates {@code $expr} as {@link CriteriaDefinition}. + * + * @return the {@link CriteriaDefinition} from this expression. + */ + public CriteriaDefinition toCriteriaDefinition(AggregationOperationContext context) { + + Document criteriaObject = toDocument(context); + + return new CriteriaDefinition() { + @Override + public Document getCriteriaObject() { + return criteriaObject; + } + + @Override + public String getKey() { + return getMongoMethod(); + } + }; + } } - + private boolean usesFieldRef() { return fieldReference != null; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java index c2796aaa0..c3d1f366e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java @@ -16,7 +16,7 @@ package org.springframework.data.mongodb.core.aggregation; import org.bson.Document; -import org.springframework.data.mongodb.core.aggregation.EvaluationOperators.EvaluationOperatorFactory.Expr; + import org.springframework.data.mongodb.core.query.CriteriaDefinition; import org.springframework.util.Assert; @@ -30,6 +30,7 @@ import org.springframework.util.Assert; * @author Sebastian Herold * @author Thomas Darimont * @author Oliver Gierke + * @author Divya Srivastava * @since 1.3 * @see MongoDB Aggregation Framework: * $match @@ -38,15 +39,7 @@ public class MatchOperation implements AggregationOperation { private final CriteriaDefinition criteriaDefinition; private final AggregationExpression expression; - - /** - * Creates a new {@link MatchOperation} - */ - public MatchOperation() { - this.criteriaDefinition = null; - this.expression = null; - } - + /** * Creates a new {@link MatchOperation} for the given {@link CriteriaDefinition}. * @@ -55,41 +48,34 @@ public class MatchOperation implements AggregationOperation { public MatchOperation(CriteriaDefinition criteriaDefinition) { Assert.notNull(criteriaDefinition, "Criteria must not be null!"); + this.criteriaDefinition = criteriaDefinition; this.expression = null; } - - /** - * Creates a new {@link MatchOperation} for the given {@link Expression}. - * - * @param criteriaDefinition must not be {@literal null}. - */ - private MatchOperation(Expr expression) { - Assert.notNull(expression, "Expression must not be null!"); - this.criteriaDefinition = null; - this.expression = expression; - } - + /** * Creates a new {@link MatchOperation} for the given {@link AggregationExpression}. * * @param expression must not be {@literal null}. + * @since 3.3 */ - public MatchOperation withValueOf(AggregationExpression expression) { + public MatchOperation(AggregationExpression expression) { + Assert.notNull(expression, "Expression must not be null!"); - return new MatchOperation(EvaluationOperators.valueOf(expression).expr()); + + this.criteriaDefinition = null; + this.expression = expression; } - + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext) */ @Override public Document toDocument(AggregationOperationContext context) { - if(expression != null) { - return new Document(getOperator(), expression.toDocument()); - } - return new Document(getOperator(), context.getMappedObject(criteriaDefinition.getCriteriaObject())); + + return new Document(getOperator(), + context.getMappedObject(expression != null ? expression.toDocument() : criteriaDefinition.getCriteriaObject())); } /* 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 new file mode 100644 index 000000000..67f5093b8 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/EvaluationOperatorsUnitTests.java @@ -0,0 +1,35 @@ +/* + * 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.springframework.data.mongodb.test.util.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link EvaluationOperators}. + * + * @author Mark Paluch + */ +class EvaluationOperatorsUnitTests { + + @Test // GH-3790 + void shouldRenderExprCorrectly() { + + assertThat(EvaluationOperators.valueOf("foo").expr().toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo("{ $expr: \"$foo\" }"); + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/MatchOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/MatchOperationUnitTests.java index 04d3824de..ec3decb7a 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/MatchOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/MatchOperationUnitTests.java @@ -1,26 +1,23 @@ package org.springframework.data.mongodb.core.aggregation; -import static org.assertj.core.api.Assertions.*; -import org.bson.Document; +import static org.springframework.data.mongodb.test.util.Assertions.*; + import org.junit.jupiter.api.Test; +/** + * Unit tests for {@link MatchOperation}. + * + * @author Divya Srivastava + */ class MatchOperationUnitTests { - - @Test // DATAMONGO - 3729 - public void shouldRenderStdDevPopCorrectly() { - MatchOperation operation = Aggregation.match().withValueOf(ArithmeticOperators.valueOf("quiz").stdDevPop()); + + @Test // GH-3790 + void matchShouldRenderCorrectly() { + + MatchOperation operation = Aggregation.match(ArithmeticOperators.valueOf("quiz").stdDevPop()); assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT)). - isEqualTo(Document.parse("{ $match: { \"$expr\" : { \"$stdDevPop\" : \"$quiz\" } } } ")); - - } - - @Test // DATAMONGO - 3729 - public void shouldRenderStdDevSampCorrectly() { - MatchOperation operation = Aggregation.match().withValueOf(ArithmeticOperators.valueOf("quiz").stdDevSamp()); - assertThat(operation.toDocument(Aggregation.DEFAULT_CONTEXT)). - isEqualTo(Document.parse("{ $match: { \"$expr\" : { \"$stdDevSamp\" : \"$quiz\" } } } ")); - + isEqualTo("{ $match: { \"$stdDevPop\" : \"$quiz\" } } "); } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java index e97e1ff01..9fbc36586 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceRootOperationUnitTests.java @@ -27,20 +27,20 @@ import org.springframework.data.mongodb.core.aggregation.ReplaceRootOperation.Re * * @author Mark Paluch */ -public class ReplaceRootOperationUnitTests { +class ReplaceRootOperationUnitTests { @Test // DATAMONGO-1550 - public void rejectsNullField() { + void rejectsNullField() { assertThatIllegalArgumentException().isThrownBy(() -> new ReplaceRootOperation((Field) null)); } @Test // DATAMONGO-1550 - public void rejectsNullExpression() { + void rejectsNullExpression() { assertThatIllegalArgumentException().isThrownBy(() -> new ReplaceRootOperation((AggregationExpression) null)); } @Test // DATAMONGO-1550 - public void shouldRenderCorrectly() { + void shouldRenderCorrectly() { ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder() .withDocument(new Document("hello", "world")); @@ -50,7 +50,7 @@ public class ReplaceRootOperationUnitTests { } @Test // DATAMONGO-1550 - public void shouldRenderExpressionCorrectly() { + void shouldRenderExpressionCorrectly() { ReplaceRootOperation operation = new ReplaceRootOperation(VariableOperators // .mapItemsOf("array") // @@ -64,7 +64,7 @@ public class ReplaceRootOperationUnitTests { } @Test // DATAMONGO-1550 - public void shouldComposeDocument() { + void shouldComposeDocument() { ReplaceRootOperation operation = ReplaceRootDocumentOperation.builder().withDocument() // .andValue("value").as("key") // @@ -77,7 +77,7 @@ public class ReplaceRootOperationUnitTests { } @Test // DATAMONGO-1550 - public void shouldComposeSubDocument() { + void shouldComposeSubDocument() { Document partialReplacement = new Document("key", "override").append("key2", "value2"); @@ -92,7 +92,7 @@ public class ReplaceRootOperationUnitTests { } @Test // DATAMONGO-1550 - public void shouldNotExposeFields() { + void shouldNotExposeFields() { ReplaceRootOperation operation = new ReplaceRootOperation(Fields.field("field")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceWithOperationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceWithOperationUnitTests.java index 8f8b5c9dd..d1a21a254 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceWithOperationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ReplaceWithOperationUnitTests.java @@ -25,15 +25,15 @@ import org.junit.jupiter.api.Test; * * @author Christoph Strobl */ -public class ReplaceWithOperationUnitTests { +class ReplaceWithOperationUnitTests { @Test // DATAMONGO-2331 - public void rejectsNullField() { + void rejectsNullField() { assertThatIllegalArgumentException().isThrownBy(() -> new ReplaceWithOperation(null)); } @Test // DATAMONGO-2331 - public void shouldRenderValueCorrectly() { + void shouldRenderValueCorrectly() { ReplaceWithOperation operation = ReplaceWithOperation.replaceWithValue(new Document("hello", "world")); Document dbObject = operation.toDocument(Aggregation.DEFAULT_CONTEXT); @@ -42,7 +42,7 @@ public class ReplaceWithOperationUnitTests { } @Test // DATAMONGO-2331 - public void shouldRenderExpressionCorrectly() { + void shouldRenderExpressionCorrectly() { ReplaceWithOperation operation = ReplaceWithOperation.replaceWithValueOf(VariableOperators // .mapItemsOf("array") // diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java index 808263697..46db6e7d6 100755 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java @@ -43,6 +43,9 @@ import org.springframework.data.domain.Sort.Direction; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.DocumentTestUtils; import org.springframework.data.mongodb.core.Person; +import org.springframework.data.mongodb.core.aggregation.ConditionalOperators; +import org.springframework.data.mongodb.core.aggregation.EvaluationOperators; +import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.geo.GeoJsonPolygon; import org.springframework.data.mongodb.core.mapping.DBRef; @@ -1330,6 +1333,21 @@ public class QueryMapperUnitTests { assertThat(mapper.getMappedSort(query.getQueryObject(), context.getPersistentEntity(Customer.class))).isEqualTo(new org.bson.Document("address.street", "1007 Mountain Drive")); } + @Test // GH-3790 + void shouldAcceptExprAsCriteriaDefinition() { + + EvaluationOperators.EvaluationOperatorFactory.Expr expr = EvaluationOperators + .valueOf(ConditionalOperators.ifNull("customizedField").then(true)).expr(); + + Query query = query( + expr.toCriteriaDefinition(new TypeBasedAggregationOperationContext(EmbeddedClass.class, context, mapper))); + + org.bson.Document mappedQuery = mapper.getMappedObject(query.getQueryObject(), + context.getRequiredPersistentEntity(EmbeddedClass.class)); + + assertThat(mappedQuery).isEqualTo("{ $expr : { $ifNull : [\"$fancy_custom_name\", true] } }"); + } + @Test // GH-3668 void mapStringIdFieldProjection() {