Skip to content

Commit

Permalink
Fix mongo aggregate projection (#2950)
Browse files Browse the repository at this point in the history
* Fix mongo projection against complex value

* Tweak test and classes being tested

* Trying to simplify reading projection result

* Update AbstractMongoRepositoryOperations.java

* Fix Sonar warnings
  • Loading branch information
radovanradic authored May 22, 2024
1 parent a8d0d97 commit 477eabc
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -73,6 +74,8 @@ abstract sealed class AbstractMongoRepositoryOperations<Dtb> 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;

Expand Down Expand Up @@ -162,22 +165,12 @@ protected <R> R convertResult(CodecRegistry codecRegistry,
}
Optional<BeanIntrospection<R>> introspection = BeanIntrospector.SHARED.findIntrospection(resultType);
if (introspection.isPresent()) {
return (new BeanIntrospectionMapper<BsonDocument, R>() {
@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) {
Expand All @@ -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 {
Expand All @@ -203,6 +196,25 @@ public ConversionService getConversionService() {
return conversionService.convertRequired(MongoUtils.toValue(value), resultType);
}

private <R> R mapIntrospectedObject(BsonDocument result, Class<R> resultType) {
return (new BeanIntrospectionMapper<BsonDocument, R>() {
@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<Object> persistentEntity,
Object child, RuntimePersistentEntity<Object> childPersistentEntity) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -658,4 +688,9 @@ class MongoDocumentRepositorySpec extends AbstractDocumentRepositorySpec impleme
ElementRowRepository getElementRowRepository() {
return context.getBean(ElementRowRepository)
}

@Memoized
ComplexEntityRepository getComplexEntityRepository() {
return context.getBean(ComplexEntityRepository)
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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) {
}
Original file line number Diff line number Diff line change
@@ -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<ComplexEntity, String> {

Optional<ComplexValue> findComplexValueById(String id);

Optional<String> findSimpleValueById(String id);

List<ComplexValue> findAllComplexValue();
}

0 comments on commit 477eabc

Please sign in to comment.