diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java index 332ec87e8..ea858d2de 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java @@ -93,6 +93,7 @@ import org.springframework.data.mongodb.core.mapreduce.GroupBy; import org.springframework.data.mongodb.core.mapreduce.GroupByResults; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; import org.springframework.data.mongodb.core.mapreduce.MapReduceResults; +import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Meta; @@ -238,7 +239,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, this.projectionFactory = new SpelAwareProxyProjectionFactory(); this.operations = new EntityOperations(this.mongoConverter.getMappingContext()); this.propertyOperations = new PropertyOperations(this.mongoConverter.getMappingContext()); - this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, mongoDbFactory); + this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations, mongoDbFactory); // We always have a mapping context in the converter, whether it's a simple one or not mappingContext = this.mongoConverter.getMappingContext(); @@ -424,8 +425,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, MongoPersistentEntity persistentEntity = mappingContext.getPersistentEntity(entityType); - Document mappedFields = getMappedFieldsObject(query.getFieldsObject(), persistentEntity, returnType); - Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), persistentEntity); + QueryContext queryContext = queryOperations.createQueryContext(query); + + Document mappedQuery = queryContext.getMappedQuery(persistentEntity); + Document mappedFields = queryContext.getMappedFields(persistentEntity, returnType, projectionFactory); FindIterable cursor = new QueryCursorPreparer(query, entityType).initiateFind(collection, col -> col.find(mappedQuery, Document.class).projection(mappedFields)); @@ -1054,7 +1057,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, QueryContext queryContext = queryOperations.createQueryContext(query); Document mappedQuery = queryContext.getMappedQuery(entity); - Document mappedFields = queryContext.getMappedFields(entity); + Document mappedFields = queryContext.getMappedFields(entity, resultType, projectionFactory); Document mappedSort = queryContext.getMappedSort(entity); replacement = maybeCallBeforeConvert(replacement, collectionName); @@ -1819,7 +1822,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, if (query.getMeta().getMaxTimeMsec() != null) { mapReduce = mapReduce.maxTime(query.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS); } - mapReduce = mapReduce.sort(getMappedSortObject(query, domainType)); + + Document mappedSort = getMappedSortObject(query, domainType); + if(mappedSort != null && !mappedSort.isEmpty()) { + mapReduce = mapReduce.sort(getMappedSortObject(query, domainType)); + } mapReduce = mapReduce .filter(queryMapper.getMappedObject(query.getQueryObject(), mappingContext.getPersistentEntity(domainType))); @@ -2435,8 +2442,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - Document mappedQuery = queryMapper.getMappedObject(query, entity); - Document mappedFields = queryMapper.getMappedObject(fields, entity); + QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields)); + Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory); + Document mappedQuery = queryContext.getMappedQuery(entity); if (LOGGER.isDebugEnabled()) { LOGGER.debug("findOne using query: {} fields: {} for class: {} in collection: {}", serializeToJsonSafely(query), @@ -2486,8 +2494,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - Document mappedFields = queryMapper.getMappedFields(fields, entity); - Document mappedQuery = queryMapper.getMappedObject(query, entity); + QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields)); + Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory); + Document mappedQuery = queryContext.getMappedQuery(entity); if (LOGGER.isDebugEnabled()) { LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}", @@ -2509,8 +2518,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, MongoPersistentEntity entity = mappingContext.getPersistentEntity(sourceClass); - Document mappedFields = getMappedFieldsObject(fields, entity, targetClass); - Document mappedQuery = queryMapper.getMappedObject(query, entity); + QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields)); + Document mappedFields = queryContext.getMappedFields(entity, targetClass, projectionFactory); + Document mappedQuery = queryContext.getMappedQuery(entity); if (LOGGER.isDebugEnabled()) { LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}", @@ -2839,23 +2849,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware, return queryMapper.getMappedSort(query.getSortObject(), mappingContext.getPersistentEntity(type)); } - private Document getMappedFieldsObject(Document fields, @Nullable MongoPersistentEntity entity, - Class targetType) { - - if (entity == null) { - return fields; - } - - Document projectedFields = propertyOperations.computeFieldsForProjection(projectionFactory, fields, - entity.getType(), targetType); - - if (ObjectUtils.nullSafeEquals(fields, projectedFields)) { - return queryMapper.getMappedFields(projectedFields, entity); - } - - return queryMapper.getMappedFields(projectedFields, mappingContext.getRequiredPersistentEntity(targetType)); - } - /** * Tries to convert the given {@link RuntimeException} into a {@link DataAccessException} but returns the original * exception if the conversation failed. Thus allows safe re-throwing of the return value. diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java index 357f1bdde..438e53c5d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java @@ -47,8 +47,10 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.UpdateDefinition; import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter; import org.springframework.data.mongodb.util.BsonUtils; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import com.mongodb.client.model.CountOptions; @@ -70,6 +72,7 @@ class QueryOperations { private final QueryMapper queryMapper; private final UpdateMapper updateMapper; private final EntityOperations entityOperations; + private final PropertyOperations propertyOperations; private final CodecRegistryProvider codecRegistryProvider; private final MappingContext, MongoPersistentProperty> mappingContext; private final AggregationUtil aggregationUtil; @@ -81,14 +84,16 @@ class QueryOperations { * @param queryMapper must not be {@literal null}. * @param updateMapper must not be {@literal null}. * @param entityOperations must not be {@literal null}. + * @param propertyOperations must not be {@literal null}. * @param codecRegistryProvider must not be {@literal null}. */ QueryOperations(QueryMapper queryMapper, UpdateMapper updateMapper, EntityOperations entityOperations, - CodecRegistryProvider codecRegistryProvider) { + PropertyOperations propertyOperations, CodecRegistryProvider codecRegistryProvider) { this.queryMapper = queryMapper; this.updateMapper = updateMapper; this.entityOperations = entityOperations; + this.propertyOperations = propertyOperations; this.codecRegistryProvider = codecRegistryProvider; this.mappingContext = queryMapper.getMappingContext(); this.aggregationUtil = new AggregationUtil(queryMapper, mappingContext); @@ -250,14 +255,31 @@ class QueryOperations { return queryMapper.getMappedObject(getQueryObject(), entity); } - /** - * Get the already mapped {@link Query#getFieldsObject() fields projection} - * - * @param entity the Entity to map field names to. Can be {@literal null}. - * @return never {@literal null}. - */ - Document getMappedFields(@Nullable MongoPersistentEntity entity) { - return queryMapper.getMappedFields(query.getFieldsObject(), entity); + Document getMappedFields(@Nullable MongoPersistentEntity entity, Class targetType, + ProjectionFactory projectionFactory) { + + Document fields = query.getFieldsObject(); + Document mappedFields = fields; + + if (entity == null) { + return mappedFields; + } + + Document projectedFields = propertyOperations.computeFieldsForProjection(projectionFactory, fields, + entity.getType(), targetType); + + if (ObjectUtils.nullSafeEquals(fields, projectedFields)) { + mappedFields = queryMapper.getMappedFields(projectedFields, entity); + } else { + mappedFields = queryMapper.getMappedFields(projectedFields, + mappingContext.getRequiredPersistentEntity(targetType)); + } + + if (entity != null && entity.hasTextScoreProperty() && !query.getQueryObject().containsKey("$text")) { + mappedFields.remove(entity.getTextScoreProperty().getFieldName()); + } + + return mappedFields; } /** @@ -319,6 +341,10 @@ class QueryOperations { } @Override + Document getMappedFields(@Nullable MongoPersistentEntity entity, Class targetType, ProjectionFactory projectionFactory) { + return getMappedFields(entity); + } + Document getMappedFields(@Nullable MongoPersistentEntity entity) { return queryMapper.getMappedFields(new Document(fieldName, 1), entity); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java index c35f02e5b..baddbb2b5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java @@ -39,7 +39,6 @@ import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -94,6 +93,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes; import org.springframework.data.mongodb.core.mapping.event.*; import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions; +import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Meta; import org.springframework.data.mongodb.core.query.Meta.CursorOption; @@ -258,7 +258,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati this.mappingContext = this.mongoConverter.getMappingContext(); this.operations = new EntityOperations(this.mappingContext); this.propertyOperations = new PropertyOperations(this.mappingContext); - this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, mongoDatabaseFactory); + this.queryOperations = new QueryOperations(queryMapper, updateMapper, operations, propertyOperations, + mongoDatabaseFactory); // We create indexes based on mapping events if (this.mappingContext instanceof MongoMappingContext) { @@ -1161,7 +1162,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati QueryContext queryContext = queryOperations.createQueryContext(query); Document mappedQuery = queryContext.getMappedQuery(entity); - Document mappedFields = queryContext.getMappedFields(entity); + Document mappedFields = queryContext.getMappedFields(entity, resultType, projectionFactory); Document mappedSort = queryContext.getMappedSort(entity); return Mono.defer(() -> { @@ -2152,7 +2153,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati MapReducePublisher publisher = collection.mapReduce(mapFunction, reduceFunction, Document.class); publisher.filter(mappedQuery); - publisher.sort(getMappedSortObject(filterQuery, domainType)); + + Document mappedSort = getMappedSortObject(filterQuery, domainType); + if (mappedSort != null && !mappedSort.isEmpty()) { + publisher.sort(mappedSort); + } if (filterQuery.getMeta().getMaxTimeMsec() != null) { publisher.maxTime(filterQuery.getMeta().getMaxTimeMsec(), TimeUnit.MILLISECONDS); @@ -2369,8 +2374,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati Class entityClass, FindPublisherPreparer preparer) { MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - Document mappedQuery = queryMapper.getMappedObject(query, entity); - Document mappedFields = fields == null ? null : queryMapper.getMappedObject(fields, entity); + + QueryContext queryContext = queryOperations + .createQueryContext(new BasicQuery(query, fields != null ? fields : new Document())); + Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory); + Document mappedQuery = queryContext.getMappedQuery(entity); if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("findOne using query: %s fields: %s for class: %s in collection: %s", @@ -2420,8 +2428,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati MongoPersistentEntity entity = mappingContext.getPersistentEntity(entityClass); - Document mappedFields = queryMapper.getMappedFields(fields, entity); - Document mappedQuery = queryMapper.getMappedObject(query, entity); + QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields)); + Document mappedFields = queryContext.getMappedFields(entity, entityClass, projectionFactory); + Document mappedQuery = queryContext.getMappedQuery(entity); if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("find using query: %s fields: %s for class: %s in collection: %s", @@ -2443,8 +2452,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati MongoPersistentEntity entity = mappingContext.getPersistentEntity(sourceClass); - Document mappedFields = getMappedFieldsObject(fields, entity, targetClass); - Document mappedQuery = queryMapper.getMappedObject(query, entity); + QueryContext queryContext = queryOperations.createQueryContext(new BasicQuery(query, fields)); + Document mappedFields = queryContext.getMappedFields(entity, targetClass, projectionFactory); + Document mappedQuery = queryContext.getMappedQuery(entity); if (LOGGER.isDebugEnabled()) { LOGGER.debug("find using query: {} fields: {} for class: {} in collection: {}", diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java index 07757dc25..363f167a0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java @@ -208,8 +208,9 @@ public class ReactiveMongoTemplateIndexTests { StepVerifier.create(template.getCollection("indexedSample").listIndexes(Document.class)).expectNextCount(0) .verifyComplete(); - template.findAll(IndexedSample.class) // - .delayElements(Duration.ofMillis(200)) // TODO: check if 4.2.0 server GA still requires this timeout + template.findAll(IndexedSample.class).defaultIfEmpty(new IndexedSample()) // + .delayElements(Duration.ofMillis(500)) // TODO: check if 4.2.0 server GA still requires this timeout + .then() .as(StepVerifier::create) // .verifyComplete(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/UpdateOperationsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/UpdateOperationsUnitTests.java index 26eee53bf..916010553 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/UpdateOperationsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/UpdateOperationsUnitTests.java @@ -49,8 +49,9 @@ class UpdateOperationsUnitTests { QueryMapper queryMapper = new QueryMapper(mongoConverter); UpdateMapper updateMapper = new UpdateMapper(mongoConverter); EntityOperations entityOperations = new EntityOperations(mappingContext); + PropertyOperations propertyOperations = new PropertyOperations(mappingContext); - ExtendedQueryOperations queryOperations = new ExtendedQueryOperations(queryMapper, updateMapper, entityOperations, + ExtendedQueryOperations queryOperations = new ExtendedQueryOperations(queryMapper, updateMapper, entityOperations, propertyOperations, MongoClientSettings::getDefaultCodecRegistry); @Test // DATAMONGO-2341 @@ -123,9 +124,9 @@ class UpdateOperationsUnitTests { class ExtendedQueryOperations extends QueryOperations { - ExtendedQueryOperations(QueryMapper queryMapper, UpdateMapper updateMapper, EntityOperations entityOperations, + ExtendedQueryOperations(QueryMapper queryMapper, UpdateMapper updateMapper, EntityOperations entityOperations, PropertyOperations propertyOperations, CodecRegistryProvider codecRegistryProvider) { - super(queryMapper, updateMapper, entityOperations, codecRegistryProvider); + super(queryMapper, updateMapper, entityOperations, propertyOperations, codecRegistryProvider); } @NonNull diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java index 75c83049d..7e7f3c1ed 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java @@ -169,13 +169,13 @@ public class MongoRepositoryTextSearchIntegrationTests { assertThat(result.get(0)).isEqualTo(snipes); } - @Test // DATAMONGO-973 + @Test // DATAMONGO-973, DATAMONGO-2516 public void derivedFinderMethodWithoutFullTextShouldNoCauseTroubleWhenHavingEntityWithTextScoreProperty() { initRepoWithDefaultDocuments(); List result = repo.findByTitle(DROP_ZONE.getTitle()); assertThat(result.get(0)).isEqualTo(DROP_ZONE); - assertThat(result.get(0).score).isEqualTo(0.0F); + assertThat(result.get(0).score).isNull(); } private void initRepoWithDefaultDocuments() {