spring-data-mongodb icon indicating copy to clipboard operation
spring-data-mongodb copied to clipboard

@Aggregation not work for { $group: { _id: $XXX } }

Open vipcxj opened this issue 3 months ago • 3 comments

public interface ResourceRepository
        extends ReactiveMongoRepository<Resource, String>, ReactiveQuerydslPredicateExecutor<Resource> {
    @Aggregation(pipeline = {
            "{ $match: { sha256: ?0, completed: true } }",
            "{ $group: { _id: $refId, count: { $sum: 1 }, ids: { $push: $_id } } }",
            "{ $project: { _id: 1, count: 1, ids: 1, sortKey: { $cond: { if: { $eq: ['$_id', null] }, then: -Infinity, else: '$count' } } } }",
            "{ $sort: { sortKey: -1 } }",
            "{ $group: { _id: null, mostFrequent: { $first: '$$ROOT' }, allGroups: { $push: '$$ROOT' } } }",
            "{ $project: { refId: '$mostFrequent._id', ids: { $reduce: { input: { $filter: { input: '$allGroups', cond: { $or: [{ $eq: ['$mostFrequent._id', null] }, { $ne: ['$$this._id', '$mostFrequent._id'] }] } } }, initialValue: [], in: { $concatArrays: ['$$value', '$$this.ids'] } } }, _id: 0 } }"
    })
    Mono<RefIdResult> findMostFrequentRefIdWithIds(String sha256);
}

Here is my method. And when invoking it, an error occured:

org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [org.bson.types.ObjectId] for value [$refId]

