Support ReadConcern & ReadPreference via the Query and Aggregation API.

Add support for setting the ReadConcern and ReadPreference via the Query and Aggregation API.

Closes: #4277, #4286
Original Pull Request: #4288
This commit is contained in:
Mark Paluch
2023-02-06 09:13:36 -05:00
committed by Christoph Strobl
parent 368c644922
commit c5c6fc107c
14 changed files with 810 additions and 219 deletions

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import org.springframework.util.Assert;
import com.mongodb.client.MongoCollection;
/**
* Interface for functional preparation of a {@link MongoCollection}.
*
* @author Mark Paluch
* @since 4.1
*/
public interface CollectionPreparer<T> {
/**
* Returns a preparer that always returns its input collection.
*
* @return a preparer that always returns its input collection.
*/
static <T> CollectionPreparer<T> identity() {
return it -> it;
}
/**
* Prepare the {@code collection}.
*
* @param collection the collection to prepare.
* @return the prepared collection.
*/
T prepare(T collection);
/**
* Returns a composed {@code CollectionPreparer} that first applies this preparer to the collection, and then applies
* the {@code after} preparer to the result. If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param after the collection preparer to apply after this function is applied.
* @return a composed {@code CollectionPreparer} that first applies this preparer and then applies the {@code after}
* preparer.
*/
default CollectionPreparer<T> andThen(CollectionPreparer<T> after) {
Assert.notNull(after, "After CollectionPreparer must not be null");
return c -> after.prepare(prepare(c));
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.bson.Document;
import com.mongodb.ReadConcern;
import com.mongodb.ReadPreference;
import com.mongodb.client.MongoCollection;
/**
* Support class for delegate implementations to apply {@link ReadConcern} and {@link ReadPreference} settings upon
* {@link CollectionPreparer preparing a collection}.
*
* @author Mark Paluch
* @since 4.1
*/
class CollectionPreparerSupport implements ReadConcernAware, ReadPreferenceAware {
private final List<Object> sources;
private CollectionPreparerSupport(List<Object> sources) {
this.sources = sources;
}
<T> T doPrepare(T collection, Function<T, ReadConcern> concernAccessor, BiFunction<T, ReadConcern, T> concernFunction,
Function<T, ReadPreference> preferenceAccessor, BiFunction<T, ReadPreference, T> preferenceFunction) {
T collectionToUse = collection;
for (Object source : sources) {
if (source instanceof ReadConcernAware rca && rca.hasReadConcern()) {
ReadConcern concern = rca.getReadConcern();
if (concernAccessor.apply(collectionToUse) != concern) {
collectionToUse = concernFunction.apply(collectionToUse, concern);
}
break;
}
}
for (Object source : sources) {
if (source instanceof ReadPreferenceAware rpa && rpa.hasReadPreference()) {
ReadPreference preference = rpa.getReadPreference();
if (preferenceAccessor.apply(collectionToUse) != preference) {
collectionToUse = preferenceFunction.apply(collectionToUse, preference);
}
break;
}
}
return collectionToUse;
}
@Override
public boolean hasReadConcern() {
for (Object aware : sources) {
if (aware instanceof ReadConcernAware rca && rca.hasReadConcern()) {
return true;
}
}
return false;
}
@Override
public ReadConcern getReadConcern() {
for (Object aware : sources) {
if (aware instanceof ReadConcernAware rca && rca.hasReadConcern()) {
return rca.getReadConcern();
}
}
return null;
}
@Override
public boolean hasReadPreference() {
for (Object aware : sources) {
if (aware instanceof ReadPreferenceAware rpa && rpa.hasReadPreference()) {
return true;
}
}
return false;
}
@Override
public ReadPreference getReadPreference() {
for (Object aware : sources) {
if (aware instanceof ReadPreferenceAware rpa && rpa.hasReadPreference()) {
return rpa.getReadPreference();
}
}
return null;
}
static class CollectionPreparerDelegate extends CollectionPreparerSupport
implements CollectionPreparer<MongoCollection<Document>> {
private CollectionPreparerDelegate(List<Object> sources) {
super(sources);
}
public static CollectionPreparerDelegate of(ReadPreferenceAware... awares) {
return of((Object[]) awares);
}
public static CollectionPreparerDelegate of(Object... mixedAwares) {
if (mixedAwares.length == 1 && mixedAwares[0] instanceof CollectionPreparerDelegate) {
return (CollectionPreparerDelegate) mixedAwares[0];
}
return new CollectionPreparerDelegate(Arrays.asList(mixedAwares));
}
@Override
public MongoCollection<Document> prepare(MongoCollection<Document> collection) {
return doPrepare(collection, MongoCollection::getReadConcern, MongoCollection::withReadConcern,
MongoCollection::getReadPreference, MongoCollection::withReadPreference);
}
}
static class ReactiveCollectionPreparerDelegate extends CollectionPreparerSupport
implements CollectionPreparer<com.mongodb.reactivestreams.client.MongoCollection<Document>> {
private ReactiveCollectionPreparerDelegate(List<Object> sources) {
super(sources);
}
public static ReactiveCollectionPreparerDelegate of(ReadPreferenceAware... awares) {
return of((Object[]) awares);
}
public static ReactiveCollectionPreparerDelegate of(Object... mixedAwares) {
if (mixedAwares.length == 1 && mixedAwares[0] instanceof CollectionPreparerDelegate) {
return (ReactiveCollectionPreparerDelegate) mixedAwares[0];
}
return new ReactiveCollectionPreparerDelegate(Arrays.asList(mixedAwares));
}
@Override
public com.mongodb.reactivestreams.client.MongoCollection<Document> prepare(
com.mongodb.reactivestreams.client.MongoCollection<Document> collection) {
return doPrepare(collection, //
com.mongodb.reactivestreams.client.MongoCollection::getReadConcern,
com.mongodb.reactivestreams.client.MongoCollection::withReadConcern,
com.mongodb.reactivestreams.client.MongoCollection::getReadPreference,
com.mongodb.reactivestreams.client.MongoCollection::withReadPreference);
}
}
}

View File

@@ -20,7 +20,6 @@ import java.util.Optional;
import java.util.stream.Stream;
import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
@@ -168,7 +167,8 @@ class ExecutableFindOperationSupport implements ExecutableFindOperation {
Document queryObject = query.getQueryObject();
Document fieldsObject = query.getFieldsObject();
return template.doFind(getCollectionName(), queryObject, fieldsObject, domainType, returnType,
return template.doFind(template.createDelegate(query), getCollectionName(), queryObject, fieldsObject, domainType,
returnType,
getCursorPreparer(query, preparer));
}

View File

@@ -55,6 +55,7 @@ import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.MongoDatabaseUtils;
import org.springframework.data.mongodb.SessionSynchronization;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.CollectionPreparerSupport.CollectionPreparerDelegate;
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
import org.springframework.data.mongodb.core.QueryOperations.AggregationDefinition;
@@ -66,6 +67,7 @@ import org.springframework.data.mongodb.core.QueryOperations.UpdateContext;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.AggregationOptions.Builder;
import org.springframework.data.mongodb.core.aggregation.AggregationPipeline;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
@@ -83,7 +85,6 @@ 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.Meta;
import org.springframework.data.mongodb.core.query.Meta.CursorOption;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
@@ -141,7 +142,8 @@ import com.mongodb.client.result.UpdateResult;
* @author Bartłomiej Mazur
* @author Michael Krog
*/
public class MongoTemplate implements MongoOperations, ApplicationContextAware, IndexOperationsProvider {
public class MongoTemplate
implements MongoOperations, ApplicationContextAware, IndexOperationsProvider, ReadPreferenceAware {
private static final Log LOGGER = LogFactory.getLog(MongoTemplate.class);
private static final WriteResultChecking DEFAULT_WRITE_RESULT_CHECKING = WriteResultChecking.NONE;
@@ -293,6 +295,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
this.readPreference = readPreference;
}
@Override
public boolean hasReadPreference() {
return this.readPreference != null;
}
@Override
public ReadPreference getReadPreference() {
return this.readPreference;
}
/**
* Configure whether lifecycle events such as {@link AfterLoadEvent}, {@link BeforeSaveEvent}, etc. should be
* published or whether emission should be suppressed. Enabled by default.
@@ -363,10 +375,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (enabled) {
this.countExecution = (collectionName, filter, options) -> {
this.countExecution = (collectionPreparer, collectionName, filter, options) -> {
if (!estimationFilter.test(filter, options)) {
return doExactCount(collectionName, filter, options);
return doExactCount(collectionPreparer, collectionName, filter, options);
}
EstimatedDocumentCountOptions estimatedDocumentCountOptions = new EstimatedDocumentCountOptions();
@@ -374,7 +386,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
estimatedDocumentCountOptions.maxTime(options.getMaxTime(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
}
return doEstimatedCount(collectionName, estimatedDocumentCountOptions);
return doEstimatedCount(collectionPreparer, collectionName, estimatedDocumentCountOptions);
};
} else {
this.countExecution = this::doExactCount;
@@ -443,8 +455,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document mappedQuery = queryContext.getMappedQuery(persistentEntity);
Document mappedFields = queryContext.getMappedFields(persistentEntity, projection);
CollectionPreparerDelegate readPreference = createDelegate(query);
FindIterable<Document> cursor = new QueryCursorPreparer(query, entityType).initiateFind(collection,
col -> col.find(mappedQuery, Document.class).projection(mappedFields));
col -> readPreference.prepare(col).find(mappedQuery, Document.class).projection(mappedFields));
return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator,
new ProjectingReadCallback<>(mongoConverter, projection, collectionName)).stream();
@@ -517,7 +530,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(queryObject), sortObject, fieldsObject, collectionName));
}
this.executeQueryInternal(new FindCallback(queryObject, fieldsObject, null),
this.executeQueryInternal(new FindCallback(createDelegate(query), queryObject, fieldsObject, null),
preparer != null ? preparer : CursorPreparer.NO_OP_PREPARER, documentCallbackHandler, collectionName);
}
@@ -765,7 +778,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (ObjectUtils.isEmpty(query.getSortObject())) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(),
return doFindOne(createDelegate(query), collectionName, query.getQueryObject(), query.getFieldsObject(),
new QueryCursorPreparer(query, entityClass), entityClass);
} else {
query.limit(1);
@@ -797,7 +810,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document mappedQuery = queryContext.getMappedQuery(entityClass, this::getPersistentEntity);
return execute(collectionName,
new ExistsCallback(mappedQuery, queryContext.getCollation(entityClass).orElse(null)));
new ExistsCallback(createDelegate(query), mappedQuery, queryContext.getCollation(entityClass).orElse(null)));
}
// Find methods that take a Query to express the query and that return a List of objects.
@@ -814,7 +827,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.notNull(collectionName, "CollectionName must not be null");
Assert.notNull(entityClass, "EntityClass must not be null");
return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
return doFind(createDelegate(query), collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
new QueryCursorPreparer(query, entityClass));
}
@@ -834,7 +847,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
String idKey = operations.getIdPropertyName(entityClass);
return doFindOne(collectionName, new Document(idKey, id), new Document(), entityClass);
return doFindOne(CollectionPreparer.identity(), collectionName, new Document(idKey, id), new Document(),
entityClass);
}
@Override
@@ -867,10 +881,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(mappedQuery), field, collectionName));
}
QueryCursorPreparer preparer = new QueryCursorPreparer(query, entityClass);
if (preparer.hasReadPreference()) {
collection = collection.withReadPreference(preparer.getReadPreference());
}
collection = createDelegate(query).prepare(collection);
DistinctIterable<T> iterable = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
distinctQueryContext.applyCollation(entityClass, iterable::collation);
@@ -920,8 +931,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
String collection = StringUtils.hasText(collectionName) ? collectionName : getCollectionName(domainType);
String distanceField = operations.nearQueryDistanceFieldName(domainType);
Builder optionsBuilder = AggregationOptions.builder().collation(near.getCollation());
Query query = near.getQuery();
if (query != null && query.hasReadPreference()) {
optionsBuilder.readPreference(query.getReadPreference());
}
Aggregation $geoNear = TypedAggregation.newAggregation(domainType, Aggregation.geoNear(near, distanceField))
.withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
.withOptions(optionsBuilder.build());
AggregationResults<Document> results = aggregate($geoNear, collection, Document.class);
EntityProjection<T, ?> projection = operations.introspectProjection(returnType, domainType);
@@ -986,7 +1004,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
operations.forType(entityClass).getCollation(query).ifPresent(optionsToUse::collation);
}
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
return doFindAndModify(createDelegate(query), collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
}
@@ -1008,6 +1026,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
QueryContext queryContext = queryOperations.createQueryContext(query);
EntityProjection<T, S> projection = operations.introspectProjection(resultType, entityType);
CollectionPreparerDelegate collectionPreparer = createDelegate(query);
Document mappedQuery = queryContext.getMappedQuery(entity);
Document mappedFields = queryContext.getMappedFields(entity, projection);
Document mappedSort = queryContext.getMappedSort(entity);
@@ -1018,7 +1037,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
maybeEmitEvent(new BeforeSaveEvent<>(replacement, mappedReplacement, collectionName));
maybeCallBeforeSave(replacement, mappedReplacement, collectionName);
T saved = doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
T saved = doFindAndReplace(collectionPreparer, collectionName, mappedQuery, mappedFields, mappedSort,
queryContext.getCollation(entityType).orElse(null), entityType, mappedReplacement, options, projection);
if (saved != null) {
@@ -1046,7 +1065,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Assert.notNull(entityClass, "EntityClass must not be null");
Assert.notNull(collectionName, "CollectionName must not be null");
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
return doFindAndRemove(createDelegate(query), collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), operations.forType(entityClass).getCollation(query).orElse(null),
entityClass);
}
@@ -1078,17 +1097,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
CountOptions options = countContext.getCountOptions(entityClass);
Document mappedQuery = countContext.getMappedQuery(entityClass, mappingContext::getPersistentEntity);
return doCount(collectionName, mappedQuery, options);
CollectionPreparerDelegate readPreference = createDelegate(query);
return doCount(readPreference, collectionName, mappedQuery, options);
}
protected long doCount(String collectionName, Document filter, CountOptions options) {
protected long doCount(CollectionPreparer collectionPreparer, String collectionName, Document filter,
CountOptions options) {
if (LOGGER.isDebugEnabled()) {
LOGGER
.debug(String.format("Executing count: %s in collection: %s", serializeToJsonSafely(filter), collectionName));
}
return countExecution.countDocuments(collectionName, filter, options);
return countExecution.countDocuments(collectionPreparer, collectionName, filter, options);
}
/*
@@ -1097,11 +1118,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
@Override
public long estimatedCount(String collectionName) {
return doEstimatedCount(collectionName, new EstimatedDocumentCountOptions());
return doEstimatedCount(CollectionPreparerDelegate.of(this), collectionName, new EstimatedDocumentCountOptions());
}
protected long doEstimatedCount(String collectionName, EstimatedDocumentCountOptions options) {
return execute(collectionName, collection -> collection.estimatedDocumentCount(options));
protected long doEstimatedCount(CollectionPreparer<MongoCollection<Document>> collectionPreparer,
String collectionName,
EstimatedDocumentCountOptions options) {
return execute(collectionName,
collection -> collectionPreparer.prepare(collection).estimatedDocumentCount(options));
}
@Override
@@ -1112,12 +1136,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
CountOptions options = countContext.getCountOptions(entityClass);
Document mappedQuery = countContext.getMappedQuery(entityClass, mappingContext::getPersistentEntity);
return doExactCount(collectionName, mappedQuery, options);
return doExactCount(createDelegate(query), collectionName, mappedQuery, options);
}
protected long doExactCount(String collectionName, Document filter, CountOptions options) {
return execute(collectionName,
collection -> collection.countDocuments(CountQuery.of(filter).toQueryDocument(), options));
protected long doExactCount(CollectionPreparer<MongoCollection<Document>> collectionPreparer, String collectionName,
Document filter, CountOptions options) {
return execute(collectionName, collection -> collectionPreparer.prepare(collection)
.countDocuments(CountQuery.of(filter).toQueryDocument(), options));
}
protected boolean countCanBeEstimated(Document filter, CountOptions options) {
@@ -1177,8 +1202,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
protected MongoCollection<Document> prepareCollection(MongoCollection<Document> collection) {
if (this.readPreference != null) {
collection = collection.withReadPreference(readPreference);
if (this.readPreference != null && this.readPreference != collection.getReadPreference()) {
return collection.withReadPreference(readPreference);
}
return collection;
@@ -1754,7 +1779,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Override
public <T> List<T> findAll(Class<T> entityClass, String collectionName) {
return executeFindMultiInternal(
new FindCallback(new Document(), new Document(),
new FindCallback(CollectionPreparer.identity(), new Document(), new Document(),
operations.forType(entityClass).getCollation().map(Collation::toMongoCollation).orElse(null)),
CursorPreparer.NO_OP_PREPARER, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName),
collectionName);
@@ -1812,7 +1837,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
String mapFunc = replaceWithResourceIfNecessary(mapFunction);
String reduceFunc = replaceWithResourceIfNecessary(reduceFunction);
MongoCollection<Document> inputCollection = getAndPrepareCollection(doGetDatabase(), inputCollectionName);
CollectionPreparerDelegate readPreference = createDelegate(query);
MongoCollection<Document> inputCollection = readPreference
.prepare(getAndPrepareCollection(doGetDatabase(), inputCollectionName));
// MapReduceOp
MapReduceIterable<Document> mapReduce = inputCollection.mapReduce(mapFunc, reduceFunc, Document.class);
@@ -1977,6 +2004,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (!CollectionUtils.isEmpty(result)) {
Query byIdInQuery = operations.getByIdInQuery(result);
if (query.hasReadPreference()) {
byIdInQuery.withReadPreference(query.getReadPreference());
}
remove(byIdInQuery, entityClass, collectionName);
}
@@ -2032,7 +2062,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return execute(collectionName, collection -> {
List<Document> rawResult = new ArrayList<>();
CollectionPreparerDelegate delegate = CollectionPreparerDelegate.of(options);
Class<?> domainType = aggregation instanceof TypedAggregation ? ((TypedAggregation<?>) aggregation).getInputType()
: null;
@@ -2040,7 +2070,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
() -> operations.forType(domainType) //
.getCollation());
AggregateIterable<Document> aggregateIterable = collection.aggregate(pipeline, Document.class) //
AggregateIterable<Document> aggregateIterable = delegate.prepare(collection).aggregate(pipeline, Document.class) //
.collation(collation.map(Collation::toMongoCollation).orElse(null)) //
.allowDiskUse(options.isAllowDiskUse());
@@ -2103,7 +2133,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return execute(collectionName, (CollectionCallback<Stream<O>>) collection -> {
AggregateIterable<Document> cursor = collection.aggregate(pipeline, Document.class) //
CollectionPreparerDelegate delegate = CollectionPreparerDelegate.of(options);
AggregateIterable<Document> cursor = delegate.prepare(collection).aggregate(pipeline, Document.class) //
.allowDiskUse(options.isAllowDiskUse());
if (options.getCursorBatchSize() != null) {
@@ -2350,8 +2382,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @return the converted object or {@literal null} if none exists.
*/
@Nullable
protected <T> T doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass) {
return doFindOne(collectionName, query, fields, CursorPreparer.NO_OP_PREPARER, entityClass);
protected <T> T doFindOne(CollectionPreparer collectionPreparer, String collectionName, Document query,
Document fields, Class<T> entityClass) {
return doFindOne(collectionPreparer, collectionName, query, fields, CursorPreparer.NO_OP_PREPARER, entityClass);
}
/**
@@ -2368,8 +2401,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
@Nullable
@SuppressWarnings("ConstantConditions")
protected <T> T doFindOne(String collectionName, Document query, Document fields, CursorPreparer preparer,
Class<T> entityClass) {
protected <T> T doFindOne(CollectionPreparer collectionPreparer, String collectionName, Document query,
Document fields, CursorPreparer preparer, Class<T> entityClass) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
@@ -2382,7 +2415,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(query), mappedFields, entityClass, collectionName));
}
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, preparer),
return executeFindOneInternal(new FindOneCallback(collectionPreparer, mappedQuery, mappedFields, preparer),
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
}
@@ -2396,8 +2429,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @param entityClass the parameterized type of the returned list.
* @return the List of converted objects.
*/
protected <T> List<T> doFind(String collectionName, Document query, Document fields, Class<T> entityClass) {
return doFind(collectionName, query, fields, entityClass, null,
protected <T> List<T> doFind(CollectionPreparer collectionPreparer, String collectionName, Document query,
Document fields, Class<T> entityClass) {
return doFind(collectionPreparer, collectionName, query, fields, entityClass, null,
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName));
}
@@ -2414,14 +2448,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* (apply limits, skips and so on).
* @return the {@link List} of converted objects.
*/
protected <T> List<T> doFind(String collectionName, Document query, Document fields, Class<T> entityClass,
CursorPreparer preparer) {
return doFind(collectionName, query, fields, entityClass, preparer,
protected <T> List<T> doFind(CollectionPreparer collectionPreparer, String collectionName, Document query,
Document fields, Class<T> entityClass, CursorPreparer preparer) {
return doFind(collectionPreparer, collectionName, query, fields, entityClass, preparer,
new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName));
}
protected <S, T> List<T> doFind(String collectionName, Document query, Document fields, Class<S> entityClass,
@Nullable CursorPreparer preparer, DocumentCallback<T> objectCallback) {
protected <S, T> List<T> doFind(CollectionPreparer collectionPreparer, String collectionName, Document query,
Document fields, Class<S> entityClass, @Nullable CursorPreparer preparer, DocumentCallback<T> objectCallback) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
@@ -2434,7 +2468,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName));
}
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null),
return executeFindMultiInternal(new FindCallback(collectionPreparer, mappedQuery, mappedFields, null),
preparer != null ? preparer : CursorPreparer.NO_OP_PREPARER, objectCallback, collectionName);
}
@@ -2444,8 +2478,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*
* @since 2.0
*/
<S, T> List<T> doFind(String collectionName, Document query, Document fields, Class<S> sourceClass,
Class<T> targetClass, CursorPreparer preparer) {
<S, T> List<T> doFind(CollectionPreparer collectionPreparer, String collectionName, Document query, Document fields,
Class<S> sourceClass, Class<T> targetClass, CursorPreparer preparer) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(sourceClass);
EntityProjection<T, S> projection = operations.introspectProjection(targetClass, sourceClass);
@@ -2459,7 +2493,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(mappedQuery), mappedFields, sourceClass, collectionName));
}
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null), preparer,
return executeFindMultiInternal(new FindCallback(collectionPreparer, mappedQuery, mappedFields, null), preparer,
new ProjectingReadCallback<>(mongoConverter, projection, collectionName), collectionName);
}
@@ -2533,8 +2567,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @return the List of converted objects.
*/
@SuppressWarnings("ConstantConditions")
protected <T> T doFindAndRemove(String collectionName, Document query, Document fields, Document sort,
@Nullable Collation collation, Class<T> entityClass) {
protected <T> T doFindAndRemove(CollectionPreparer collectionPreparer, String collectionName, Document query,
Document fields, Document sort, @Nullable Collation collation, Class<T> entityClass) {
EntityReader<? super T, Bson> readerToUse = this.mongoConverter;
@@ -2545,14 +2579,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
return executeFindOneInternal(
new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort, collation),
return executeFindOneInternal(new FindAndRemoveCallback(collectionPreparer,
queryMapper.getMappedObject(query, entity), fields, sort, collation),
new ReadDocumentCallback<>(readerToUse, entityClass, collectionName), collectionName);
}
@SuppressWarnings("ConstantConditions")
protected <T> T doFindAndModify(String collectionName, Document query, Document fields, Document sort,
Class<T> entityClass, UpdateDefinition update, @Nullable FindAndModifyOptions options) {
protected <T> T doFindAndModify(CollectionPreparer collectionPreparer, String collectionName, Document query,
Document fields, Document sort, Class<T> entityClass, UpdateDefinition update,
@Nullable FindAndModifyOptions options) {
EntityReader<? super T, Bson> readerToUse = this.mongoConverter;
@@ -2577,7 +2612,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
return executeFindOneInternal(
new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate,
new FindAndModifyCallback(collectionPreparer, mappedQuery, fields, sort, mappedUpdate,
update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()), options),
new ReadDocumentCallback<>(readerToUse, entityClass, collectionName), collectionName);
}
@@ -2598,14 +2633,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
*/
@Nullable
protected <T> T doFindAndReplace(String collectionName, Document mappedQuery, Document mappedFields,
Document mappedSort, @Nullable com.mongodb.client.model.Collation collation, Class<?> entityType,
Document replacement, FindAndReplaceOptions options, Class<T> resultType) {
protected <T> T doFindAndReplace(CollectionPreparer collectionPreparer, String collectionName, Document mappedQuery,
Document mappedFields, Document mappedSort, @Nullable com.mongodb.client.model.Collation collation,
Class<?> entityType, Document replacement, FindAndReplaceOptions options, Class<T> resultType) {
EntityProjection<T, ?> projection = operations.introspectProjection(resultType, entityType);
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort, collation, entityType, replacement,
options, projection);
return doFindAndReplace(collectionPreparer, collectionName, mappedQuery, mappedFields, mappedSort, collation,
entityType, replacement, options, projection);
}
CollectionPreparerDelegate createDelegate(Query query) {
return CollectionPreparerDelegate.of(query);
}
/**
@@ -2625,9 +2664,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
* @since 3.4
*/
@Nullable
private <T> T doFindAndReplace(String collectionName, Document mappedQuery, Document mappedFields,
Document mappedSort, @Nullable com.mongodb.client.model.Collation collation, Class<?> entityType,
Document replacement, FindAndReplaceOptions options, EntityProjection<T, ?> projection) {
private <T> T doFindAndReplace(CollectionPreparer collectionPreparer, String collectionName, Document mappedQuery,
Document mappedFields, Document mappedSort, @Nullable com.mongodb.client.model.Collation collation,
Class<?> entityType, Document replacement, FindAndReplaceOptions options, EntityProjection<T, ?> projection) {
if (LOGGER.isDebugEnabled()) {
LOGGER
@@ -2638,9 +2677,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
serializeToJsonSafely(mappedSort), entityType, serializeToJsonSafely(replacement), collectionName));
}
return executeFindOneInternal(
new FindAndReplaceCallback(mappedQuery, mappedFields, mappedSort, replacement, collation, options),
new ProjectingReadCallback<>(mongoConverter, projection, collectionName), collectionName);
return executeFindOneInternal(new FindAndReplaceCallback(collectionPreparer, mappedQuery, mappedFields, mappedSort,
replacement, collation, options), new ProjectingReadCallback<>(mongoConverter, projection, collectionName),
collectionName);
}
/**
@@ -2810,12 +2849,15 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
private static class FindOneCallback implements CollectionCallback<Document> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Optional<Document> fields;
private final CursorPreparer cursorPreparer;
FindOneCallback(Document query, Document fields, CursorPreparer preparer) {
FindOneCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query, Document fields,
CursorPreparer preparer) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = Optional.of(fields).filter(it -> !ObjectUtils.isEmpty(fields));
this.cursorPreparer = preparer;
@@ -2824,7 +2866,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Override
public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
FindIterable<Document> iterable = cursorPreparer.initiateFind(collection, col -> col.find(query, Document.class));
FindIterable<Document> iterable = cursorPreparer.initiateFind(collection,
col -> collectionPreparer.prepare(col).find(query, Document.class));
if (LOGGER.isDebugEnabled()) {
@@ -2851,15 +2894,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
private static class FindCallback implements CollectionCallback<FindIterable<Document>> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Document fields;
private final @Nullable com.mongodb.client.model.Collation collation;
public FindCallback(Document query, Document fields, @Nullable com.mongodb.client.model.Collation collation) {
public FindCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query,
Document fields,
@Nullable com.mongodb.client.model.Collation collation) {
Assert.notNull(query, "Query must not be null");
Assert.notNull(fields, "Fields must not be null");
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = fields;
this.collation = collation;
@@ -2869,7 +2916,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
public FindIterable<Document> doInCollection(MongoCollection<Document> collection)
throws MongoException, DataAccessException {
FindIterable<Document> findIterable = collection.find(query, Document.class).projection(fields);
FindIterable<Document> findIterable = collectionPreparer.prepare(collection).find(query, Document.class)
.projection(fields);
if (collation != null) {
findIterable = findIterable.collation(collation);
@@ -2887,11 +2935,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
private class ExistsCallback implements CollectionCallback<Boolean> {
private final CollectionPreparer collectionPreparer;
private final Document mappedQuery;
private final com.mongodb.client.model.Collation collation;
ExistsCallback(Document mappedQuery, com.mongodb.client.model.Collation collation) {
ExistsCallback(CollectionPreparer collectionPreparer, Document mappedQuery,
com.mongodb.client.model.Collation collation) {
this.collectionPreparer = collectionPreparer;
this.mappedQuery = mappedQuery;
this.collation = collation;
}
@@ -2899,7 +2950,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@Override
public Boolean doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
return doCount(collection.getNamespace().getCollectionName(), mappedQuery,
return doCount(collectionPreparer, collection.getNamespace().getCollectionName(), mappedQuery,
new CountOptions().limit(1).collation(collation)) > 0;
}
}
@@ -2912,12 +2963,16 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
private static class FindAndRemoveCallback implements CollectionCallback<Document> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Document fields;
private final Document sort;
private final Optional<Collation> collation;
FindAndRemoveCallback(Document query, Document fields, Document sort, @Nullable Collation collation) {
FindAndRemoveCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query,
Document fields, Document sort,
@Nullable Collation collation) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = fields;
@@ -2931,12 +2986,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
FindOneAndDeleteOptions opts = new FindOneAndDeleteOptions().sort(sort).projection(fields);
collation.map(Collation::toMongoCollation).ifPresent(opts::collation);
return collection.findOneAndDelete(query, opts);
return collectionPreparer.prepare(collection).findOneAndDelete(query, opts);
}
}
private static class FindAndModifyCallback implements CollectionCallback<Document> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Document fields;
private final Document sort;
@@ -2944,9 +3000,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private final List<Document> arrayFilters;
private final FindAndModifyOptions options;
FindAndModifyCallback(Document query, Document fields, Document sort, Object update, List<Document> arrayFilters,
FindAndModifyOptions options) {
FindAndModifyCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query,
Document fields, Document sort,
Object update, List<Document> arrayFilters, FindAndModifyOptions options) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = fields;
this.sort = sort;
@@ -2975,9 +3033,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
}
if (update instanceof Document) {
return collection.findOneAndUpdate(query, (Document) update, opts);
return collectionPreparer.prepare(collection).findOneAndUpdate(query, (Document) update, opts);
} else if (update instanceof List) {
return collection.findOneAndUpdate(query, (List<Document>) update, opts);
return collectionPreparer.prepare(collection).findOneAndUpdate(query, (List<Document>) update, opts);
}
throw new IllegalArgumentException(String.format("Using %s is not supported in findOneAndUpdate", update));
@@ -2993,6 +3051,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
*/
private static class FindAndReplaceCallback implements CollectionCallback<Document> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Document fields;
private final Document sort;
@@ -3000,9 +3059,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
private final @Nullable com.mongodb.client.model.Collation collation;
private final FindAndReplaceOptions options;
FindAndReplaceCallback(Document query, Document fields, Document sort, Document update,
@Nullable com.mongodb.client.model.Collation collation, FindAndReplaceOptions options) {
FindAndReplaceCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query,
Document fields, Document sort,
Document update, @Nullable com.mongodb.client.model.Collation collation, FindAndReplaceOptions options) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = fields;
this.sort = sort;
@@ -3027,7 +3087,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
opts.returnDocument(ReturnDocument.AFTER);
}
return collection.findOneAndReplace(query, update, opts);
return collectionPreparer.prepare(collection).findOneAndReplace(query, update, opts);
}
}
@@ -3209,11 +3269,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
return cursorToUse;
}
@Override
public ReadPreference getReadPreference() {
return query.getMeta().getFlags().contains(CursorOption.SECONDARY_READS) ? ReadPreference.primaryPreferred()
: null;
}
}
/**
@@ -3399,6 +3454,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
@FunctionalInterface
interface CountExecution {
long countDocuments(String collection, Document filter, CountOptions options);
long countDocuments(CollectionPreparer collectionPreparer, String collection, Document filter,
CountOptions options);
}
}

View File

@@ -20,6 +20,7 @@ import reactor.core.publisher.Mono;
import org.bson.Document;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.mongodb.core.CollectionPreparerSupport.ReactiveCollectionPreparerDelegate;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.SerializationUtils;
@@ -67,8 +68,8 @@ class ReactiveFindOperationSupport implements ReactiveFindOperation {
private final String collection;
private final Query query;
ReactiveFindSupport(ReactiveMongoTemplate template, Class<?> domainType, Class<T> returnType,
String collection, Query query) {
ReactiveFindSupport(ReactiveMongoTemplate template, Class<?> domainType, Class<T> returnType, String collection,
Query query) {
this.template = template;
this.domainType = domainType;
@@ -169,8 +170,8 @@ class ReactiveFindOperationSupport implements ReactiveFindOperation {
Document queryObject = query.getQueryObject();
Document fieldsObject = query.getFieldsObject();
return template.doFind(getCollectionName(), queryObject, fieldsObject, domainType, returnType,
preparer != null ? preparer : getCursorPreparer(query));
return template.doFind(ReactiveCollectionPreparerDelegate.of(query), getCollectionName(), queryObject,
fieldsObject, domainType, returnType, preparer != null ? preparer : getCursorPreparer(query));
}
@SuppressWarnings("unchecked")

View File

@@ -70,6 +70,7 @@ import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.ReactiveMongoDatabaseUtils;
import org.springframework.data.mongodb.SessionSynchronization;
import org.springframework.data.mongodb.core.CollectionPreparerSupport.ReactiveCollectionPreparerDelegate;
import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
import org.springframework.data.mongodb.core.QueryOperations.AggregationDefinition;
import org.springframework.data.mongodb.core.QueryOperations.CountContext;
@@ -105,7 +106,6 @@ 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;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
@@ -756,8 +756,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public <T> Mono<T> findOne(Query query, Class<T> entityClass, String collectionName) {
if (ObjectUtils.isEmpty(query.getSortObject())) {
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
new QueryFindPublisherPreparer(query, entityClass));
return doFindOne(ReactiveCollectionPreparerDelegate.of(query), collectionName, query.getQueryObject(),
query.getFieldsObject(), entityClass, new QueryFindPublisherPreparer(query, entityClass));
}
query.limit(1);
@@ -783,10 +783,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return createFlux(collectionName, collection -> {
ReactiveCollectionPreparerDelegate collectionPreparer = ReactiveCollectionPreparerDelegate.of(query);
QueryContext queryContext = queryOperations.createQueryContext(query);
Document filter = queryContext.getMappedQuery(entityClass, this::getPersistentEntity);
FindPublisher<Document> findPublisher = collection.find(filter, Document.class)
FindPublisher<Document> findPublisher = collectionPreparer.prepare(collection).find(filter, Document.class)
.projection(new Document("_id", 1));
if (LOGGER.isDebugEnabled()) {
@@ -811,8 +812,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return findAll(entityClass, collectionName);
}
return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
new QueryFindPublisherPreparer(query, entityClass));
return doFind(ReactiveCollectionPreparerDelegate.of(query), collectionName, query.getQueryObject(),
query.getFieldsObject(), entityClass, new QueryFindPublisherPreparer(query, entityClass));
}
@Override
@@ -825,7 +826,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
String idKey = operations.getIdPropertyName(entityClass);
return doFindOne(collectionName, new Document(idKey, id), null, entityClass, (Collation) null);
return doFindOne(CollectionPreparer.identity(), collectionName, new Document(idKey, id), null, entityClass,
(Collation) null);
}
@Override
@@ -850,6 +852,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
Document mappedQuery = distinctQueryContext.getMappedQuery(entity);
String mappedFieldName = distinctQueryContext.getMappedFieldName(entity);
Class<T> mongoDriverCompatibleType = distinctQueryContext.getDriverCompatibleClass(resultClass);
ReactiveCollectionPreparerDelegate collectionPreparer = ReactiveCollectionPreparerDelegate.of(query);
Flux<?> result = execute(collectionName, collection -> {
@@ -859,11 +862,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
FindPublisherPreparer preparer = new QueryFindPublisherPreparer(query, entityClass);
if (preparer.hasReadPreference()) {
collection = collection.withReadPreference(preparer.getReadPreference());
}
DistinctPublisher<T> publisher = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
DistinctPublisher<T> publisher = collectionPreparer.prepare(collection).distinct(mappedFieldName, mappedQuery,
mongoDriverCompatibleType);
distinctQueryContext.applyCollation(entityClass, publisher::collation);
return publisher;
});
@@ -929,7 +930,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
boolean isOutOrMerge, AggregationOptions options, ReadDocumentCallback<O> readCallback,
@Nullable Class<?> inputType) {
AggregatePublisher<Document> cursor = collection.aggregate(pipeline, Document.class)
ReactiveCollectionPreparerDelegate collectionPreparer = ReactiveCollectionPreparerDelegate.of(options);
AggregatePublisher<Document> cursor = collectionPreparer.prepare(collection).aggregate(pipeline, Document.class)
.allowDiskUse(options.isAllowDiskUse());
if (options.getCursorBatchSize() != null) {
@@ -1028,8 +1030,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
operations.forType(entityClass).getCollation(query).ifPresent(optionsToUse::collation);
}
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
return doFindAndModify(ReactiveCollectionPreparerDelegate.of(query), collectionName, query.getQueryObject(),
query.getFieldsObject(), getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
}
@Override
@@ -1053,6 +1055,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
Document mappedQuery = queryContext.getMappedQuery(entity);
Document mappedFields = queryContext.getMappedFields(entity, projection);
Document mappedSort = queryContext.getMappedSort(entity);
ReactiveCollectionPreparerDelegate collectionPreparer = ReactiveCollectionPreparerDelegate.of(query);
return Mono.defer(() -> {
@@ -1070,8 +1073,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
mapped.getCollection()));
}).flatMap(it -> {
Mono<T> afterFindAndReplace = doFindAndReplace(it.getCollection(), mappedQuery, mappedFields, mappedSort,
queryContext.getCollation(entityType).orElse(null), entityType, it.getTarget(), options, projection);
Mono<T> afterFindAndReplace = doFindAndReplace(collectionPreparer, it.getCollection(), mappedQuery,
mappedFields, mappedSort, queryContext.getCollation(entityType).orElse(null), entityType, it.getTarget(),
options, projection);
return afterFindAndReplace.flatMap(saved -> {
maybeEmitEvent(new AfterSaveEvent<>(saved, it.getTarget(), it.getCollection()));
return maybeCallAfterSave(saved, it.getTarget(), it.getCollection());
@@ -1089,9 +1093,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public <T> Mono<T> findAndRemove(Query query, Class<T> entityClass, String collectionName) {
operations.forType(entityClass).getCollation(query);
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
getMappedSortObject(query, entityClass), operations.forType(entityClass).getCollation(query).orElse(null),
entityClass);
return doFindAndRemove(ReactiveCollectionPreparerDelegate.of(query), collectionName, query.getQueryObject(),
query.getFieldsObject(), getMappedSortObject(query, entityClass),
operations.forType(entityClass).getCollation(query).orElse(null), entityClass);
}
/*
@@ -1799,12 +1803,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, entityClass,
null, removeQuery);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
ReactiveCollectionPreparerDelegate collectionPreparer = ReactiveCollectionPreparerDelegate.of(query);
return execute(collectionName, collection -> {
maybeEmitEvent(new BeforeDeleteEvent<>(removeQuery, entityClass, collectionName));
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
MongoCollection<Document> collectionToUse = collectionPreparer
.prepare(prepareCollection(collection, writeConcernToUse));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("Remove using query: %s in collection: %s.", serializeToJsonSafely(removeQuery),
@@ -1839,8 +1845,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
@Override
public <T> Flux<T> findAll(Class<T> entityClass, String collectionName) {
return executeFindMultiInternal(new FindCallback(null), FindPublisherPreparer.NO_OP_PREPARER,
new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName);
return executeFindMultiInternal(new FindCallback(CollectionPreparer.identity(), null),
FindPublisherPreparer.NO_OP_PREPARER, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName),
collectionName);
}
@Override
@@ -1867,17 +1874,19 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
@Override
public <T> Flux<T> tail(@Nullable Query query, Class<T> entityClass, String collectionName) {
ReactiveCollectionPreparerDelegate collectionPreparer = ReactiveCollectionPreparerDelegate.of(query);
if (query == null) {
LOGGER.debug(String.format("Tail for class: %s in collection: %s", entityClass, collectionName));
return executeFindMultiInternal(
collection -> new FindCallback(null).doInCollection(collection).cursorType(CursorType.TailableAwait),
collection -> new FindCallback(collectionPreparer, null).doInCollection(collection)
.cursorType(CursorType.TailableAwait),
FindPublisherPreparer.NO_OP_PREPARER, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName),
collectionName);
}
return doFind(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
return doFind(collectionPreparer, collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
new TailingQueryFindPublisherPreparer(query, entityClass));
}
@@ -1961,12 +1970,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
assertLocalFunctionNames(mapFunction, reduceFunction);
ReactiveCollectionPreparerDelegate collectionPreparer = ReactiveCollectionPreparerDelegate.of(filterQuery);
return createFlux(inputCollectionName, collection -> {
Document mappedQuery = queryMapper.getMappedObject(filterQuery.getQueryObject(),
mappingContext.getPersistentEntity(domainType));
MapReducePublisher<Document> publisher = collection.mapReduce(mapFunction, reduceFunction, Document.class);
MapReducePublisher<Document> publisher = collectionPreparer.prepare(collection).mapReduce(mapFunction,
reduceFunction, Document.class);
publisher.filter(mappedQuery);
@@ -2139,10 +2150,11 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @param collation can be {@literal null}.
* @return the {@link List} of converted objects.
*/
protected <T> Mono<T> doFindOne(String collectionName, Document query, @Nullable Document fields,
Class<T> entityClass, @Nullable Collation collation) {
protected <T> Mono<T> doFindOne(CollectionPreparer<MongoCollection<Document>> collectionPreparer,
String collectionName, Document query, @Nullable Document fields, Class<T> entityClass,
@Nullable Collation collation) {
return doFindOne(collectionName, query, fields, entityClass,
return doFindOne(collectionPreparer, collectionName, query, fields, entityClass,
findPublisher -> collation != null ? findPublisher.collation(collation.toMongoCollation()) : findPublisher);
}
@@ -2158,8 +2170,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @return the {@link List} of converted objects.
* @since 2.2
*/
protected <T> Mono<T> doFindOne(String collectionName, Document query, @Nullable Document fields,
Class<T> entityClass, FindPublisherPreparer preparer) {
protected <T> Mono<T> doFindOne(CollectionPreparer<MongoCollection<Document>> collectionPreparer,
String collectionName, Document query, @Nullable Document fields, Class<T> entityClass,
FindPublisherPreparer preparer) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
@@ -2173,7 +2186,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
serializeToJsonSafely(query), mappedFields, entityClass, collectionName));
}
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, preparer),
return executeFindOneInternal(new FindOneCallback(collectionPreparer, mappedQuery, mappedFields, preparer),
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
}
@@ -2187,8 +2200,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @param entityClass the parameterized type of the returned list.
* @return the List of converted objects.
*/
protected <T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<T> entityClass) {
return doFind(collectionName, query, fields, entityClass, null,
protected <T> Flux<T> doFind(CollectionPreparer<MongoCollection<Document>> collectionPreparer, String collectionName,
Document query, Document fields, Class<T> entityClass) {
return doFind(collectionPreparer, collectionName, query, fields, entityClass, null,
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName));
}
@@ -2205,13 +2219,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* the result set, (apply limits, skips and so on).
* @return the {@link List} of converted objects.
*/
protected <T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<T> entityClass,
FindPublisherPreparer preparer) {
return doFind(collectionName, query, fields, entityClass, preparer,
protected <T> Flux<T> doFind(CollectionPreparer<MongoCollection<Document>> collectionPreparer, String collectionName,
Document query, Document fields, Class<T> entityClass, FindPublisherPreparer preparer) {
return doFind(collectionPreparer, collectionName, query, fields, entityClass, preparer,
new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName));
}
protected <S, T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<S> entityClass,
protected <S, T> Flux<T> doFind(CollectionPreparer<MongoCollection<Document>> collectionPreparer,
String collectionName, Document query, Document fields, Class<S> entityClass,
@Nullable FindPublisherPreparer preparer, DocumentCallback<T> objectCallback) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
@@ -2225,8 +2240,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName));
}
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer, objectCallback,
collectionName);
return executeFindMultiInternal(new FindCallback(collectionPreparer, mappedQuery, mappedFields), preparer,
objectCallback, collectionName);
}
/**
@@ -2235,8 +2250,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*
* @since 2.0
*/
<S, T> Flux<T> doFind(String collectionName, Document query, Document fields, Class<S> sourceClass,
Class<T> targetClass, FindPublisherPreparer preparer) {
<S, T> Flux<T> doFind(CollectionPreparer<MongoCollection<Document>> collectionPreparer, String collectionName,
Document query, Document fields, Class<S> sourceClass, Class<T> targetClass, FindPublisherPreparer preparer) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(sourceClass);
EntityProjection<T, S> projection = operations.introspectProjection(targetClass, sourceClass);
@@ -2250,7 +2265,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
serializeToJsonSafely(mappedQuery), mappedFields, sourceClass, collectionName));
}
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer,
return executeFindMultiInternal(new FindCallback(collectionPreparer, mappedQuery, mappedFields), preparer,
new ProjectingReadCallback<>(mongoConverter, projection, collectionName), collectionName);
}
@@ -2274,8 +2289,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @param entityClass the parameterized type of the returned list.
* @return the List of converted objects.
*/
protected <T> Mono<T> doFindAndRemove(String collectionName, Document query, Document fields, Document sort,
@Nullable Collation collation, Class<T> entityClass) {
protected <T> Mono<T> doFindAndRemove(CollectionPreparer<MongoCollection<Document>> collectionPreparer,
String collectionName, Document query, Document fields, Document sort, @Nullable Collation collation,
Class<T> entityClass) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(String.format("findAndRemove using query: %s fields: %s sort: %s for class: %s in collection: %s",
@@ -2284,13 +2300,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
return executeFindOneInternal(
new FindAndRemoveCallback(queryMapper.getMappedObject(query, entity), fields, sort, collation),
return executeFindOneInternal(new FindAndRemoveCallback(collectionPreparer,
queryMapper.getMappedObject(query, entity), fields, sort, collation),
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
}
protected <T> Mono<T> doFindAndModify(String collectionName, Document query, Document fields, Document sort,
Class<T> entityClass, UpdateDefinition update, FindAndModifyOptions options) {
protected <T> Mono<T> doFindAndModify(CollectionPreparer<MongoCollection<Document>> collectionPreparer,
String collectionName, Document query, Document fields, Document sort, Class<T> entityClass,
UpdateDefinition update, FindAndModifyOptions options) {
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
UpdateContext updateContext = queryOperations.updateSingleContext(update, query, false);
@@ -2310,7 +2327,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
}
return executeFindOneInternal(
new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate,
new FindAndModifyCallback(collectionPreparer, mappedQuery, fields, sort, mappedUpdate,
update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()), options),
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
});
@@ -2332,14 +2349,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
* @since 2.1
*/
protected <T> Mono<T> doFindAndReplace(String collectionName, Document mappedQuery, Document mappedFields,
Document mappedSort, com.mongodb.client.model.Collation collation, Class<?> entityType, Document replacement,
protected <T> Mono<T> doFindAndReplace(CollectionPreparer<MongoCollection<Document>> collectionPreparer,
String collectionName, Document mappedQuery, Document mappedFields, Document mappedSort,
com.mongodb.client.model.Collation collation, Class<?> entityType, Document replacement,
FindAndReplaceOptions options, Class<T> resultType) {
EntityProjection<T, ?> projection = operations.introspectProjection(resultType, entityType);
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort, collation, entityType, replacement,
options, projection);
return doFindAndReplace(collectionPreparer, collectionName, mappedQuery, mappedFields, mappedSort, collation,
entityType, replacement, options, projection);
}
/**
@@ -2358,8 +2376,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* {@literal false} and {@link FindAndReplaceOptions#isUpsert() upsert} is {@literal false}.
* @since 3.4
*/
private <T> Mono<T> doFindAndReplace(String collectionName, Document mappedQuery, Document mappedFields,
Document mappedSort, com.mongodb.client.model.Collation collation, Class<?> entityType, Document replacement,
private <T> Mono<T> doFindAndReplace(CollectionPreparer<MongoCollection<Document>> collectionPreparer,
String collectionName, Document mappedQuery, Document mappedFields, Document mappedSort,
com.mongodb.client.model.Collation collation, Class<?> entityType, Document replacement,
FindAndReplaceOptions options, EntityProjection<T, ?> projection) {
return Mono.defer(() -> {
@@ -2372,8 +2391,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
serializeToJsonSafely(replacement), collectionName));
}
return executeFindOneInternal(
new FindAndReplaceCallback(mappedQuery, mappedFields, mappedSort, replacement, collation, options),
return executeFindOneInternal(new FindAndReplaceCallback(collectionPreparer, mappedQuery, mappedFields,
mappedSort, replacement, collation, options),
new ProjectingReadCallback<>(this.mongoConverter, projection, collectionName), collectionName);
});
@@ -2451,7 +2470,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
* @param collection
*/
protected MongoCollection<Document> prepareCollection(MongoCollection<Document> collection) {
return this.readPreference != null ? collection.withReadPreference(readPreference) : collection;
if (this.readPreference != null && this.readPreference != collection.getReadPreference()) {
return collection.withReadPreference(readPreference);
}
return collection;
}
/**
@@ -2621,11 +2645,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
private static class FindOneCallback implements ReactiveCollectionCallback<Document> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Optional<Document> fields;
private final FindPublisherPreparer preparer;
FindOneCallback(Document query, @Nullable Document fields, FindPublisherPreparer preparer) {
FindOneCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query,
@Nullable Document fields, FindPublisherPreparer preparer) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = Optional.ofNullable(fields);
this.preparer = preparer;
@@ -2642,7 +2669,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
serializeToJsonSafely(fields.orElseGet(Document::new)), collection.getNamespace().getFullName()));
}
FindPublisher<Document> publisher = preparer.initiateFind(collection, col -> col.find(query, Document.class));
FindPublisher<Document> publisher = preparer.initiateFind(collectionPreparer.prepare(collection),
col -> col.find(query, Document.class));
if (fields.isPresent()) {
publisher = publisher.projection(fields.get());
@@ -2660,15 +2688,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
private static class FindCallback implements ReactiveCollectionQueryCallback<Document> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final @Nullable Document query;
private final @Nullable Document fields;
FindCallback(@Nullable Document query) {
this(query, null);
FindCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, @Nullable Document query) {
this(collectionPreparer, query, null);
}
FindCallback(Document query, Document fields) {
FindCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query, Document fields) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = fields;
}
@@ -2676,11 +2706,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
@Override
public FindPublisher<Document> doInCollection(MongoCollection<Document> collection) {
MongoCollection<Document> collectionToUse = collectionPreparer.prepare(collection);
FindPublisher<Document> findPublisher;
if (ObjectUtils.isEmpty(query)) {
findPublisher = collection.find(Document.class);
findPublisher = collectionToUse.find(Document.class);
} else {
findPublisher = collection.find(query, Document.class);
findPublisher = collectionToUse.find(query, Document.class);
}
if (ObjectUtils.isEmpty(fields)) {
@@ -2699,13 +2730,15 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
private static class FindAndRemoveCallback implements ReactiveCollectionCallback<Document> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Document fields;
private final Document sort;
private final Optional<Collation> collation;
FindAndRemoveCallback(Document query, Document fields, Document sort, @Nullable Collation collation) {
FindAndRemoveCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query,
Document fields, Document sort, @Nullable Collation collation) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = fields;
this.sort = sort;
@@ -2719,7 +2752,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
FindOneAndDeleteOptions findOneAndDeleteOptions = convertToFindOneAndDeleteOptions(fields, sort);
collation.map(Collation::toMongoCollation).ifPresent(findOneAndDeleteOptions::collation);
return collection.findOneAndDelete(query, findOneAndDeleteOptions);
return collectionPreparer.prepare(collection).findOneAndDelete(query, findOneAndDeleteOptions);
}
}
@@ -2728,6 +2761,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
private static class FindAndModifyCallback implements ReactiveCollectionCallback<Document> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Document fields;
private final Document sort;
@@ -2735,9 +2769,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private final List<Document> arrayFilters;
private final FindAndModifyOptions options;
FindAndModifyCallback(Document query, Document fields, Document sort, Object update, List<Document> arrayFilters,
FindAndModifyOptions options) {
FindAndModifyCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query,
Document fields, Document sort, Object update, List<Document> arrayFilters, FindAndModifyOptions options) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = fields;
this.sort = sort;
@@ -2750,21 +2785,22 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
public Publisher<Document> doInCollection(MongoCollection<Document> collection)
throws MongoException, DataAccessException {
MongoCollection<Document> collectionToUse = collectionPreparer.prepare(collection);
if (options.isRemove()) {
FindOneAndDeleteOptions findOneAndDeleteOptions = convertToFindOneAndDeleteOptions(fields, sort);
findOneAndDeleteOptions = options.getCollation().map(Collation::toMongoCollation)
.map(findOneAndDeleteOptions::collation).orElse(findOneAndDeleteOptions);
return collection.findOneAndDelete(query, findOneAndDeleteOptions);
return collectionToUse.findOneAndDelete(query, findOneAndDeleteOptions);
}
FindOneAndUpdateOptions findOneAndUpdateOptions = convertToFindOneAndUpdateOptions(options, fields, sort,
arrayFilters);
if (update instanceof Document) {
return collection.findOneAndUpdate(query, (Document) update, findOneAndUpdateOptions);
return collectionToUse.findOneAndUpdate(query, (Document) update, findOneAndUpdateOptions);
} else if (update instanceof List) {
return collection.findOneAndUpdate(query, (List<Document>) update, findOneAndUpdateOptions);
return collectionToUse.findOneAndUpdate(query, (List<Document>) update, findOneAndUpdateOptions);
}
return Flux
@@ -2803,6 +2839,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
*/
private static class FindAndReplaceCallback implements ReactiveCollectionCallback<Document> {
private final CollectionPreparer<MongoCollection<Document>> collectionPreparer;
private final Document query;
private final Document fields;
private final Document sort;
@@ -2810,9 +2847,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
private final @Nullable com.mongodb.client.model.Collation collation;
private final FindAndReplaceOptions options;
FindAndReplaceCallback(Document query, Document fields, Document sort, Document update,
com.mongodb.client.model.Collation collation, FindAndReplaceOptions options) {
FindAndReplaceCallback(CollectionPreparer<MongoCollection<Document>> collectionPreparer, Document query,
Document fields, Document sort, Document update, com.mongodb.client.model.Collation collation,
FindAndReplaceOptions options) {
this.collectionPreparer = collectionPreparer;
this.query = query;
this.fields = fields;
this.sort = sort;
@@ -2826,7 +2864,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
throws MongoException, DataAccessException {
FindOneAndReplaceOptions findOneAndReplaceOptions = convertToFindOneAndReplaceOptions(options, fields, sort);
return collection.findOneAndReplace(query, update, findOneAndReplaceOptions);
return collectionPreparer.prepare(collection).findOneAndReplace(query, update, findOneAndReplaceOptions);
}
private FindOneAndReplaceOptions convertToFindOneAndReplaceOptions(FindAndReplaceOptions options, Document fields,
@@ -3092,11 +3130,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return findPublisherToUse;
}
@Override
public ReadPreference getReadPreference() {
return query.getMeta().getFlags().contains(CursorOption.SECONDARY_READS) ? ReadPreference.primaryPreferred()
: null;
}
}
class TailingQueryFindPublisherPreparer extends QueryFindPublisherPreparer {

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import org.springframework.lang.Nullable;
import com.mongodb.ReadConcern;
/**
* Interface to be implemented by any object that wishes to expose the {@link ReadConcern}.
* <p>
* Typically implemented by cursor or query preparer objects.
*
* @author Mark Paluch
* @since 4.1
*/
public interface ReadConcernAware {
/**
* @return {@literal true} if a {@link ReadConcern} is set.
*/
default boolean hasReadConcern() {
return getReadConcern() != null;
}
/**
* @return the {@link ReadConcern} to apply or {@literal null} if none set.
*/
@Nullable
ReadConcern getReadConcern();
}

View File

@@ -19,11 +19,16 @@ import java.time.Duration;
import java.util.Optional;
import org.bson.Document;
import org.springframework.data.mongodb.core.ReadConcernAware;
import org.springframework.data.mongodb.core.ReadPreferenceAware;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.mongodb.ReadConcern;
import com.mongodb.ReadPreference;
/**
* Holds a set of configurable aggregation options that can be used within an aggregation pipeline. A list of support
* aggregation options can be found in the MongoDB reference documentation
@@ -39,7 +44,7 @@ import org.springframework.util.Assert;
* @see TypedAggregation#withOptions(AggregationOptions)
* @since 1.6
*/
public class AggregationOptions {
public class AggregationOptions implements ReadConcernAware, ReadPreferenceAware {
private static final String BATCH_SIZE = "batchSize";
private static final String CURSOR = "cursor";
@@ -56,6 +61,10 @@ public class AggregationOptions {
private final Optional<Collation> collation;
private final Optional<String> comment;
private final Optional<Object> hint;
private Optional<ReadConcern> readConcern;
private Optional<ReadPreference> readPreference;
private Duration maxTime = Duration.ZERO;
private ResultOptions resultOptions = ResultOptions.READ;
private DomainTypeMapping domainTypeMapping = DomainTypeMapping.RELAXED;
@@ -123,6 +132,8 @@ public class AggregationOptions {
this.collation = Optional.ofNullable(collation);
this.comment = Optional.ofNullable(comment);
this.hint = Optional.ofNullable(hint);
this.readConcern = Optional.empty();
this.readPreference = Optional.empty();
}
/**
@@ -268,6 +279,26 @@ public class AggregationOptions {
return hint;
}
@Override
public boolean hasReadConcern() {
return readConcern.isPresent();
}
@Override
public ReadConcern getReadConcern() {
return readConcern.orElse(null);
}
@Override
public boolean hasReadPreference() {
return readPreference.isPresent();
}
@Override
public ReadPreference getReadPreference() {
return readPreference.orElse(null);
}
/**
* @return the time limit for processing. {@link Duration#ZERO} is used for the default unbounded behavior.
* @since 3.0
@@ -385,6 +416,8 @@ public class AggregationOptions {
private @Nullable Collation collation;
private @Nullable String comment;
private @Nullable Object hint;
private @Nullable ReadConcern readConcern;
private @Nullable ReadPreference readPreference;
private @Nullable Duration maxTime;
private @Nullable ResultOptions resultOptions;
private @Nullable DomainTypeMapping domainTypeMapping;
@@ -490,6 +523,32 @@ public class AggregationOptions {
return this;
}
/**
* Define a {@link ReadConcern} to apply to the aggregation.
*
* @param readConcern can be {@literal null}.
* @return this.
* @since 4.1
*/
public Builder readConcern(@Nullable ReadConcern readConcern) {
this.readConcern = readConcern;
return this;
}
/**
* Define a {@link ReadPreference} to apply to the aggregation.
*
* @param readPreference can be {@literal null}.
* @return this.
* @since 4.1
*/
public Builder readPreference(@Nullable ReadPreference readPreference) {
this.readPreference = readPreference;
return this;
}
/**
* Set the time limit for processing.
*
@@ -573,6 +632,12 @@ public class AggregationOptions {
if (domainTypeMapping != null) {
options.domainTypeMapping = domainTypeMapping;
}
if (readConcern != null) {
options.readConcern = Optional.of(readConcern);
}
if (readPreference != null) {
options.readPreference = Optional.of(readPreference);
}
return options;
}

View File

@@ -536,6 +536,11 @@ public final class NearQuery {
return this;
}
@Nullable
public Query getQuery() {
return query;
}
/**
* @return the number of elements to skip.
*/

View File

@@ -34,10 +34,16 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
import org.springframework.data.mongodb.core.ReadConcernAware;
import org.springframework.data.mongodb.core.ReadPreferenceAware;
import org.springframework.data.mongodb.core.query.Meta.CursorOption;
import org.springframework.data.mongodb.util.BsonUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.mongodb.ReadConcern;
import com.mongodb.ReadPreference;
/**
* MongoDB Query object representing criteria, projection, sorting and query hints.
*
@@ -48,7 +54,7 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @author Anton Barkan
*/
public class Query {
public class Query implements ReadConcernAware, ReadPreferenceAware {
private static final String RESTRICTED_TYPES_KEY = "_$RESTRICTED_TYPES";
@@ -58,6 +64,9 @@ public class Query {
private Sort sort = Sort.unsorted();
private long skip;
private int limit;
private @Nullable ReadConcern readConcern;
private @Nullable ReadPreference readPreference;
private @Nullable String hint;
private Meta meta = new Meta();
@@ -160,6 +169,59 @@ public class Query {
return this;
}
/**
* Configures the query to use the given {@link ReadConcern} when being executed.
*
* @param readConcern must not be {@literal null}.
* @return this.
* @since 3.1
*/
public Query withReadConcern(ReadConcern readConcern) {
Assert.notNull(readConcern, "ReadConcern must not be null");
this.readConcern = readConcern;
return this;
}
/**
* Configures the query to use the given {@link ReadPreference} when being executed.
*
* @param readPreference must not be {@literal null}.
* @return this.
* @since 4.1
*/
public Query withReadPreference(ReadPreference readPreference) {
Assert.notNull(readPreference, "ReadPreference must not be null");
this.readPreference = readPreference;
return this;
}
@Override
public boolean hasReadConcern() {
return this.readConcern != null;
}
@Override
public ReadConcern getReadConcern() {
return this.readConcern;
}
@Override
public boolean hasReadPreference() {
return this.readPreference != null || getMeta().getFlags().contains(CursorOption.SECONDARY_READS);
}
@Override
public ReadPreference getReadPreference() {
if (readPreference == null) {
return getMeta().getFlags().contains(CursorOption.SECONDARY_READS) ? ReadPreference.primaryPreferred() : null;
}
return this.readPreference;
}
/**
* Configures the query to use the given {@link Document hint} when being executed.
*

View File

@@ -64,6 +64,7 @@ class QueryUtils {
combinedSort.putAll((Document) invocation.proceed());
return combinedSort;
});
factory.setInterfaces(new Class[0]);
return (Query) factory.getProxy(query.getClass().getClassLoader());
}
@@ -113,7 +114,7 @@ class QueryUtils {
if(parameters.isEmpty()) {
return -1;
}
int i = 0;
for(Class<?> parameterType : parameters) {
if(ClassUtils.isAssignable(type, parameterType)) {

View File

@@ -108,6 +108,7 @@ import org.springframework.util.CollectionUtils;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoException;
import com.mongodb.MongoNamespace;
import com.mongodb.ReadConcern;
import com.mongodb.ReadPreference;
import com.mongodb.ServerAddress;
import com.mongodb.ServerCursor;
@@ -120,16 +121,7 @@ import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.model.FindOneAndReplaceOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.MapReduceAction;
import com.mongodb.client.model.ReplaceOptions;
import com.mongodb.client.model.TimeSeriesGranularity;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.model.*;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
@@ -182,6 +174,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
when(collection.estimatedDocumentCount(any())).thenReturn(1L);
when(collection.getNamespace()).thenReturn(new MongoNamespace("db.mock-collection"));
when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable);
when(collection.withReadConcern(any())).thenReturn(collection);
when(collection.withReadPreference(any())).thenReturn(collection);
when(collection.replaceOne(any(), any(), any(ReplaceOptions.class))).thenReturn(updateResult);
when(collection.withWriteConcern(any())).thenReturn(collectionWithWriteConcern);
@@ -478,6 +471,34 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(collection, never()).withReadPreference(any());
}
@Test // GH-4277
void aggregateShouldHonorOptionsReadConcernWhenSet() {
AggregationOptions options = AggregationOptions.builder().readConcern(ReadConcern.SNAPSHOT).build();
template.aggregate(newAggregation(Aggregation.unwind("foo")).withOptions(options), "collection-1", Wrapper.class);
verify(collection).withReadConcern(ReadConcern.SNAPSHOT);
}
@Test // GH-4277
void aggregateShouldHonorOptionsReadPreferenceWhenSet() {
AggregationOptions options = AggregationOptions.builder().readPreference(ReadPreference.secondary()).build();
template.aggregate(newAggregation(Aggregation.unwind("foo")).withOptions(options), "collection-1", Wrapper.class);
verify(collection).withReadPreference(ReadPreference.secondary());
}
@Test // GH-4277
void aggregateStreamShouldHonorOptionsReadPreferenceWhenSet() {
AggregationOptions options = AggregationOptions.builder().readPreference(ReadPreference.secondary()).build();
template.aggregateStream(newAggregation(Aggregation.unwind("foo")).withOptions(options), "collection-1",
Wrapper.class);
verify(collection).withReadPreference(ReadPreference.secondary());
}
@Test // DATAMONGO-2153
void aggregateShouldHonorOptionsComment() {
@@ -558,6 +579,19 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(collection).withReadPreference(eq(ReadPreference.secondary()));
}
@Test // GH-4277
void geoNearShouldHonorReadPreferenceFromQuery() {
NearQuery query = NearQuery.near(new Point(1, 1));
Query inner = new Query();
inner.withReadPreference(ReadPreference.secondary());
query.query(inner);
template.geoNear(query, Wrapper.class);
verify(collection).withReadPreference(eq(ReadPreference.secondary()));
}
@Test // DATAMONGO-1166, DATAMONGO-2264
void geoNearShouldIgnoreReadPreferenceWhenNotSet() {
@@ -802,6 +836,24 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
verify(findIterable).batchSize(1234);
}
@Test // GH-4277
void findShouldUseReadConcernWhenPresent() {
template.find(new BasicQuery("{'foo' : 'bar'}").withReadConcern(ReadConcern.SNAPSHOT),
AutogenerateableId.class);
verify(collection).withReadConcern(ReadConcern.SNAPSHOT);
}
@Test // GH-4277
void findShouldUseReadPreferenceWhenPresent() {
template.find(new BasicQuery("{'foo' : 'bar'}").withReadPreference(ReadPreference.secondary()),
AutogenerateableId.class);
verify(collection).withReadPreference(ReadPreference.secondary());
}
@Test // DATAMONGO-1518
void executeQueryShouldUseCollationWhenPresent() {
@@ -1048,7 +1100,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-1733
void appliesFieldsWhenInterfaceProjectionIsClosedAndQueryDoesNotDefineFields() {
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonProjection.class,
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
PersonProjection.class,
CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(new Document("firstname", 1)));
@@ -1057,7 +1110,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-1733
void doesNotApplyFieldsWhenInterfaceProjectionIsClosedAndQueryDefinesFields() {
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, PersonProjection.class,
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document("bar", 1), Person.class,
PersonProjection.class,
CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(new Document("bar", 1)));
@@ -1066,7 +1120,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-1733
void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() {
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonSpELProjection.class,
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
PersonSpELProjection.class,
CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
@@ -1075,7 +1130,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-1733, DATAMONGO-2041
void appliesFieldsToDtoProjection() {
template.doFind("star-wars", new Document(), new Document(), Person.class, Jedi.class,
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
Jedi.class,
CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(new Document("firstname", 1)));
@@ -1084,7 +1140,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-1733
void doesNotApplyFieldsToDtoProjectionWhenQueryDefinesFields() {
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, Jedi.class,
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document("bar", 1), Person.class,
Jedi.class,
CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(new Document("bar", 1)));
@@ -1093,7 +1150,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-1733
void doesNotApplyFieldsWhenTargetIsNotAProjection() {
template.doFind("star-wars", new Document(), new Document(), Person.class, Person.class,
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
Person.class,
CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));
@@ -1102,7 +1160,8 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Test // DATAMONGO-1733
void doesNotApplyFieldsWhenTargetExtendsDomainType() {
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonExtended.class,
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
PersonExtended.class,
CursorPreparer.NO_OP_PREPARER);
verify(findIterable).projection(eq(BsonUtils.EMPTY_DOCUMENT));

View File

@@ -23,8 +23,6 @@ import static org.springframework.data.mongodb.test.util.Assertions.assertThat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.mongodb.core.MongoTemplateUnitTests.Wrapper;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -56,7 +54,6 @@ import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
@@ -100,6 +97,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.CollectionUtils;
import com.mongodb.MongoClientSettings;
import com.mongodb.ReadConcern;
import com.mongodb.ReadPreference;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.CreateCollectionOptions;
@@ -168,6 +166,7 @@ public class ReactiveMongoTemplateUnitTests {
when(db.runCommand(any(), any(Class.class))).thenReturn(runCommandPublisher);
when(db.createCollection(any(), any(CreateCollectionOptions.class))).thenReturn(runCommandPublisher);
when(collection.withReadPreference(any())).thenReturn(collection);
when(collection.withReadConcern(any())).thenReturn(collection);
when(collection.find(any(Class.class))).thenReturn(findPublisher);
when(collection.find(any(Document.class), any(Class.class))).thenReturn(findPublisher);
when(collection.aggregate(anyList())).thenReturn(aggregatePublisher);
@@ -388,8 +387,8 @@ public class ReactiveMongoTemplateUnitTests {
@Test // DATAMONGO-1719
void appliesFieldsWhenInterfaceProjectionIsClosedAndQueryDoesNotDefineFields() {
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonProjection.class,
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
PersonProjection.class, FindPublisherPreparer.NO_OP_PREPARER).subscribe();
verify(findPublisher).projection(eq(new Document("firstname", 1)));
}
@@ -397,8 +396,8 @@ public class ReactiveMongoTemplateUnitTests {
@Test // DATAMONGO-1719
void doesNotApplyFieldsWhenInterfaceProjectionIsClosedAndQueryDefinesFields() {
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, PersonProjection.class,
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document("bar", 1), Person.class,
PersonProjection.class, FindPublisherPreparer.NO_OP_PREPARER).subscribe();
verify(findPublisher).projection(eq(new Document("bar", 1)));
}
@@ -406,8 +405,8 @@ public class ReactiveMongoTemplateUnitTests {
@Test // DATAMONGO-1719
void doesNotApplyFieldsWhenInterfaceProjectionIsOpen() {
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonSpELProjection.class,
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
PersonSpELProjection.class, FindPublisherPreparer.NO_OP_PREPARER).subscribe();
verify(findPublisher, never()).projection(any());
}
@@ -415,8 +414,8 @@ public class ReactiveMongoTemplateUnitTests {
@Test // DATAMONGO-1719, DATAMONGO-2041
void appliesFieldsToDtoProjection() {
template.doFind("star-wars", new Document(), new Document(), Person.class, Jedi.class,
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
Jedi.class, FindPublisherPreparer.NO_OP_PREPARER).subscribe();
verify(findPublisher).projection(eq(new Document("firstname", 1)));
}
@@ -424,8 +423,8 @@ public class ReactiveMongoTemplateUnitTests {
@Test // DATAMONGO-1719
void doesNotApplyFieldsToDtoProjectionWhenQueryDefinesFields() {
template.doFind("star-wars", new Document(), new Document("bar", 1), Person.class, Jedi.class,
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document("bar", 1), Person.class,
Jedi.class, FindPublisherPreparer.NO_OP_PREPARER).subscribe();
verify(findPublisher).projection(eq(new Document("bar", 1)));
}
@@ -433,8 +432,8 @@ public class ReactiveMongoTemplateUnitTests {
@Test // DATAMONGO-1719
void doesNotApplyFieldsWhenTargetIsNotAProjection() {
template.doFind("star-wars", new Document(), new Document(), Person.class, Person.class,
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
Person.class, FindPublisherPreparer.NO_OP_PREPARER).subscribe();
verify(findPublisher, never()).projection(any());
}
@@ -442,8 +441,8 @@ public class ReactiveMongoTemplateUnitTests {
@Test // DATAMONGO-1719
void doesNotApplyFieldsWhenTargetExtendsDomainType() {
template.doFind("star-wars", new Document(), new Document(), Person.class, PersonExtended.class,
FindPublisherPreparer.NO_OP_PREPARER).subscribe();
template.doFind(CollectionPreparer.identity(), "star-wars", new Document(), new Document(), Person.class,
PersonExtended.class, FindPublisherPreparer.NO_OP_PREPARER).subscribe();
verify(findPublisher, never()).projection(any());
}
@@ -632,6 +631,26 @@ public class ReactiveMongoTemplateUnitTests {
verify(aggregatePublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
}
@Test // GH-4277
void aggreateShouldUseReadConcern() {
AggregationOptions options = AggregationOptions.builder().readConcern(ReadConcern.SNAPSHOT).build();
template.aggregate(newAggregation(Sith.class, project("id")).withOptions(options), AutogenerateableId.class,
Document.class).subscribe();
verify(collection).withReadConcern(ReadConcern.SNAPSHOT);
}
@Test // GH-4286
void aggreateShouldUseReadReadPreference() {
AggregationOptions options = AggregationOptions.builder().readPreference(ReadPreference.primaryPreferred()).build();
template.aggregate(newAggregation(Sith.class, project("id")).withOptions(options), AutogenerateableId.class,
Document.class).subscribe();
verify(collection).withReadPreference(ReadPreference.primaryPreferred());
}
@Test // DATAMONGO-1854
void aggreateShouldUseCollationFromOptionsEvenIfDefaultCollationIsPresent() {

View File

@@ -342,7 +342,10 @@ class QueryTests {
source.limit(10);
source.setSortObject(new Document("_id", 1));
Query target = Query.of((Query) new ProxyFactory(source).getProxy());
ProxyFactory proxyFactory = new ProxyFactory(source);
proxyFactory.setInterfaces(new Class[0]);
Query target = Query.of((Query) proxyFactory.getProxy());
compareQueries(target, source);
}