Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix conversion of types when mapping Aggregation pipeline. #4723

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.4.0-SNAPSHOT</version>
<version>4.4.x-GH-4722-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.4.0-SNAPSHOT</version>
<version>4.4.x-GH-4722-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.4.0-SNAPSHOT</version>
<version>4.4.x-GH-4722-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>4.4.0-SNAPSHOT</version>
<version>4.4.x-GH-4722-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ static List<Document> toDocument(List<AggregationOperation> operations, Aggregat
if (operation instanceof InheritsFieldsAggregationOperation || exposedFieldsOperation.inheritsFields()) {
contextToUse = contextToUse.inheritAndExpose(fields);
} else {
contextToUse = fields.exposesNoFields() ? DEFAULT_CONTEXT
contextToUse = fields.exposesNoFields() ? ConverterAwareNoOpContext.instance(rootContext)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow why we have this distinction here. Switching to contextToUse = contextToUse.expose(fields); also lets all the tests pass.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might cause the mapping to consider filenames of follow up stages to be object to field name mapping which should not be done. if no tests fail, we should make sure that we have coverage there and do not hit a blank spot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, then this one needs to wait for the next service release as we supposedly do not have sufficient test coverage.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confirmed, we're missing a test here - like if we have an operation that forces field mapping to back off (like the replaceRoot one) a strict context would fail because of the missing exposure.

: contextToUse.expose(fields);
}
}
Expand All @@ -72,6 +72,39 @@ static List<Document> toDocument(List<AggregationOperation> operations, Aggregat
return operationDocuments;
}

private static class ConverterAwareNoOpContext implements AggregationOperationContext {

AggregationOperationContext ctx;

static ConverterAwareNoOpContext instance(AggregationOperationContext ctx) {

if(ctx instanceof ConverterAwareNoOpContext noOpContext) {
return noOpContext;
}

return new ConverterAwareNoOpContext(ctx);
}

ConverterAwareNoOpContext(AggregationOperationContext ctx) {
this.ctx = ctx;
}

@Override
public Document getMappedObject(Document document, @Nullable Class<?> type) {
return ctx.getMappedObject(document, null);
}

@Override
public FieldReference getReference(Field field) {
return new DirectFieldReference(new ExposedField(field, true));
}

@Override
public FieldReference getReference(String name) {
return new DirectFieldReference(new ExposedField(new AggregationField(name), true));
}
}

/**
* Simple {@link AggregationOperationContext} that just returns {@link FieldReference}s as is.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,29 @@
*/
package org.springframework.data.mongodb.core.aggregation;

import static org.mockito.Mockito.*;
import static org.springframework.data.domain.Sort.Direction.*;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;

import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.springframework.data.domain.Sort.Direction.DESC;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.project;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.sort;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Set;

import org.assertj.core.api.Assertions;
import org.bson.Document;
import org.junit.jupiter.api.Test;

import org.springframework.data.annotation.Id;
import org.springframework.data.convert.ConverterBuilder;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.CustomConversions.StoreConversions;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
import org.springframework.data.mongodb.core.convert.QueryMapper;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.test.util.MongoTestMappingContext;

/**
Expand All @@ -47,32 +58,56 @@ void nonFieldsExposingAggregationOperationContinuesWithSameContextForNextStage()
verify(stage2).toPipelineStages(eq(rootContext));
}

record TestRecord(@Id String field1, String field2, LayerOne layerOne) {
record LayerOne(List<LayerTwo> layerTwo) {
}
@Test
void contextShouldCarryOnRelaxedFieldMapping() {

record LayerTwo(LayerThree layerThree) {
}
MongoTestMappingContext ctx = new MongoTestMappingContext(cfg -> {
cfg.initialEntitySet(TestRecord.class);
});

MappingMongoConverter mongoConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, ctx);

record LayerThree(int fieldA, int fieldB)
{}
Aggregation agg = Aggregation.newAggregation(Aggregation.unwind("layerOne.layerTwo"),
project().and("layerOne.layerTwo.layerThree").as("layerOne.layerThree"),
sort(DESC, "layerOne.layerThree.fieldA"));

AggregationOperationRenderer.toDocument(agg.getPipeline().getOperations(),
new RelaxedTypeBasedAggregationOperationContext(TestRecord.class, ctx, new QueryMapper(mongoConverter)));
}

@Test
void xxx() {
@Test // GH-4722
void appliesConversionToValuesUsedInAggregation() {

MongoTestMappingContext ctx = new MongoTestMappingContext(cfg -> {
cfg.initialEntitySet(TestRecord.class);
});

MappingMongoConverter mongoConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, ctx);

Aggregation agg = Aggregation.newAggregation(
Aggregation.unwind("layerOne.layerTwo"),
project().and("layerOne.layerTwo.layerThree").as("layerOne.layerThree"),
sort(DESC, "layerOne.layerThree.fieldA")
mongoConverter.setCustomConversions(new CustomConversions(StoreConversions.NONE,
Set.copyOf(ConverterBuilder.writing(ZonedDateTime.class, String.class, ZonedDateTime::toString)
.andReading(it -> ZonedDateTime.parse(it)).getConverters())));
mongoConverter.afterPropertiesSet();

var agg = Aggregation.newAggregation(Aggregation.sort(Direction.DESC, "version"),
Aggregation.group("entityId").first(Aggregation.ROOT).as("value"), Aggregation.replaceRoot("value"),
Aggregation.match(Criteria.where("createdDate").lt(ZonedDateTime.now())) // here is the problem
);

AggregationOperationRenderer.toDocument(agg.getPipeline().getOperations(), new RelaxedTypeBasedAggregationOperationContext(TestRecord.class, ctx, new QueryMapper(mongoConverter)));
List<Document> document = AggregationOperationRenderer.toDocument(agg.getPipeline().getOperations(),
new RelaxedTypeBasedAggregationOperationContext(TestRecord.class, ctx, new QueryMapper(mongoConverter)));
Assertions.assertThat(document).last()
.extracting(it -> it.getEmbedded(List.of("$match", "createdDate", "$lt"), Object.class))
.isInstanceOf(String.class);
}

record TestRecord(@Id String field1, String field2, LayerOne layerOne) {
record LayerOne(List<LayerTwo> layerTwo) {
}

record LayerTwo(LayerThree layerThree) {
}

record LayerThree(int fieldA, int fieldB) {
}
}
}
Loading