Skip to content

4.0.1 breaks aggregation projection $map functionality #4370

Closed
@sculeb

Description

@sculeb

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions