Browse Source

DATAMONGO-2516 - Assert compatibility with MongoDB 4.4-rc0.

Fixes:
- Fields list must not contain text search score property when no $text criteria present.
- Sort must not be an empty document when running map reduce.
- Timeout in tests creating indexes.

Changes:
- score property might now be null when not having a $text criteria present. Was zero before.

Original pull request: #856.
pull/862/head
Christoph Strobl 6 years ago committed by Mark Paluch
parent
commit
fba6d7d8be
No known key found for this signature in database
GPG Key ID: 51A00FA751B91849
  1. 49
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  2. 44
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/QueryOperations.java
  3. 30
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  4. 5
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java
  5. 7
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/UpdateOperationsUnitTests.java
  6. 4
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java

49
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; @@ -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, @@ -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, @@ -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<Document> 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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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.

44
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; @@ -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 { @@ -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<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final AggregationUtil aggregationUtil;
@ -81,14 +84,16 @@ class QueryOperations { @@ -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 { @@ -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 { @@ -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);
}

30
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

@ -39,7 +39,6 @@ import org.reactivestreams.Publisher; @@ -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; @@ -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 @@ -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 @@ -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 @@ -2152,7 +2153,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
MapReducePublisher<Document> 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 @@ -2369,8 +2374,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
Class<T> 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 @@ -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 @@ -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: {}",

5
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/ReactiveMongoTemplateIndexTests.java

@ -208,8 +208,9 @@ public class ReactiveMongoTemplateIndexTests { @@ -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();

7
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/UpdateOperationsUnitTests.java

@ -49,8 +49,9 @@ class UpdateOperationsUnitTests { @@ -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 { @@ -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

4
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/MongoRepositoryTextSearchIntegrationTests.java

@ -169,13 +169,13 @@ public class MongoRepositoryTextSearchIntegrationTests { @@ -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<FullTextDocument> 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() {

Loading…
Cancel
Save