Skip to content

4.0.1 breaks aggregation projection $map functionality #4370

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

Closed
sculeb opened this issue Apr 19, 2023 · 2 comments
Closed

4.0.1 breaks aggregation projection $map functionality #4370

sculeb opened this issue Apr 19, 2023 · 2 comments
Labels
status: waiting-for-feedback We need additional information before we can continue type: documentation A documentation update

Comments

@sculeb
Copy link

sculeb commented Apr 19, 2023

With 4.0.1, specifically #4240, it's not possible to use Spring AggregationExpression in $map of $project stage. A org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for CodecCacheKey{clazz=class org.springframework.data.mongodb.core.aggregation.ConditionalOperators$IfNull is thrown. It is not specific to IfNull tho.

What I want to achieve:

document in mongodb:

{
    "_id": ObjectId("..."),
    "attributes": [
        {
            "text": {
                "de": "DE",
                "en": "EN"
            }
        },
        {
            "text": {
                "en": "EN 2"
            }
        }
    ]
}

Desired result

[
    {
        "_id": ObjectId("..."),
        "attributes": [
            {
                "text": "DE"
            },
            {
                "text": "EN 2"
            }
        ]
    }
]

with following aggregation:

db.collection.aggregate([
    {
        "$project": {
            "_id": 1,
            "attributes": {
                "$map": {
                    "input": "$attributes",
                    "as": "attribute",
                    "in": {
                        "text": {
                            "$ifNull": [
                                "$$attribute.text.de",
                                "$$attribute.text.en"
                            ]
                        }
                    }
                }
            }
        }
    }
])

With 4.0.0:

var attributeProjection = VariableOperators.Map
	.itemsOf("attributes")
	.as("attribute")
	.andApply(ctx -> new Document("text",
			ifNull("$$attribute.text.de").thenValueOf("$$attribute.text.en")));
var aggregation = new TypedAggregation<>(Entity.class,
	project("_id")
		.and(attributeProjection).as("attributes")
);
List<EntityProjection> results = mongoTemplate.aggregate(aggregation, EntityProjection.class)
	.getMappedResults();

With 4.0.1:

var aggregation = new TypedAggregation<>(Entity.class,
	project("_id")
		.and(ctx -> new Document("$map",
			new Document("input", "$attributes")
				.append("as", "attribute")
				.append("in", new Document("text",
					new Document("$ifNull", new ArrayList<>(
						List.of("$$attribute.text.de", "$$attribute.text.en")))
					)
				)))
		.as("attributes")
);
List<EntityProjection> results = mongoTemplate.aggregate(aggregation, EntityProjection.class)
	.getMappedResults();

I attached an example project to reproduce the scenario. With Spring Boot 3.0.1, both tests will pass, with >3.0.1 one will fail.

my analysis

With 4.0.1 - because of #4240 - the pipeline is not mapped anymore in AggregationUtil::createPipeline.

While I still can achieve my desired outcome, I still wanted to bring this up as it feels like a breaking change. But maybe what I did until 4.0.0 was not the "right" way to begin with? In that case I'd be happy for pointers how I should solve my problem with Spring Data MongoDB.
spring-data-mongodb-agg-project-map.zip

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Apr 19, 2023
@christophstrobl
Copy link
Member

Thank you for the detailed report.
The change fixed an undesired behaviour that made things work for you by accident. I've to admit that the documentation of AggregationExpression is not very clear in that regard, and I'll take steps to update it.
However you may still use ConditionalOperators. Only make sure to call toDocument(ctx) to trigger the mapping as outlined below.

...
.andApply(ctx -> new Document("text",
    ifNull("$$attribute.text.de")
      .thenValueOf("$$attribute.text.en")
      .toDocument(ctx);
));

@christophstrobl christophstrobl added type: documentation A documentation update status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-triage An issue we've not yet triaged labels Apr 20, 2023
@sculeb
Copy link
Author

sculeb commented Apr 20, 2023

Thank you @christophstrobl for the quick response!
Works as expected now, thanks!

@sculeb sculeb closed this as completed Apr 20, 2023
christophstrobl added a commit that referenced this issue Apr 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-feedback We need additional information before we can continue type: documentation A documentation update
Projects
None yet
Development

No branches or pull requests

3 participants