Polishing.

Tweak wording in the docs. Remove unused code. Fix generics. Rename AggregateContext to AggregationOperation to AggregationDefinition to avoid yet another Context object.

See #3542.
Original pull request: #3545.
This commit is contained in:
Mark Paluch
2021-01-27 09:36:29 +01:00
parent 4700b4dda2
commit 1d60cd7e98
7 changed files with 61 additions and 102 deletions

View File

@@ -67,7 +67,9 @@ class AggregationUtil {
AggregationOperationContext createAggregationContext(Aggregation aggregation, @Nullable Class<?> inputType) {
if (aggregation.getOptions().getDomainTypeMapping() == DomainTypeMapping.NONE) {
DomainTypeMapping domainTypeMapping = aggregation.getOptions().getDomainTypeMapping();
if (domainTypeMapping == DomainTypeMapping.NONE) {
return Aggregation.DEFAULT_CONTEXT;
}
@@ -77,7 +79,7 @@ class AggregationUtil {
return untypedMappingContext.get();
}
if (aggregation.getOptions().getDomainTypeMapping() == DomainTypeMapping.STRICT
if (domainTypeMapping == DomainTypeMapping.STRICT
&& !aggregation.getPipeline().containsUnionWith()) {
return new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
}
@@ -85,8 +87,8 @@ class AggregationUtil {
return new RelaxedTypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
}
inputType = ((TypedAggregation) aggregation).getInputType();
if (aggregation.getOptions().getDomainTypeMapping() == DomainTypeMapping.STRICT
inputType = ((TypedAggregation<?>) aggregation).getInputType();
if (domainTypeMapping == DomainTypeMapping.STRICT
&& !aggregation.getPipeline().containsUnionWith()) {
return new TypeBasedAggregationOperationContext(inputType, mappingContext, queryMapper);
}
@@ -130,53 +132,6 @@ class AggregationUtil {
return command;
}
/**
* Create a {@code $count} aggregation for {@link Query} and optionally a {@link Class entity class}.
*
* @param query must not be {@literal null}.
* @param entityClass can be {@literal null} if the {@link Query} object is empty.
* @return the {@link Aggregation} pipeline definition to run a {@code $count} aggregation.
*/
Aggregation createCountAggregation(Query query, @Nullable Class<?> entityClass) {
List<AggregationOperation> pipeline = computeCountAggregationPipeline(query, entityClass);
Aggregation aggregation = entityClass != null ? Aggregation.newAggregation(entityClass, pipeline)
: Aggregation.newAggregation(pipeline);
aggregation.withOptions(AggregationOptions.builder().collation(query.getCollation().orElse(null)).build());
return aggregation;
}
private List<AggregationOperation> computeCountAggregationPipeline(Query query, @Nullable Class<?> entityType) {
CountOperation count = Aggregation.count().as("totalEntityCount");
if (query.getQueryObject().isEmpty()) {
return Collections.singletonList(count);
}
Assert.notNull(entityType, "Entity type must not be null!");
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(),
mappingContext.getPersistentEntity(entityType));
CriteriaDefinition criteria = new CriteriaDefinition() {
@Override
public Document getCriteriaObject() {
return mappedQuery;
}
@Nullable
@Override
public String getKey() {
return null;
}
};
return Arrays.asList(Aggregation.match(criteria), count);
}
private List<Document> mapAggregationPipeline(List<Document> pipeline) {
return pipeline.stream().map(val -> queryMapper.getMappedObject(val, Optional.empty()))

View File

@@ -55,7 +55,7 @@ import org.springframework.data.mongodb.SessionSynchronization;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
import org.springframework.data.mongodb.core.QueryOperations.AggregateContext;
import org.springframework.data.mongodb.core.QueryOperations.AggregationDefinition;
import org.springframework.data.mongodb.core.QueryOperations.CountContext;
import org.springframework.data.mongodb.core.QueryOperations.DeleteContext;
import org.springframework.data.mongodb.core.QueryOperations.DistinctQueryContext;
@@ -1989,7 +1989,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public <O> AggregationResults<O> aggregate(Aggregation aggregation, Class<?> inputType, Class<O> outputType) {
return aggregate(aggregation, getCollectionName(inputType), outputType,
queryOperations.createAggregationContext(aggregation, inputType).getAggregationOperationContext());
queryOperations.createAggregation(aggregation, inputType).getAggregationOperationContext());
}
/* (non-Javadoc)
@@ -2096,11 +2096,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.notNull(aggregation, "Aggregation pipeline must not be null!");
Assert.notNull(outputType, "Output type must not be null!");
return doAggregate(aggregation, collectionName, outputType, queryOperations.createAggregationContext(aggregation, context));
return doAggregate(aggregation, collectionName, outputType,
queryOperations.createAggregation(aggregation, context));
}
private <O> AggregationResults<O> doAggregate(Aggregation aggregation, String collectionName, Class<O> outputType,
AggregateContext context) {
AggregationDefinition context) {
return doAggregate(aggregation, collectionName, outputType, context.getAggregationOperationContext());
}
@@ -2189,10 +2190,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.notNull(outputType, "Output type must not be null!");
Assert.isTrue(!aggregation.getOptions().isExplain(), "Can't use explain option with streaming!");
AggregateContext aggregateContext = queryOperations.createAggregationContext(aggregation, context);
AggregationDefinition aggregationDefinition = queryOperations.createAggregation(aggregation, context);
AggregationOptions options = aggregation.getOptions();
List<Document> pipeline = aggregateContext.getAggregationPipeline();
List<Document> pipeline = aggregationDefinition.getAggregationPipeline();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(pipeline), collectionName);

View File

@@ -200,33 +200,33 @@ class QueryOperations {
}
/**
* Create a new {@link AggregateContext} for the given {@link Aggregation}.
* Create a new {@link AggregationDefinition} for the given {@link Aggregation}.
*
* @param aggregation must not be {@literal null}.
* @param inputType fallback mapping type in case of untyped aggregation. Can be {@literal null}.
* @return new instance of {@link AggregateContext}.
* @return new instance of {@link AggregationDefinition}.
* @since 3.2
*/
AggregateContext createAggregationContext(Aggregation aggregation, @Nullable Class<?> inputType) {
return new AggregateContext(aggregation, inputType);
AggregationDefinition createAggregation(Aggregation aggregation, @Nullable Class<?> inputType) {
return new AggregationDefinition(aggregation, inputType);
}
/**
* Create a new {@link AggregateContext} for the given {@link Aggregation}.
* Create a new {@link AggregationDefinition} for the given {@link Aggregation}.
*
* @param aggregation must not be {@literal null}.
* @param aggregationOperationContext the {@link AggregationOperationContext} to use. Can be {@literal null}.
* @return new instance of {@link AggregateContext}.
* @return new instance of {@link AggregationDefinition}.
* @since 3.2
*/
AggregateContext createAggregationContext(Aggregation aggregation,
AggregationDefinition createAggregation(Aggregation aggregation,
@Nullable AggregationOperationContext aggregationOperationContext) {
return new AggregateContext(aggregation, aggregationOperationContext);
return new AggregationDefinition(aggregation, aggregationOperationContext);
}
/**
* {@link QueryContext} encapsulates common tasks required to convert a {@link Query} into its MongoDB document
* representation, mapping fieldnames, as well as determinging and applying {@link Collation collations}.
* representation, mapping field names, as well as determining and applying {@link Collation collations}.
*
* @author Christoph Strobl
*/
@@ -235,7 +235,7 @@ class QueryOperations {
private final Query query;
/**
* Create new a {@link QueryContext} instance from the given {@literal query} (can be eihter a {@link Query} or a
* Create new a {@link QueryContext} instance from the given {@literal query} (can be either a {@link Query} or a
* plain {@link Document}.
*
* @param query can be {@literal null}.
@@ -305,7 +305,7 @@ class QueryOperations {
mappingContext.getRequiredPersistentEntity(targetType));
}
if (entity != null && entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) {
if (entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) {
mappedFields.remove(entity.getTextScoreProperty().getFieldName());
}
@@ -793,19 +793,19 @@ class QueryOperations {
}
/**
* A context class that encapsulates common tasks required when running {@literal aggregations}.
* A value object that encapsulates common tasks required when running {@literal aggregations}.
*
* @since 3.2
*/
class AggregateContext {
class AggregationDefinition {
private Aggregation aggregation;
private Lazy<AggregationOperationContext> aggregationOperationContext;
private Lazy<List<Document>> pipeline;
private @Nullable Class<?> inputType;
private final Aggregation aggregation;
private final Lazy<AggregationOperationContext> aggregationOperationContext;
private final Lazy<List<Document>> pipeline;
private final @Nullable Class<?> inputType;
/**
* Creates new instance of {@link AggregateContext} extracting the input type from either the
* Creates new instance of {@link AggregationDefinition} extracting the input type from either the
* {@link org.springframework.data.mongodb.core.aggregation.Aggregation} in case of a {@link TypedAggregation} or
* the given {@literal aggregationOperationContext} if present. <br />
* Creates a new {@link AggregationOperationContext} if none given, based on the {@link Aggregation} input type and
@@ -815,21 +815,25 @@ class QueryOperations {
* @param aggregation the source aggregation.
* @param aggregationOperationContext can be {@literal null}.
*/
AggregateContext(Aggregation aggregation, @Nullable AggregationOperationContext aggregationOperationContext) {
AggregationDefinition(Aggregation aggregation, @Nullable AggregationOperationContext aggregationOperationContext) {
this.aggregation = aggregation;
if (aggregation instanceof TypedAggregation) {
this.inputType = ((TypedAggregation) aggregation).getInputType();
this.inputType = ((TypedAggregation<?>) aggregation).getInputType();
} else if (aggregationOperationContext instanceof TypeBasedAggregationOperationContext) {
this.inputType = ((TypeBasedAggregationOperationContext) aggregationOperationContext).getType();
} else {
this.inputType = null;
}
this.aggregationOperationContext = Lazy.of(() -> aggregationOperationContext != null ? aggregationOperationContext
: aggregationUtil.createAggregationContext(aggregation, getInputType()));
this.pipeline = Lazy.of(() -> aggregationUtil.createPipeline(this.aggregation, getAggregationOperationContext()));
}
/**
* Creates new instance of {@link AggregateContext} extracting the input type from either the
* Creates new instance of {@link AggregationDefinition} extracting the input type from either the
* {@link org.springframework.data.mongodb.core.aggregation.Aggregation} in case of a {@link TypedAggregation} or
* the given {@literal aggregationOperationContext} if present. <br />
* Creates a new {@link AggregationOperationContext} based on the {@link Aggregation} input type and the desired
@@ -839,12 +843,12 @@ class QueryOperations {
* @param aggregation the source aggregation.
* @param inputType can be {@literal null}.
*/
AggregateContext(Aggregation aggregation, @Nullable Class<?> inputType) {
AggregationDefinition(Aggregation aggregation, @Nullable Class<?> inputType) {
this.aggregation = aggregation;
if (aggregation instanceof TypedAggregation) {
this.inputType = ((TypedAggregation) aggregation).getInputType();
this.inputType = ((TypedAggregation<?>) aggregation).getInputType();
} else {
this.inputType = inputType;
}
@@ -887,9 +891,5 @@ class QueryOperations {
Class<?> getInputType() {
return inputType;
}
Document getAggregationCommand(String collectionName) {
return aggregationUtil.createCommand(collectionName, aggregation, getAggregationOperationContext());
}
}
}

View File

@@ -17,7 +17,7 @@ package org.springframework.data.mongodb.core;
import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import org.springframework.data.mongodb.core.QueryOperations.AggregateContext;
import org.springframework.data.mongodb.core.QueryOperations.AggregationDefinition;
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -986,7 +986,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
AggregationOptions options = aggregation.getOptions();
Assert.isTrue(!options.isExplain(), "Cannot use explain option with streaming!");
AggregateContext ctx = queryOperations.createAggregationContext(aggregation, inputType);
AggregationDefinition ctx = queryOperations.createAggregation(aggregation, inputType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Streaming aggregation: {} in collection {}", serializeToJsonSafely(ctx.getAggregationPipeline()), collectionName);

View File

@@ -487,7 +487,7 @@ public class AggregationOptions {
/**
* Apply a strict domain type mapping considering {@link org.springframework.data.mongodb.core.mapping.Field}
* annotations throwing errors for non existing, but referenced fields.
* annotations throwing errors for non-existent, but referenced fields.
*
* @return this.
* @since 3.2
@@ -512,7 +512,7 @@ public class AggregationOptions {
}
/**
* Apply no domain type mapping at all taking the pipeline as is.
* Apply no domain type mapping at all taking the pipeline as-is.
*
* @return this.
* @since 3.2
@@ -568,15 +568,17 @@ public class AggregationOptions {
public enum DomainTypeMapping {
/**
* Mapping throws errors for non existing, but referenced fields.
* Mapping throws errors for non-existent, but referenced fields.
*/
STRICT,
/**
* Fields that do not exist in the model are treated as is.
* Fields that do not exist in the model are treated as-is.
*/
RELAXED,
/**
* Do not attempt to map fields against the model and treat the entire pipeline as is.
* Do not attempt to map fields against the model and treat the entire pipeline as-is.
*/
NONE
}

View File

@@ -25,7 +25,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.QueryOperations.AggregateContext;
import org.springframework.data.mongodb.core.QueryOperations.AggregationDefinition;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
@@ -35,8 +35,9 @@ import org.springframework.data.mongodb.core.convert.UpdateMapper;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
/**
* Unit tests for {@link QueryOperations}.
*
* @author Christoph Strobl
* @since 2021/01
*/
@ExtendWith(MockitoExtension.class)
class QueryOperationsUnitTests {
@@ -66,7 +67,7 @@ class QueryOperationsUnitTests {
void createAggregationContextUsesRelaxedOneForUntypedAggregationsWhenNoInputTypeProvided() {
Aggregation aggregation = Aggregation.newAggregation(Aggregation.project("name"));
AggregateContext ctx = queryOperations.createAggregationContext(aggregation, (Class<?>) null);
AggregationDefinition ctx = queryOperations.createAggregation(aggregation, (Class<?>) null);
assertThat(ctx.getAggregationOperationContext()).isInstanceOf(RelaxedTypeBasedAggregationOperationContext.class);
}
@@ -75,7 +76,7 @@ class QueryOperationsUnitTests {
void createAggregationContextUsesRelaxedOneForTypedAggregationsWhenNoInputTypeProvided() {
Aggregation aggregation = Aggregation.newAggregation(Person.class, Aggregation.project("name"));
AggregateContext ctx = queryOperations.createAggregationContext(aggregation, (Class<?>) null);
AggregationDefinition ctx = queryOperations.createAggregation(aggregation, (Class<?>) null);
assertThat(ctx.getAggregationOperationContext()).isInstanceOf(RelaxedTypeBasedAggregationOperationContext.class);
}
@@ -84,7 +85,7 @@ class QueryOperationsUnitTests {
void createAggregationContextUsesRelaxedOneForUntypedAggregationsWhenInputTypeProvided() {
Aggregation aggregation = Aggregation.newAggregation(Aggregation.project("name"));
AggregateContext ctx = queryOperations.createAggregationContext(aggregation, Person.class);
AggregationDefinition ctx = queryOperations.createAggregation(aggregation, Person.class);
assertThat(ctx.getAggregationOperationContext()).isInstanceOf(RelaxedTypeBasedAggregationOperationContext.class);
}
@@ -93,7 +94,7 @@ class QueryOperationsUnitTests {
void createAggregationContextUsesDefaultIfNoMappingDesired() {
Aggregation aggregation = Aggregation.newAggregation(Aggregation.project("name")).withOptions(NO_MAPPING);
AggregateContext ctx = queryOperations.createAggregationContext(aggregation, Person.class);
AggregationDefinition ctx = queryOperations.createAggregation(aggregation, Person.class);
assertThat(ctx.getAggregationOperationContext()).isEqualTo(Aggregation.DEFAULT_CONTEXT);
}
@@ -103,7 +104,7 @@ class QueryOperationsUnitTests {
Aggregation aggregation = Aggregation.newAggregation(Person.class, Aggregation.project("name"))
.withOptions(STRICT_MAPPING);
AggregateContext ctx = queryOperations.createAggregationContext(aggregation, (Class<?>) null);
AggregationDefinition ctx = queryOperations.createAggregation(aggregation, (Class<?>) null);
assertThat(ctx.getAggregationOperationContext()).isInstanceOf(TypeBasedAggregationOperationContext.class);
}

View File

@@ -2502,7 +2502,7 @@ For further information, see the full https://docs.mongodb.org/manual/aggregatio
[[mongo.aggregation.basic-concepts]]
=== Basic Concepts
The Aggregation Framework support in Spring Data MongoDB is based on the following key abstractions: `Aggregation`, `AggregationOperation`, and `AggregationResults`.
The Aggregation Framework support in Spring Data MongoDB is based on the following key abstractions: `Aggregation`, `AggregationDefinition`, and `AggregationResults`.
* `Aggregation`
+
@@ -2517,12 +2517,12 @@ A `TypedAggregation`, just like an `Aggregation`, holds the instructions of the
At runtime, field references get checked against the given input type, considering potential `@Field` annotations.
[NOTE]
====
Changed in 3.2 referencing nonexistent properties does no longer raise errors. To restore the previous behaviour use the `strictMapping` option of `AggregationOptions`.
Changed in 3.2 referencing none-xistent properties does no longer raise errors. To restore the previous behaviour use the `strictMapping` option of `AggregationOptions`.
====
+
* `AggregationOperation`
* `AggregationDefinition`
+
An `AggregationOperation` represents a MongoDB aggregation pipeline operation and describes the processing that should be performed in this aggregation step. Although you could manually create an `AggregationOperation`, we recommend using the static factory methods provided by the `Aggregate` class to construct an `AggregateOperation`.
An `AggregationDefinition` represents a MongoDB aggregation pipeline operation and describes the processing that should be performed in this aggregation step. Although you could manually create an `AggregationDefinition`, we recommend using the static factory methods provided by the `Aggregate` class to construct an `AggregateOperation`.
+
* `AggregationResults`
+