After debug, I found the problem here (QueryMapper.class):

				Field field = createPropertyField(entity, key, mappingContext);  // here it created a field point to Resource.id, becuase key == '_id'

				// TODO: move to dedicated method
				if (field.getProperty() != null && field.getProperty().isUnwrapped()) {

					Object theNestedObject = BsonUtils.get(query, key);
					Document mappedValue = (Document) getMappedValue(field, theNestedObject);
					if (!StringUtils.hasText(field.getMappedKey())) {
						result.putAll(mappedValue);
					} else {
						result.put(field.getMappedKey(), mappedValue);
					}
				} else {

					Entry<String, Object> entry = getMappedObjectForField(field, BsonUtils.get(query, key)); // BsonUtils.get(query, key) == '$refId', spring-data-mongodb think $refId is an ObjectId, and convert it to ObjectId by new ObjectId("$refId"), and failed.

vipcxj avatar Sep 12 '25 08:09 vipcxj

Thank you for reporting - it would help us if you could provide a complete minimal sample (something that we can unzip or git clone, build, and deploy) that reproduces the problem.

christophstrobl avatar Sep 12 '25 08:09 christophstrobl

@christophstrobl Here is the sample By the way, if this is confirmed to be a bug, I hope a workaround can be provided. Waiting for an official update is too long.

Here is the output:

%TESTC  1 v2
%TSTTREE2,com.cxj.bugreport.bugreport.mongo.dao.ResourceRepositoryTest,true,1,false,1,ResourceRepositoryTest,,[engine:junit-jupiter]/[class:com.cxj.bugreport.bugreport.mongo.dao.ResourceRepositoryTest]
%TSTTREE3,testFindMostFrequentRefIdWithIds(com.cxj.bugreport.bugreport.mongo.dao.ResourceRepositoryTest),false,1,false,2,testFindMostFrequentRefIdWithIds(),,[engine:junit-jupiter]/[class:com.cxj.bugreport.bugreport.mongo.dao.ResourceRepositoryTest]/[method:testFindMostFrequentRefIdWithIds()]
%TESTS  3,testFindMostFrequentRefIdWithIds(com.cxj.bugreport.bugreport.mongo.dao.ResourceRepositoryTest)

%FAILED 3,testFindMostFrequentRefIdWithIds(com.cxj.bugreport.bugreport.mongo.dao.ResourceRepositoryTest)
%TRACES 
java.lang.AssertionError: expectation "assertNext" failed (expected: onNext(); actual: onError(org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [org.bson.types.ObjectId] for value [$refId]))
        at reactor.test.MessageFormatter.assertionError(MessageFormatter.java:115)
        at reactor.test.MessageFormatter.failPrefix(MessageFormatter.java:104)
        at reactor.test.MessageFormatter.fail(MessageFormatter.java:73)
        at reactor.test.MessageFormatter.failOptional(MessageFormatter.java:88)
        at reactor.test.DefaultStepVerifierBuilder.lambda$consumeNextWith$1(DefaultStepVerifierBuilder.java:276)
        at reactor.test.DefaultStepVerifierBuilder$SignalEvent.test(DefaultStepVerifierBuilder.java:2289)
        at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onSignal(DefaultStepVerifierBuilder.java:1529)
        at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onExpectation(DefaultStepVerifierBuilder.java:1477)
        at reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onError(DefaultStepVerifierBuilder.java:1129)
        at reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:93)
        at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.deferredError(FluxUsingWhen.java:403)
        at reactor.core.publisher.FluxUsingWhen$RollbackInner.onComplete(FluxUsingWhen.java:480)
        at reactor.core.publisher.Operators.complete(Operators.java:137)
        at reactor.core.publisher.MonoEmpty.subscribe(MonoEmpty.java:46)
        at reactor.core.publisher.Mono.subscribe(Mono.java:4576)
        at reactor.core.publisher.FluxUsingWhen$UsingWhenSubscriber.onError(FluxUsingWhen.java:368)
        at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onError(MonoFlatMapMany.java:256)
        at reactor.core.publisher.MonoNext$NextSubscriber.onError(MonoNext.java:93)
        at reactor.core.publisher.FluxMap$MapSubscriber.onError(FluxMap.java:134)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:106)
        at reactor.core.publisher.Operators.error(Operators.java:198)
        at reactor.core.publisher.MonoError.subscribe(MonoError.java:53)
        at reactor.core.publisher.Mono.subscribe(Mono.java:4576)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
        at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onNext(MonoFlatMapMany.java:168)
        at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122)
        at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
        at reactor.core.publisher.MonoSupplier$MonoSupplierSubscription.request(MonoSupplier.java:145)
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2366)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onSubscribe(FluxOnErrorResume.java:74)
        at reactor.core.publisher.MonoSupplier.subscribe(MonoSupplier.java:48)
        at reactor.core.publisher.Mono.subscribe(Mono.java:4576)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.onError(MonoFlatMap.java:180)
        at reactor.core.publisher.FluxFilter$FilterSubscriber.onError(FluxFilter.java:157)
        at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onError(FluxMap.java:265)
        at reactor.core.publisher.Operators.error(Operators.java:198)
        at reactor.core.publisher.MonoError.subscribe(MonoError.java:53)
        at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55)
        at reactor.core.publisher.Mono.subscribe(Mono.java:4576)
        at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onNext(MonoFlatMapMany.java:196)
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:245)
        at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:305)
        at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2096)
        at reactor.core.publisher.MonoCollectList$MonoCollectListSubscriber.onComplete(MonoCollectList.java:118)
        at reactor.core.publisher.FluxConcatIterable$ConcatIterableSubscriber.onComplete(FluxConcatIterable.java:121)
        at reactor.core.publisher.FluxConcatIterable.subscribe(FluxConcatIterable.java:60)
        at reactor.core.publisher.MonoFromFluxOperator.subscribe(MonoFromFluxOperator.java:83)
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165)
        at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122)
        at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:122)
        at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
        at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2570)
        at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onSubscribeInner(MonoFlatMapMany.java:150)
        at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onNext(MonoFlatMapMany.java:189)
        at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
        at reactor.core.publisher.MonoSupplier$MonoSupplierSubscription.request(MonoSupplier.java:145)
        at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2366)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onSubscribe(FluxOnErrorResume.java:74)
        at reactor.core.publisher.MonoSupplier.subscribe(MonoSupplier.java:48)
        at reactor.core.publisher.Mono.subscribe(Mono.java:4576)
        at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onError(FluxOnErrorResume.java:103)
        at reactor.core.publisher.MonoFlatMap$FlatMapMain.onError(MonoFlatMap.java:180)
        at reactor.core.publisher.FluxFilter$FilterSubscriber.onError(FluxFilter.java:157)
        at reactor.core.publisher.FluxMap$MapConditionalSubscriber.onError(FluxMap.java:265)
        at reactor.core.publisher.Operators.error(Operators.java:198)
        at reactor.core.publisher.MonoError.subscribe(MonoError.java:53)
        at reactor.core.publisher.MonoDeferContextual.subscribe(MonoDeferContextual.java:55)
        at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:76)
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:53)
        at reactor.core.publisher.Flux.subscribe(Flux.java:8891)
        at reactor.core.publisher.FluxUsingWhen.subscribe(FluxUsingWhen.java:94)
        at reactor.core.publisher.Mono.subscribe(Mono.java:4576)
        at reactor.test.DefaultStepVerifierBuilder$DefaultStepVerifier.toVerifierAndSubscribe(DefaultStepVerifierBuilder.java:891)
        at reactor.test.DefaultStepVerifierBuilder$DefaultStepVerifier.verify(DefaultStepVerifierBuilder.java:831)
        at reactor.test.DefaultStepVerifierBuilder$DefaultStepVerifier.verify(DefaultStepVerifierBuilder.java:823)
        at reactor.test.DefaultStepVerifierBuilder.verifyComplete(DefaultStepVerifierBuilder.java:690)
        at com.cxj.bugreport.bugreport.mongo.dao.ResourceRepositoryTest.testFindMostFrequentRefIdWithIds(ResourceRepositoryTest.java:74)
        at java.base/java.lang.reflect.Method.invoke(Method.java:569)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
        Suppressed: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [org.bson.types.ObjectId] for value [$refId]
                at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47)
                at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:182)
                at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:165)
                at org.springframework.data.mongodb.core.convert.QueryMapper.applyFieldTargetTypeHintToValue(QueryMapper.java:930)

                at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedValue(QueryMapper.java:461)
                at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObjectForField(QueryMapper.java:365)
                at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObject(QueryMapper.java:184)
                at org.springframework.data.mongodb.core.convert.QueryMapper.convertSimpleOrDocument(QueryMapper.java:566)
                at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedKeyword(QueryMapper.java:424)
                at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObject(QueryMapper.java:143)
                at org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext.getMappedObject(TypeBasedAggregationOperationContext.java:102)
                at org.springframework.data.mongodb.repository.query.StringAggregationOperation.toDocument(StringAggregationOperation.java:54)
                at org.springframework.data.mongodb.core.aggregation.AggregationOperation.toPipelineStages(AggregationOperation.java:55)
                at org.springframework.data.mongodb.core.aggregation.AggregationOperationRenderer.toDocument(AggregationOperationRenderer.java:56)
                at org.springframework.data.mongodb.core.aggregation.AggregationPipeline.toDocuments(AggregationPipeline.java:86)
                at org.springframework.data.mongodb.core.aggregation.Aggregation.toPipeline(Aggregation.java:771)
                at org.springframework.data.mongodb.core.AggregationUtil.createPipeline(AggregationUtil.java:88)
                at org.springframework.data.mongodb.core.QueryOperations$AggregationDefinition.lambda$new$3(QueryOperations.java:1008)
                at org.springframework.data.util.Lazy.getNullable(Lazy.java:135)
                at org.springframework.data.util.Lazy.get(Lazy.java:113)
                at org.springframework.data.mongodb.core.QueryOperations$AggregationDefinition.getAggregationPipeline(QueryOperations.java:1017)
                at org.springframework.data.mongodb.core.ReactiveMongoTemplate.lambda$doAggregate$26(ReactiveMongoTemplate.java:1032)
                at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onNext(MonoFlatMapMany.java:163)
                ... 59 more
        Caused by: java.lang.IllegalArgumentException: state should be: hexString has 24 characters
                at org.bson.assertions.Assertions.isTrueArgument(Assertions.java:64)
                at org.bson.types.ObjectId.parseHexString(ObjectId.java:384)
                at org.bson.types.ObjectId.<init>(ObjectId.java:193)
                at org.springframework.data.mongodb.core.convert.MongoConverters$StringToObjectIdConverter.convert(MongoConverters.java:146)
                at org.springframework.data.mongodb.core.convert.MongoConverters$StringToObjectIdConverter.convert(MongoConverters.java:142)
                at org.springframework.core.convert.support.GenericConversionService$ConverterAdapter.convert(GenericConversionService.java:364)
                at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
                ... 81 more

%TRACEE 
%TESTE  3,testFindMostFrequentRefIdWithIds(com.cxj.bugreport.bugreport.mongo.dao.ResourceRepositoryTest)

%RUNTIME5586

vipcxj avatar Sep 12 '25 09:09 vipcxj

thanks for reporting. looks like we need a dedicated aggregation mapper that can deal with field references instead of piping values through the converter as the QueryMapper does.

christophstrobl avatar Oct 28 '25 13:10 christophstrobl