diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java index 1addc323e..95d3dc167 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java @@ -52,6 +52,7 @@ public class Meta { private final Map values = new LinkedHashMap<>(2); private final Set flags = new LinkedHashSet<>(); private Integer cursorBatchSize; + private Boolean allowDiskUse; public Meta() {} @@ -65,6 +66,7 @@ public class Meta { this.values.putAll(source.values); this.flags.addAll(source.flags); this.cursorBatchSize = source.cursorBatchSize; + this.allowDiskUse = source.allowDiskUse; } /** @@ -245,6 +247,27 @@ public class Meta { return ObjectUtils.nullSafeEquals(this.flags, other.flags); } + /** + * When set to true, aggregation stages can write data to disk. + * + * @return {@literal null} if not set. + * @since 3.0 + */ + @Nullable + public Boolean getAllowDiskUse() { + return allowDiskUse; + } + + /** + * Set to true, to allow aggregation stages to write data to disk. + * + * @param allowDiskUse use {@literal null} for server defaults. + * @since 3.0 + */ + public void setAllowDiskUse(@Nullable Boolean allowDiskUse) { + this.allowDiskUse = allowDiskUse; + } + /** * {@link CursorOption} represents {@code OP_QUERY} wire protocol flags to change the behavior of queries. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Meta.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Meta.java index 0d6454b88..64159605e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Meta.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Meta.java @@ -67,4 +67,13 @@ public @interface Meta { */ org.springframework.data.mongodb.core.query.Meta.CursorOption[] flags() default {}; + /** + * When set to true, aggregation stages can write data to disk. + * + * @return {@literal false} by default. + * @since 3.0 + * @see Aggregation + */ + boolean allowDiskUse() default false; + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java index a514d3a2d..d780d43c1 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.repository.query; import lombok.experimental.UtilityClass; +import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -93,6 +94,14 @@ class AggregationUtils { builder.cursorBatchSize(meta.getCursorBatchSize()); } + if(meta.getMaxTimeMsec() != null && meta.getMaxTimeMsec() > 0) { + builder.maxTime(Duration.ofMillis(meta.getMaxTimeMsec())); + } + + if (meta.getAllowDiskUse() != null) { + builder.allowDiskUse(meta.getAllowDiskUse()); + } + return builder; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java index 05d864a17..d97a84632 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java @@ -293,6 +293,10 @@ public class MongoQueryMethod extends QueryMethod { } } + if(meta.allowDiskUse()) { + metaAttributes.setAllowDiskUse(meta.allowDiskUse()); + } + return metaAttributes; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java index e77e7e77b..f1ba24aba 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java @@ -22,6 +22,7 @@ import static org.mockito.Mockito.*; import lombok.Value; import java.lang.reflect.Method; +import java.time.Duration; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -33,7 +34,6 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; - import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.core.MongoOperations; @@ -102,18 +102,19 @@ public class StringBasedAggregationUnitTests { assertThat(pipelineOf(invocation)).containsExactly(GROUP_BY_LASTNAME, SORT); } - @Test // DATAMONGO-2153 + @Test // DATAMONGO-2153, DATAMONGO-2449 public void plainStringAggregationConsidersMeta() { AggregationInvocation invocation = executeAggregation("plainStringAggregation"); - AggregationOptions options = invocation.aggregation.getOptions(); assertThat(options.getComment()).contains("expensive-aggregation"); assertThat(options.getCursorBatchSize()).isEqualTo(42); + assertThat(options.isAllowDiskUse()).isTrue(); + assertThat(options.getMaxTime()).isEqualTo(Duration.ofMillis(100)); } - @Test // DATAMONGO-2153 + @Test // DATAMONGO-2153, DATAMONGO-2449 public void returnSingleObject() { PersonAggregate expected = new PersonAggregate(); @@ -126,6 +127,8 @@ public class StringBasedAggregationUnitTests { assertThat(options.getComment()).isEmpty(); assertThat(options.getCursorBatchSize()).isNull(); + assertThat(options.isAllowDiskUse()).isFalse(); + assertThat(options.getMaxTime()).isEqualTo(Duration.ZERO); } @Test // DATAMONGO-2153 @@ -246,7 +249,7 @@ public class StringBasedAggregationUnitTests { private interface SampleRepository extends Repository { - @Meta(cursorBatchSize = 42, comment = "expensive-aggregation") + @Meta(cursorBatchSize = 42, comment = "expensive-aggregation", allowDiskUse = true, maxExecutionTimeMs = 100) @Aggregation({ RAW_GROUP_BY_LASTNAME_STRING, RAW_SORT_STRING }) PersonAggregate plainStringAggregation(); diff --git a/src/main/asciidoc/reference/mongo-repositories-aggregation.adoc b/src/main/asciidoc/reference/mongo-repositories-aggregation.adoc index feba576ad..2372aaede 100644 --- a/src/main/asciidoc/reference/mongo-repositories-aggregation.adoc +++ b/src/main/asciidoc/reference/mongo-repositories-aggregation.adoc @@ -11,7 +11,6 @@ The definition may contain simple placeholders like `?0` as well as https://docs ---- public interface PersonRepository extends CrudReppsitory { - @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }") List groupByLastnameAndFirstnames(); <1> @@ -74,6 +73,36 @@ To gain more control, you might consider `AggregationResult` as method return ty <8> Like in <6>, a single value can be directly obtained from multiple result ``Document``s. ==== +In some scenarios aggregations might require additional options like a maximum execution time, additional log comments or the permission to temporarily write data to disk. +Use the `@Meta` annotation to set those options via `maxExecutionTimeMs`, `comment` or `allowDiskUse`. + +[source,java] +---- +public interface PersonRepository extends CrudReppsitory { + + @Meta(allowDiskUse = true) + @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }") + List groupByLastnameAndFirstnames(); +} +---- + +Or use `@Meta` to create your own annotation as shown in the sample below. + +[source,java] +---- +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD }) +@Meta(allowDiskUse = true) +@interface AllowDiskUse { } + +public interface PersonRepository extends CrudReppsitory { + + @AllowDiskUse + @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }") + List groupByLastnameAndFirstnames(); +} +---- + TIP: You can use `@Aggregation` also with <>. [NOTE]