Skip to content

Commit caff79f

Browse files
committed
spring-projectsGH-3726: Add support for $sampleRate match expression.
Closes spring-projects#3726
1 parent 45971b2 commit caff79f

File tree

6 files changed

+200
-3
lines changed

6 files changed

+200
-3
lines changed

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/Aggregation.java

+12
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
* @author Nikolay Bogdanov
5151
* @author Gustavo de Geus
5252
* @author Jérôme Guyon
53+
* @author James McNee
5354
* @since 1.3
5455
*/
5556
public class Aggregation {
@@ -499,6 +500,17 @@ public static MatchOperation match(CriteriaDefinition criteria) {
499500
return new MatchOperation(criteria);
500501
}
501502

503+
/**
504+
* Creates a new {@link MatchOperation} using the given {@link MatchExpression}.
505+
*
506+
* @param matchExpression must not be {@literal null}.
507+
* @return new instance of {@link MatchOperation}.
508+
* @since 3.2.4
509+
*/
510+
public static MatchOperation match(MatchExpression matchExpression) {
511+
return new MatchOperation(matchExpression);
512+
}
513+
502514
/**
503515
* Creates a new {@link GeoNearOperation} instance from the given {@link NearQuery} and the {@code distanceField}. The
504516
* {@code distanceField} defines output field that contains the calculated distance.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2015-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.aggregation;
17+
18+
import org.bson.Document;
19+
import org.springframework.data.mongodb.MongoExpression;
20+
21+
/**
22+
* A {@link MatchExpression} can be used within the {@code match} aggregation pipeline stage.
23+
*
24+
* @author James McNee
25+
*/
26+
public interface MatchExpression extends MongoExpression {
27+
28+
/**
29+
* Obtain the as is (unmapped) representation of the {@link MatchExpression}. Use {@link #toDocument(AggregationOperationContext)}
30+
* with a matching {@link AggregationOperationContext context} to engage domain type mapping including field name resolution.
31+
*
32+
* @see org.springframework.data.mongodb.MongoExpression#toDocument()
33+
*/
34+
@Override
35+
default Document toDocument() {
36+
return toDocument(Aggregation.DEFAULT_CONTEXT);
37+
}
38+
39+
/**
40+
* Turns the {@link MatchExpression} into a {@link Document} within the given {@link AggregationOperationContext}.
41+
*
42+
* @param context must not be {@literal null}.
43+
* @return the MongoDB native ({@link Document}) form of the expression.
44+
*/
45+
Document toDocument(AggregationOperationContext context);
46+
}

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/MatchOperation.java

+16-3
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,14 @@
2929
* @author Sebastian Herold
3030
* @author Thomas Darimont
3131
* @author Oliver Gierke
32+
* @author James McNee
3233
* @since 1.3
3334
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/match/">MongoDB Aggregation Framework:
3435
* $match</a>
3536
*/
3637
public class MatchOperation implements AggregationOperation {
3738

38-
private final CriteriaDefinition criteriaDefinition;
39+
private final Document document;
3940

4041
/**
4142
* Creates a new {@link MatchOperation} for the given {@link CriteriaDefinition}.
@@ -45,7 +46,19 @@ public class MatchOperation implements AggregationOperation {
4546
public MatchOperation(CriteriaDefinition criteriaDefinition) {
4647

4748
Assert.notNull(criteriaDefinition, "Criteria must not be null!");
48-
this.criteriaDefinition = criteriaDefinition;
49+
this.document = criteriaDefinition.getCriteriaObject();
50+
}
51+
52+
/**
53+
* Creates a new {@link MatchOperation} for the given {@link MatchExpression}.
54+
*
55+
* @param matchExpression must not be {@literal null}.
56+
*/
57+
public MatchOperation(MatchExpression matchExpression) {
58+
59+
Assert.notNull(matchExpression, "Match expression must not be null!");
60+
this.document = matchExpression.toDocument();
61+
4962
}
5063

5164
/*
@@ -54,7 +67,7 @@ public MatchOperation(CriteriaDefinition criteriaDefinition) {
5467
*/
5568
@Override
5669
public Document toDocument(AggregationOperationContext context) {
57-
return new Document(getOperator(), context.getMappedObject(criteriaDefinition.getCriteriaObject()));
70+
return new Document(getOperator(), context.getMappedObject(document));
5871
}
5972

6073
/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2016-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.aggregation;
17+
18+
import org.bson.Document;
19+
import org.springframework.util.Assert;
20+
21+
/**
22+
* Gateway to {@literal sampling expressions} that match documents using a provided criteria.
23+
*
24+
* @author James McNee
25+
* @since 3.2.4
26+
*/
27+
public class SamplingOperators {
28+
29+
/**
30+
* Encapsulates the aggregation framework {@code $sampleRate} operator.
31+
*
32+
* @author James McNee
33+
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/sampleRate/">
34+
* https://docs.mongodb.com/manual/reference/operator/aggregation/sampleRate/</a>
35+
*/
36+
public static class SampleRate implements MatchExpression {
37+
38+
private final double sampleRate;
39+
40+
private SampleRate(double sampleRate) {
41+
this.sampleRate = sampleRate;
42+
}
43+
44+
/**
45+
* Creates new {@link SamplingOperators.SampleRate}.
46+
*
47+
* @param sampleRate sample rate to determine number of documents to be randomly selected from the input.
48+
* @return new instance of {@link SamplingOperators.SampleRate}.
49+
*/
50+
public static SampleRate sampleRate(double sampleRate) {
51+
Assert.isTrue(sampleRate >= 0, "Sample rate must be greater than zero!");
52+
Assert.isTrue(sampleRate <= 1, "Sample rate must not be greater than one!");
53+
54+
return new SampleRate(sampleRate);
55+
}
56+
57+
@Override
58+
public Document toDocument(AggregationOperationContext context) {
59+
return new Document("$sampleRate", sampleRate);
60+
}
61+
}
62+
}

Diff for: spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java

+18
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.springframework.data.domain.Sort.Direction.*;
1919
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
2020
import static org.springframework.data.mongodb.core.aggregation.Fields.*;
21+
import static org.springframework.data.mongodb.core.aggregation.SamplingOperators.SampleRate.*;
2122
import static org.springframework.data.mongodb.core.query.Criteria.*;
2223
import static org.springframework.data.mongodb.test.util.Assertions.*;
2324

@@ -89,6 +90,7 @@
8990
* @author Maninder Singh
9091
* @author Sergey Shcherbakov
9192
* @author Minsu Kim
93+
* @author James McNee
9294
*/
9395
@ExtendWith(MongoTemplateExtension.class)
9496
public class AggregationTests {
@@ -1944,6 +1946,22 @@ void mapsEnumsInMatchClauseUsingInCriteriaCorrectly() {
19441946
assertThat(results.getMappedResults()).hasSize(1);
19451947
}
19461948

1949+
@Test // GH-3726
1950+
void shouldApplySampleRateCorrectly() {
1951+
1952+
createUserWithLikesDocuments();
1953+
1954+
TypedAggregation<UserWithLikes> agg = newAggregation(UserWithLikes.class,
1955+
unwind("likes"),
1956+
match(sampleRate(0.66))
1957+
);
1958+
1959+
assertThat(agg.toString()).isNotNull();
1960+
1961+
AggregationResults<LikeStats> result = mongoTemplate.aggregate(agg, LikeStats.class);
1962+
assertThat(result.getMappedResults().size()).isGreaterThan(0);
1963+
}
1964+
19471965
private void createUsersWithReferencedPersons() {
19481966

19491967
mongoTemplate.dropCollection(User.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2018-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.aggregation;
17+
18+
import org.bson.Document;
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.data.mongodb.core.aggregation.SamplingOperators.SampleRate;
21+
22+
import static org.assertj.core.api.Assertions.*;
23+
24+
/**
25+
* Unit tests for {@link SamplingOperators}.
26+
*
27+
* @author James McNee
28+
*/
29+
public class SamplingOperatorsUnitTest {
30+
31+
@Test // GH-3726
32+
public void shouldRejectNegativeSampleRate() {
33+
assertThatIllegalArgumentException().isThrownBy(() -> SampleRate.sampleRate(-1.0));
34+
}
35+
36+
@Test // GH-3726
37+
public void shouldRejectSampleRateGreaterThanOne() {
38+
assertThatIllegalArgumentException().isThrownBy(() -> SampleRate.sampleRate(1.1));
39+
}
40+
41+
@Test // GH-3726
42+
public void shouldCreateSampleRateExpression() {
43+
assertThat(SampleRate.sampleRate(0.34).toDocument()).isEqualTo(Document.parse("{ $sampleRate: 0.34 }"));
44+
}
45+
46+
}

0 commit comments

Comments
 (0)