From 477eabc32e925f50243accec3ac3669cc1d9f90f Mon Sep 17 00:00:00 2001 From: Radovan Radic Date: Wed, 22 May 2024 08:57:55 +0200 Subject: [PATCH] Fix mongo aggregate projection (#2950) * Fix mongo projection against complex value * Tweak test and classes being tested * Trying to simplify reading projection result * Update AbstractMongoRepositoryOperations.java * Fix Sonar warnings --- .../AbstractMongoRepositoryOperations.java | 56 ++++++++++++------- .../MongoDocumentRepositorySpec.groovy | 39 ++++++++++++- .../mongodb/entities/ComplexEntity.java | 23 ++++++++ .../mongodb/entities/ComplexValue.java | 9 +++ .../repositories/ComplexEntityRepository.java | 19 +++++++ 5 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ComplexEntity.java create mode 100644 data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ComplexValue.java create mode 100644 data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/ComplexEntityRepository.java diff --git a/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java b/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java index dbe85c599b..29c5cb336b 100644 --- a/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java +++ b/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java @@ -53,6 +53,7 @@ import org.bson.codecs.configuration.CodecRegistry; import org.bson.conversions.Bson; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Map; import java.util.Optional; @@ -73,6 +74,8 @@ abstract sealed class AbstractMongoRepositoryOperations extends AbstractRep protected static final BsonDocument EMPTY = new BsonDocument(); protected static final Logger QUERY_LOG = DataSettings.QUERY_LOG; + private static final Logger LOG = LoggerFactory.getLogger(AbstractMongoRepositoryOperations.class); + protected final MongoCollectionNameProvider collectionNameProvider; protected final MongoDatabaseNameProvider databaseNameProvider; @@ -162,22 +165,12 @@ protected R convertResult(CodecRegistry codecRegistry, } Optional> introspection = BeanIntrospector.SHARED.findIntrospection(resultType); if (introspection.isPresent()) { - return (new BeanIntrospectionMapper() { - @Override - public Object read(BsonDocument document, String alias) { - BsonValue bsonValue = document.get(alias); - if (bsonValue == null) { - return null; - } - return MongoUtils.toValue(bsonValue); - } - - @Override - public ConversionService getConversionService() { - return conversionService; - } - - }).map(result, resultType); + try { + return mapIntrospectedObject(result, resultType); + } catch (Exception e) { + LOG.warn("Failed to map @Introspection annotated result. " + + "Now attempting to fallback and read object from the document. Error: {}", e.getMessage()); + } } BsonValue value; if (result == null) { @@ -192,9 +185,9 @@ public ConversionService getConversionService() { value = result.values().iterator().next(); } } else if (isDtoProjection) { - Object dtoResult = MongoUtils.toValue(result.asDocument(), resultType, codecRegistry); + R dtoResult = MongoUtils.toValue(result.asDocument(), resultType, codecRegistry); if (resultType.isInstance(dtoResult)) { - return (R) dtoResult; + return dtoResult; } return conversionService.convertRequired(dtoResult, resultType); } else { @@ -203,6 +196,25 @@ public ConversionService getConversionService() { return conversionService.convertRequired(MongoUtils.toValue(value), resultType); } + private R mapIntrospectedObject(BsonDocument result, Class resultType) { + return (new BeanIntrospectionMapper() { + @Override + public Object read(BsonDocument document, String alias) { + BsonValue bsonValue = document.get(alias); + if (bsonValue == null) { + return null; + } + return MongoUtils.toValue(bsonValue); + } + + @Override + public ConversionService getConversionService() { + return conversionService; + } + + }).map(result, resultType); + } + protected BsonDocument association(CodecRegistry codecRegistry, Object value, RuntimePersistentEntity persistentEntity, Object child, RuntimePersistentEntity childPersistentEntity) { @@ -243,7 +255,9 @@ protected void logFind(MongoFind find) { sb.append(" collation: ").append(collation); } } - QUERY_LOG.debug(sb.toString()); + if (QUERY_LOG.isDebugEnabled()) { + QUERY_LOG.debug(sb.toString()); + } } protected void logAggregate(MongoAggregation aggregation) { @@ -257,7 +271,9 @@ protected void logAggregate(MongoAggregation aggregation) { sb.append(" collation: ").append(collation); } } - QUERY_LOG.debug(sb.toString()); + if (QUERY_LOG.isDebugEnabled()) { + QUERY_LOG.debug(sb.toString()); + } } } diff --git a/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy b/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy index fd959797c4..8e9b590e48 100644 --- a/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy +++ b/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy @@ -8,7 +8,10 @@ import com.mongodb.client.model.Sorts import com.mongodb.client.model.UpdateOptions import com.mongodb.client.model.Updates import groovy.transform.Memoized +import io.micronaut.data.document.mongodb.entities.ComplexEntity +import io.micronaut.data.document.mongodb.entities.ComplexValue import io.micronaut.data.document.mongodb.entities.ElementRow +import io.micronaut.data.document.mongodb.repositories.ComplexEntityRepository import io.micronaut.data.document.mongodb.repositories.ElementRowRepository import io.micronaut.data.document.mongodb.repositories.MongoAuthorRepository import io.micronaut.data.document.mongodb.repositories.MongoDocumentRepository @@ -37,8 +40,6 @@ import io.micronaut.data.mongodb.operations.options.MongoAggregationOptions import io.micronaut.data.mongodb.operations.options.MongoFindOptions import org.bson.BsonDocument -import java.util.stream.Collectors - import static io.micronaut.data.document.tck.repositories.DocumentRepository.Specifications.tagsArrayContains class MongoDocumentRepositorySpec extends AbstractDocumentRepositorySpec implements MongoTestPropertyProvider { @@ -601,6 +602,35 @@ class MongoDocumentRepositorySpec extends AbstractDocumentRepositorySpec impleme elementRowRepository.deleteAll() } + void 'test complex value projection'() { + when: + def complexValue = new ComplexValue("a", "1") + def complexEntity = new ComplexEntity("test1", complexValue) + def savedComplexEntity = complexEntityRepository.save(complexEntity) + def opt = complexEntityRepository.findById(savedComplexEntity.id) + then: + opt.present + opt.get() == savedComplexEntity + opt.get().complexValue == complexValue + when: + def allComplexValues = complexEntityRepository.findAllComplexValue() + then: + allComplexValues.size() == 1 + allComplexValues[0] == complexValue + when: + def optCv = complexEntityRepository.findComplexValueById(savedComplexEntity.id) + then: + optCv.present + optCv.get() == complexValue + when: + def optSimpleValue = complexEntityRepository.findSimpleValueById(savedComplexEntity.id) + then: + optSimpleValue.present + optSimpleValue.get() == savedComplexEntity.simpleValue + cleanup: + complexEntityRepository.deleteAll() + } + @Memoized MongoExecutorPersonRepository getMongoExecutorPersonRepository() { return context.getBean(MongoExecutorPersonRepository) @@ -658,4 +688,9 @@ class MongoDocumentRepositorySpec extends AbstractDocumentRepositorySpec impleme ElementRowRepository getElementRowRepository() { return context.getBean(ElementRowRepository) } + + @Memoized + ComplexEntityRepository getComplexEntityRepository() { + return context.getBean(ComplexEntityRepository) + } } diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ComplexEntity.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ComplexEntity.java new file mode 100644 index 0000000000..1a2bc08a7e --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ComplexEntity.java @@ -0,0 +1,23 @@ +package io.micronaut.data.document.mongodb.entities; + +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; + +/** + * An entity with one of field being complex field ie. object. + * @param id The id + * @param simpleValue The simple value field (String) + * @param complexValue The complex value field (Object) + */ +@MappedEntity +public record ComplexEntity ( + @Id + @GeneratedValue + String id, + String simpleValue, + ComplexValue complexValue) { + ComplexEntity(String simpleValue, ComplexValue complexValue) { + this(null, simpleValue, complexValue); + } +} diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ComplexValue.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ComplexValue.java new file mode 100644 index 0000000000..3012f67da9 --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ComplexValue.java @@ -0,0 +1,9 @@ +package io.micronaut.data.document.mongodb.entities; + +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +public record ComplexValue ( + String valueA, + String valueB) { +} diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/ComplexEntityRepository.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/ComplexEntityRepository.java new file mode 100644 index 0000000000..429d4746ae --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/ComplexEntityRepository.java @@ -0,0 +1,19 @@ +package io.micronaut.data.document.mongodb.repositories; + +import io.micronaut.data.document.mongodb.entities.ComplexEntity; +import io.micronaut.data.document.mongodb.entities.ComplexValue; +import io.micronaut.data.mongodb.annotation.MongoRepository; +import io.micronaut.data.repository.CrudRepository; + +import java.util.List; +import java.util.Optional; + +@MongoRepository +public interface ComplexEntityRepository extends CrudRepository { + + Optional findComplexValueById(String id); + + Optional findSimpleValueById(String id); + + List findAllComplexValue(); +}