DATAMONGO-1854 - Allow Collation to be configured on entity level.
The collation can now also be configured on entity level and gets applied via MongoTemplate. However one can alway override a default collation by adding the collation explicitly to either the Query or one of the Options available for various operations. When it comes to the repository level the hierarchy is method parameter over query annotation over entity metadata. Remove collation annotation in favor of attributes of Query / Document. Original pull request: #644.
This commit is contained in:
committed by
Mark Paluch
parent
8b7071e6ae
commit
ac1873a163
@@ -120,19 +120,14 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
|
||||
return execute(collection -> {
|
||||
|
||||
Document indexOptions = indexDefinition.getIndexOptions();
|
||||
MongoPersistentEntity<?> entity = lookupPersistentEntity(type, collectionName);
|
||||
|
||||
IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
|
||||
IndexOptions indexOptions = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
|
||||
|
||||
if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
|
||||
indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity);
|
||||
indexOptions = addDefaultCollationIfRequired(indexOptions, entity);
|
||||
|
||||
Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
|
||||
|
||||
ops.partialFilterExpression(mapper.getMappedObject((Document) indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY),
|
||||
lookupPersistentEntity(type, collectionName)));
|
||||
}
|
||||
|
||||
return collection.createIndex(indexDefinition.getIndexKeys(), ops);
|
||||
return collection.createIndex(indexDefinition.getIndexKeys(), indexOptions);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,7 +187,7 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
|
||||
private List<IndexInfo> getIndexData(MongoCursor<Document> cursor) {
|
||||
|
||||
List<IndexInfo> indexInfoList = new ArrayList<IndexInfo>();
|
||||
List<IndexInfo> indexInfoList = new ArrayList<>();
|
||||
|
||||
while (cursor.hasNext()) {
|
||||
|
||||
@@ -217,4 +212,25 @@ public class DefaultIndexOperations implements IndexOperations {
|
||||
|
||||
return mongoOperations.execute(collectionName, callback);
|
||||
}
|
||||
|
||||
private IndexOptions addPartialFilterIfPresent(IndexOptions ops, Document sourceOptions,
|
||||
@Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (!sourceOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
|
||||
return ops;
|
||||
}
|
||||
|
||||
Assert.isInstanceOf(Document.class, sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
|
||||
return ops.partialFilterExpression(
|
||||
mapper.getMappedObject((Document) sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), entity));
|
||||
}
|
||||
|
||||
private static IndexOptions addDefaultCollationIfRequired(IndexOptions ops, MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (ops.getCollation() != null || entity == null || !entity.hasCollation()) {
|
||||
return ops;
|
||||
}
|
||||
|
||||
return ops.collation(entity.getCollation().toMongoCollation());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,23 +94,16 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
|
||||
|
||||
return mongoOperations.execute(collectionName, collection -> {
|
||||
|
||||
Document indexOptions = indexDefinition.getIndexOptions();
|
||||
MongoPersistentEntity<?> entity = type
|
||||
.map(val -> (MongoPersistentEntity) queryMapper.getMappingContext().getRequiredPersistentEntity(val))
|
||||
.orElseGet(() -> lookupPersistentEntity(collectionName));
|
||||
|
||||
IndexOptions ops = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
|
||||
IndexOptions indexOptions = IndexConverters.indexDefinitionToIndexOptionsConverter().convert(indexDefinition);
|
||||
|
||||
if (indexOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
|
||||
indexOptions = addPartialFilterIfPresent(indexOptions, indexDefinition.getIndexOptions(), entity);
|
||||
indexOptions = addDefaultCollationIfRequired(indexOptions, entity);
|
||||
|
||||
Assert.isInstanceOf(Document.class, indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
|
||||
|
||||
MongoPersistentEntity<?> entity = type
|
||||
.map(val -> (MongoPersistentEntity) queryMapper.getMappingContext().getRequiredPersistentEntity(val))
|
||||
.orElseGet(() -> lookupPersistentEntity(collectionName));
|
||||
|
||||
ops = ops.partialFilterExpression(
|
||||
queryMapper.getMappedObject(indexOptions.get(PARTIAL_FILTER_EXPRESSION_KEY, Document.class), entity));
|
||||
}
|
||||
|
||||
return collection.createIndex(indexDefinition.getIndexKeys(), ops);
|
||||
return collection.createIndex(indexDefinition.getIndexKeys(), indexOptions);
|
||||
|
||||
}).next();
|
||||
}
|
||||
@@ -126,21 +119,24 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#dropIndex(java.lang.String)
|
||||
*/
|
||||
public Mono<Void> dropIndex(final String name) {
|
||||
return mongoOperations.execute(collectionName, collection -> collection.dropIndex(name)).then();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#dropAllIndexes()
|
||||
*/
|
||||
public Mono<Void> dropAllIndexes() {
|
||||
return dropIndex("*");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.index.ReactiveIndexOperations#getIndexInfo()
|
||||
*/
|
||||
public Flux<IndexInfo> getIndexInfo() {
|
||||
@@ -148,4 +144,25 @@ public class DefaultReactiveIndexOperations implements ReactiveIndexOperations {
|
||||
return mongoOperations.execute(collectionName, collection -> collection.listIndexes(Document.class)) //
|
||||
.map(IndexConverters.documentToIndexInfoConverter()::convert);
|
||||
}
|
||||
|
||||
private IndexOptions addPartialFilterIfPresent(IndexOptions ops, Document sourceOptions,
|
||||
@Nullable MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (!sourceOptions.containsKey(PARTIAL_FILTER_EXPRESSION_KEY)) {
|
||||
return ops;
|
||||
}
|
||||
|
||||
Assert.isInstanceOf(Document.class, sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY));
|
||||
return ops.partialFilterExpression(
|
||||
queryMapper.getMappedObject((Document) sourceOptions.get(PARTIAL_FILTER_EXPRESSION_KEY), entity));
|
||||
}
|
||||
|
||||
private static IndexOptions addDefaultCollationIfRequired(IndexOptions ops, MongoPersistentEntity<?> entity) {
|
||||
|
||||
if (ops.getCollation() != null || entity == null || !entity.hasCollation()) {
|
||||
return ops;
|
||||
}
|
||||
|
||||
return ops.collation(entity.getCollation().toMongoCollation());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,31 @@ public class FindAndModifyOptions {
|
||||
|
||||
private @Nullable Collation collation;
|
||||
|
||||
private static final FindAndModifyOptions NONE = new FindAndModifyOptions() {
|
||||
|
||||
private static final String ERROR_MSG = "FindAndModifyOptions.none() cannot be changed. Please use FindAndModifyOptions.options() instead.";
|
||||
|
||||
@Override
|
||||
public FindAndModifyOptions returnNew(boolean returnNew) {
|
||||
throw new UnsupportedOperationException(ERROR_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindAndModifyOptions upsert(boolean upsert) {
|
||||
throw new UnsupportedOperationException(ERROR_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindAndModifyOptions remove(boolean remove) {
|
||||
throw new UnsupportedOperationException(ERROR_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindAndModifyOptions collation(Collation collation) {
|
||||
throw new UnsupportedOperationException(ERROR_MSG);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Static factory method to create a FindAndModifyOptions instance
|
||||
*
|
||||
@@ -42,6 +67,16 @@ public class FindAndModifyOptions {
|
||||
return new FindAndModifyOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method returning an unmodifiable {@link FindAndModifyOptions} instance.
|
||||
*
|
||||
* @return unmodifiable {@link FindAndModifyOptions} instance.
|
||||
* @since 2.2
|
||||
*/
|
||||
public static FindAndModifyOptions none() {
|
||||
return NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link FindAndModifyOptions} based on option of given {@litearl source}.
|
||||
*
|
||||
|
||||
@@ -36,6 +36,21 @@ public class FindAndReplaceOptions {
|
||||
private boolean returnNew;
|
||||
private boolean upsert;
|
||||
|
||||
private static final FindAndReplaceOptions NONE = new FindAndReplaceOptions() {
|
||||
|
||||
private static final String ERROR_MSG = "FindAndReplaceOptions.none() cannot be changed. Please use FindAndReplaceOptions.options() instead.";
|
||||
|
||||
@Override
|
||||
public FindAndReplaceOptions returnNew() {
|
||||
throw new UnsupportedOperationException(ERROR_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FindAndReplaceOptions upsert() {
|
||||
throw new UnsupportedOperationException(ERROR_MSG);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link FindAndReplaceOptions} instance.
|
||||
* <dl>
|
||||
@@ -51,6 +66,16 @@ public class FindAndReplaceOptions {
|
||||
return new FindAndReplaceOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method returning an unmodifiable {@link FindAndReplaceOptions} instance.
|
||||
*
|
||||
* @return unmodifiable {@link FindAndReplaceOptions} instance.
|
||||
* @since 2.2
|
||||
*/
|
||||
public static FindAndReplaceOptions none() {
|
||||
return NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method to create a {@link FindAndReplaceOptions} instance with
|
||||
* <dl>
|
||||
|
||||
@@ -515,7 +515,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
sortObject, fieldsObject, collectionName);
|
||||
}
|
||||
|
||||
this.executeQueryInternal(new FindCallback(queryObject, fieldsObject), preparer, documentCallbackHandler,
|
||||
this.executeQueryInternal(new FindCallback(queryObject, fieldsObject, null), preparer, documentCallbackHandler,
|
||||
collectionName);
|
||||
}
|
||||
|
||||
@@ -602,7 +602,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @see org.springframework.data.mongodb.core.MongoOperations#createCollection(java.lang.Class)
|
||||
*/
|
||||
public <T> MongoCollection<Document> createCollection(Class<T> entityClass) {
|
||||
return createCollection(operations.determineCollectionName(entityClass));
|
||||
return createCollection(entityClass, CollectionOptions.empty());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -613,8 +613,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
@Nullable CollectionOptions collectionOptions) {
|
||||
|
||||
Assert.notNull(entityClass, "EntityClass must not be null!");
|
||||
return doCreateCollection(operations.determineCollectionName(entityClass),
|
||||
convertToDocument(collectionOptions, entityClass));
|
||||
|
||||
CollectionOptions options = collectionOptions != null ? collectionOptions : CollectionOptions.empty();
|
||||
options = Optionals
|
||||
.firstNonEmpty(() -> collectionOptions != null ? collectionOptions.getCollation() : Optional.empty(),
|
||||
() -> getCollationForType(entityClass)) //
|
||||
.map(options::collation).orElse(options);
|
||||
|
||||
return doCreateCollection(operations.determineCollectionName(entityClass), convertToDocument(options, entityClass));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -786,8 +792,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(entityClass, "EntityClass must not be null!");
|
||||
Assert.notNull(collectionName, "CollectionName must not be null!");
|
||||
|
||||
if (ObjectUtils.isEmpty(query.getSortObject()) && !query.getCollation().isPresent()) {
|
||||
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass);
|
||||
if (ObjectUtils.isEmpty(query.getSortObject())) {
|
||||
|
||||
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
|
||||
.map(Collation::toMongoCollation).orElse(null),
|
||||
entityClass);
|
||||
} else {
|
||||
query.limit(1);
|
||||
List<T> results = find(query, entityClass, collectionName);
|
||||
@@ -816,7 +826,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Document mappedQuery = queryMapper.getMappedObject(query.getQueryObject(), getPersistentEntity(entityClass));
|
||||
|
||||
return execute(collectionName,
|
||||
new ExistsCallback(mappedQuery, query.getCollation().map(Collation::toMongoCollation).orElse(null)));
|
||||
new ExistsCallback(mappedQuery,
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
|
||||
.map(Collation::toMongoCollation).orElse(null)));
|
||||
}
|
||||
|
||||
// Find methods that take a Query to express the query and that return a List of objects.
|
||||
@@ -906,7 +918,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
DistinctIterable<T> iterable = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
|
||||
|
||||
return query.getCollation().map(Collation::toMongoCollation).map(iterable::collation).orElse(iterable);
|
||||
return Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
|
||||
.map(Collation::toMongoCollation) //
|
||||
.map(iterable::collation) //
|
||||
.orElse(iterable);
|
||||
});
|
||||
|
||||
if (resultClass == Object.class || mongoDriverCompatibleType != resultClass) {
|
||||
@@ -1063,7 +1078,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
"Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two.");
|
||||
});
|
||||
|
||||
query.getCollation().ifPresent(optionsToUse::collation);
|
||||
Optionals
|
||||
.firstNonEmpty(() -> query.getCollation(), () -> options.getCollation(), () -> getCollationForType(entityClass))
|
||||
.ifPresent(optionsToUse::collation);
|
||||
|
||||
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
|
||||
@@ -1096,8 +1113,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Document mappedReplacement = operations.forEntity(replacement).toMappedDocument(this.mongoConverter).getDocument();
|
||||
|
||||
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
|
||||
query.getCollation().map(Collation::toMongoCollation).orElse(null), entityType, mappedReplacement, options,
|
||||
resultType);
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityType))
|
||||
.map(Collation::toMongoCollation).orElse(null),
|
||||
entityType, mappedReplacement, options, resultType);
|
||||
}
|
||||
|
||||
// Find methods that take a Query to express the query and that return a single object that is also removed from the
|
||||
@@ -1118,7 +1136,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Assert.notNull(collectionName, "CollectionName must not be null!");
|
||||
|
||||
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
getMappedSortObject(query, entityClass), query.getCollation().orElse(null), entityClass);
|
||||
getMappedSortObject(query, entityClass),
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)).orElse(null),
|
||||
entityClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1610,11 +1630,13 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Document queryObj = new Document();
|
||||
|
||||
if (query != null) {
|
||||
|
||||
queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(opts::collation);
|
||||
}
|
||||
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(opts::collation);
|
||||
|
||||
Document updateObj = update instanceof MappedUpdate ? update.getUpdateObject()
|
||||
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
|
||||
@@ -1717,7 +1739,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Document removeQuery = queryObject;
|
||||
|
||||
DeleteOptions options = new DeleteOptions();
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(options::collation);
|
||||
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName,
|
||||
entityClass, null, queryObject);
|
||||
@@ -1764,8 +1788,10 @@ 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()), null,
|
||||
new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName);
|
||||
return executeFindMultiInternal(
|
||||
new FindCallback(new Document(), new Document(),
|
||||
getCollationForType(entityClass).map(Collation::toMongoCollation).orElse(null)),
|
||||
null, new ReadDocumentCallback<>(mongoConverter, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1879,6 +1905,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
}
|
||||
|
||||
if(!collation.isPresent()) {
|
||||
collation = getCollationForType(domainType);
|
||||
}
|
||||
|
||||
mapReduce = collation.map(Collation::toMongoCollation).map(mapReduce::collation).orElse(mapReduce);
|
||||
|
||||
List<T> mappedResults = new ArrayList<>();
|
||||
@@ -2123,8 +2153,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
List<Document> rawResult = new ArrayList<>();
|
||||
|
||||
Optional<Collation> collation = Optionals.firstNonEmpty(() -> options.getCollation(), () -> getCollationForType(
|
||||
aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null));
|
||||
|
||||
AggregateIterable<Document> aggregateIterable = collection.aggregate(pipeline, Document.class) //
|
||||
.collation(options.getCollation().map(Collation::toMongoCollation).orElse(null)) //
|
||||
.collation(collation.map(Collation::toMongoCollation).orElse(null)) //
|
||||
.allowDiskUse(options.isAllowDiskUse());
|
||||
|
||||
if (options.getCursorBatchSize() != null) {
|
||||
@@ -2165,16 +2198,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return execute(collectionName, (CollectionCallback<CloseableIterator<O>>) collection -> {
|
||||
|
||||
AggregateIterable<Document> cursor = collection.aggregate(pipeline, Document.class) //
|
||||
.allowDiskUse(options.isAllowDiskUse()) //
|
||||
.useCursor(true);
|
||||
.allowDiskUse(options.isAllowDiskUse());
|
||||
|
||||
if (options.getCursorBatchSize() != null) {
|
||||
cursor = cursor.batchSize(options.getCursorBatchSize());
|
||||
}
|
||||
|
||||
if (options.getCollation().isPresent()) {
|
||||
cursor = cursor.collation(options.getCollation().map(Collation::toMongoCollation).get());
|
||||
}
|
||||
Optionals
|
||||
.firstNonEmpty(() -> options.getCollation(),
|
||||
() -> getCollationForType(
|
||||
aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null)) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(cursor::collation);
|
||||
|
||||
return new CloseableIterableCursorAdapter<>(cursor, exceptionTranslator, readCallback);
|
||||
});
|
||||
@@ -2368,6 +2403,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
* @return the {@link List} of converted objects.
|
||||
*/
|
||||
protected <T> T doFindOne(String collectionName, Document query, Document fields, Class<T> entityClass) {
|
||||
return doFindOne(collectionName, query, fields, null, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
|
||||
* The query document is specified as a standard {@link Document} and so is the fields specification.
|
||||
*
|
||||
* @param collectionName name of the collection to retrieve the objects from.
|
||||
* @param query the query document that specifies the criteria used to find a record.
|
||||
* @param fields the document that specifies the fields to be returned.
|
||||
* @param entityClass the parameterized type of the returned list.
|
||||
* @return the {@link List} of converted objects.
|
||||
*/
|
||||
protected <T> T doFindOne(String collectionName, Document query, Document fields,
|
||||
@Nullable com.mongodb.client.model.Collation collation, Class<T> entityClass) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
Document mappedQuery = queryMapper.getMappedObject(query, entity);
|
||||
@@ -2378,7 +2428,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
mappedFields, entityClass, collectionName);
|
||||
}
|
||||
|
||||
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields),
|
||||
return executeFindOneInternal(new FindOneCallback(mappedQuery, mappedFields, collation),
|
||||
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
@@ -2429,7 +2479,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
serializeToJsonSafely(mappedQuery), mappedFields, entityClass, collectionName);
|
||||
}
|
||||
|
||||
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer, objectCallback,
|
||||
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null), preparer, objectCallback,
|
||||
collectionName);
|
||||
}
|
||||
|
||||
@@ -2452,7 +2502,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
serializeToJsonSafely(mappedQuery), mappedFields, sourceClass, collectionName);
|
||||
}
|
||||
|
||||
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields), preparer,
|
||||
return executeFindMultiInternal(new FindCallback(mappedQuery, mappedFields, null), preparer,
|
||||
new ProjectingReadCallback<>(mongoConverter, sourceClass, targetClass, collectionName), collectionName);
|
||||
}
|
||||
|
||||
@@ -2819,15 +2869,21 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
private final Document query;
|
||||
private final Optional<Document> fields;
|
||||
private final @Nullable com.mongodb.client.model.Collation collation;
|
||||
|
||||
public FindOneCallback(Document query, Document fields, @Nullable com.mongodb.client.model.Collation collation) {
|
||||
|
||||
public FindOneCallback(Document query, Document fields) {
|
||||
this.query = query;
|
||||
this.fields = Optional.of(fields).filter(it -> !ObjectUtils.isEmpty(fields));
|
||||
this.collation = collation;
|
||||
}
|
||||
|
||||
public Document doInCollection(MongoCollection<Document> collection) throws MongoException, DataAccessException {
|
||||
|
||||
FindIterable<Document> iterable = collection.find(query, Document.class);
|
||||
if (collation != null) {
|
||||
iterable = iterable.collation(collation);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
||||
@@ -2856,20 +2912,27 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final @Nullable com.mongodb.client.model.Collation collation;
|
||||
|
||||
public FindCallback(Document query, Document fields) {
|
||||
public FindCallback(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.query = query;
|
||||
this.fields = fields;
|
||||
this.collation = collation;
|
||||
}
|
||||
|
||||
public FindIterable<Document> doInCollection(MongoCollection<Document> collection)
|
||||
throws MongoException, DataAccessException {
|
||||
|
||||
return collection.find(query, Document.class).projection(fields);
|
||||
FindIterable<Document> findIterable = collection.find(query, Document.class).projection(fields);
|
||||
|
||||
if (collation != null) {
|
||||
findIterable = findIterable.collation(collation);
|
||||
}
|
||||
return findIterable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3156,20 +3219,23 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
*/
|
||||
public FindIterable<Document> prepare(FindIterable<Document> cursor) {
|
||||
|
||||
FindIterable<Document> cursorToUse = cursor;
|
||||
|
||||
Optionals
|
||||
.firstNonEmpty(() -> query != null ? query.getCollation() : Optional.empty(), () -> getCollationForType(type))
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(cursorToUse::collation);
|
||||
|
||||
if (query == null) {
|
||||
return cursor;
|
||||
return cursorToUse;
|
||||
}
|
||||
|
||||
Meta meta = query.getMeta();
|
||||
if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
|
||||
&& !StringUtils.hasText(query.getHint()) && !meta.hasValues() && !query.getCollation().isPresent()) {
|
||||
return cursor;
|
||||
return cursorToUse;
|
||||
}
|
||||
|
||||
FindIterable<Document> cursorToUse;
|
||||
|
||||
cursorToUse = query.getCollation().map(Collation::toMongoCollation).map(cursor::collation).orElse(cursor);
|
||||
|
||||
try {
|
||||
if (query.getSkip() > 0) {
|
||||
cursorToUse = cursorToUse.skip((int) query.getSkip());
|
||||
@@ -3417,4 +3483,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
return execute(collectionName, collection -> collection.countDocuments(filter, options));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Optional<Collation> getCollationForType(Class<?> type) {
|
||||
|
||||
if (type == null || type == Document.class) {
|
||||
return Optional.empty();
|
||||
}
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
|
||||
return entity != null ? Optional.ofNullable(entity.getCollation()) : Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -70,7 +69,16 @@ import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.PrefixingDelegatingAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.*;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.JsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
|
||||
import org.springframework.data.mongodb.core.convert.MongoWriter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.index.MongoMappingEventPublisher;
|
||||
import org.springframework.data.mongodb.core.index.ReactiveIndexOperations;
|
||||
import org.springframework.data.mongodb.core.index.ReactiveMongoPersistentEntityIndexCreator;
|
||||
@@ -113,11 +121,29 @@ import com.mongodb.Mongo;
|
||||
import com.mongodb.MongoException;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.model.*;
|
||||
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.ReplaceOptions;
|
||||
import com.mongodb.client.model.ReturnDocument;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.client.model.ValidationOptions;
|
||||
import com.mongodb.client.model.changestream.FullDocument;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import com.mongodb.reactivestreams.client.*;
|
||||
import com.mongodb.reactivestreams.client.AggregatePublisher;
|
||||
import com.mongodb.reactivestreams.client.ChangeStreamPublisher;
|
||||
import com.mongodb.reactivestreams.client.ClientSession;
|
||||
import com.mongodb.reactivestreams.client.DistinctPublisher;
|
||||
import com.mongodb.reactivestreams.client.FindPublisher;
|
||||
import com.mongodb.reactivestreams.client.MapReducePublisher;
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
import com.mongodb.reactivestreams.client.Success;
|
||||
|
||||
/**
|
||||
* Primary implementation of {@link ReactiveMongoOperations}. It simplifies the use of Reactive MongoDB usage and helps
|
||||
@@ -609,7 +635,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#createCollection(java.lang.Class)
|
||||
*/
|
||||
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass) {
|
||||
return createCollection(determineCollectionName(entityClass));
|
||||
return createCollection(entityClass, CollectionOptions.empty());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -618,8 +644,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
*/
|
||||
public <T> Mono<MongoCollection<Document>> createCollection(Class<T> entityClass,
|
||||
@Nullable CollectionOptions collectionOptions) {
|
||||
|
||||
Assert.notNull(entityClass, "EntityClass must not be null!");
|
||||
|
||||
CollectionOptions options = collectionOptions != null ? collectionOptions : CollectionOptions.empty();
|
||||
options = Optionals
|
||||
.firstNonEmpty(() -> collectionOptions != null ? collectionOptions.getCollation() : Optional.empty(),
|
||||
() -> getCollationForType(entityClass)) //
|
||||
.map(options::collation).orElse(options);
|
||||
|
||||
return doCreateCollection(determineCollectionName(entityClass),
|
||||
convertToCreateCollectionOptions(collectionOptions, entityClass));
|
||||
convertToCreateCollectionOptions(options, entityClass));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -719,7 +754,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
if (ObjectUtils.isEmpty(query.getSortObject())) {
|
||||
return doFindOne(collectionName, query.getQueryObject(), query.getFieldsObject(), entityClass,
|
||||
query.getCollation().orElse(null));
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)).orElse(null));
|
||||
}
|
||||
|
||||
query.limit(1);
|
||||
@@ -762,8 +797,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
LOGGER.debug("exists: {} in collection: {}", serializeToJsonSafely(filter), collectionName);
|
||||
}
|
||||
|
||||
findPublisher = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
|
||||
.orElse(findPublisher);
|
||||
findPublisher = Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
|
||||
.map(Collation::toMongoCollation).map(findPublisher::collation).orElse(findPublisher);
|
||||
|
||||
return findPublisher.limit(1);
|
||||
}).hasElements();
|
||||
@@ -849,8 +884,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
DistinctPublisher<T> publisher = collection.distinct(mappedFieldName, mappedQuery, mongoDriverCompatibleType);
|
||||
|
||||
return query.getCollation().map(Collation::toMongoCollation).map(publisher::collation).orElse(publisher);
|
||||
return Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass))
|
||||
.map(Collation::toMongoCollation) //
|
||||
.map(publisher::collation) //
|
||||
.orElse(publisher);
|
||||
});
|
||||
|
||||
if (resultClass == Object.class || mongoDriverCompatibleType != resultClass) {
|
||||
@@ -960,11 +997,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
ReadDocumentCallback<O> readCallback = new ReadDocumentCallback<>(mongoConverter, outputType, collectionName);
|
||||
return execute(collectionName, collection -> aggregateAndMap(collection, pipeline, options, readCallback));
|
||||
return execute(collectionName, collection -> aggregateAndMap(collection, pipeline, options, readCallback,
|
||||
aggregation instanceof TypedAggregation ? ((TypedAggregation) aggregation).getInputType() : null));
|
||||
}
|
||||
|
||||
private <O> Flux<O> aggregateAndMap(MongoCollection<Document> collection, List<Document> pipeline,
|
||||
AggregationOptions options, ReadDocumentCallback<O> readCallback) {
|
||||
AggregationOptions options, ReadDocumentCallback<O> readCallback, @Nullable Class<?> inputType) {
|
||||
|
||||
AggregatePublisher<Document> cursor = collection.aggregate(pipeline, Document.class)
|
||||
.allowDiskUse(options.isAllowDiskUse());
|
||||
@@ -973,9 +1011,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
cursor = cursor.batchSize(options.getCursorBatchSize());
|
||||
}
|
||||
|
||||
if (options.getCollation().isPresent()) {
|
||||
cursor = cursor.collation(options.getCollation().map(Collation::toMongoCollation).get());
|
||||
}
|
||||
Optionals.firstNonEmpty(() -> options.getCollation(), () -> getCollationForType(inputType)) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(cursor::collation);
|
||||
|
||||
return Flux.from(cursor).map(readCallback::doWith);
|
||||
}
|
||||
@@ -1072,6 +1110,8 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
public <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null! ");
|
||||
|
||||
FindAndModifyOptions optionsToUse = FindAndModifyOptions.of(options);
|
||||
|
||||
Optionals.ifAllPresent(query.getCollation(), optionsToUse.getCollation(), (l, r) -> {
|
||||
@@ -1079,7 +1119,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
"Both Query and FindAndModifyOptions define a collation. Please provide the collation only via one of the two.");
|
||||
});
|
||||
|
||||
query.getCollation().ifPresent(optionsToUse::collation);
|
||||
Optionals
|
||||
.firstNonEmpty(() -> query.getCollation(), () -> options.getCollation(), () -> getCollationForType(entityClass))
|
||||
.ifPresent(optionsToUse::collation);
|
||||
|
||||
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
getMappedSortObject(query, entityClass), entityClass, update, optionsToUse);
|
||||
@@ -1112,8 +1154,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
Document mappedReplacement = operations.forEntity(replacement).toMappedDocument(this.mongoConverter).getDocument();
|
||||
|
||||
return doFindAndReplace(collectionName, mappedQuery, mappedFields, mappedSort,
|
||||
query.getCollation().map(Collation::toMongoCollation).orElse(null), entityType, mappedReplacement, options,
|
||||
resultType);
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityType))
|
||||
.map(Collation::toMongoCollation).orElse(null),
|
||||
entityType, mappedReplacement, options, resultType);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1131,7 +1174,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
public <T> Mono<T> findAndRemove(Query query, Class<T> entityClass, String collectionName) {
|
||||
|
||||
return doFindAndRemove(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
getMappedSortObject(query, entityClass), query.getCollation().orElse(null), entityClass);
|
||||
getMappedSortObject(query, entityClass),
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)).orElse(null),
|
||||
entityClass);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1180,6 +1225,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
}
|
||||
|
||||
Optionals
|
||||
.firstNonEmpty(() -> query != null ? query.getCollation() : Optional.empty(),
|
||||
() -> getCollationForType(entityClass))
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(options::collation);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Executing count: {} in collection: {}", serializeToJsonSafely(filter), collectionName);
|
||||
}
|
||||
@@ -1647,7 +1698,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
|
||||
|
||||
UpdateOptions updateOptions = new UpdateOptions().upsert(upsert);
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(updateOptions::collation);
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(updateOptions::collation);
|
||||
|
||||
if (update.hasArrayFilters()) {
|
||||
updateOptions.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument)
|
||||
@@ -1811,7 +1864,10 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
null, removeQuery);
|
||||
|
||||
DeleteOptions deleteOptions = new DeleteOptions();
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(deleteOptions::collation);
|
||||
|
||||
Optionals.firstNonEmpty(() -> query.getCollation(), () -> getCollationForType(entityClass)) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(deleteOptions::collation);
|
||||
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
|
||||
@@ -2381,7 +2437,9 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
collectionName));
|
||||
}
|
||||
|
||||
return executeFindOneInternal(new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate, update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()), options),
|
||||
return executeFindOneInternal(
|
||||
new FindAndModifyCallback(mappedQuery, fields, sort, mappedUpdate,
|
||||
update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()), options),
|
||||
new ReadDocumentCallback<>(this.mongoConverter, entityClass, collectionName), collectionName);
|
||||
});
|
||||
}
|
||||
@@ -2781,12 +2839,13 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
return collection.findOneAndDelete(query, findOneAndDeleteOptions);
|
||||
}
|
||||
|
||||
FindOneAndUpdateOptions findOneAndUpdateOptions = convertToFindOneAndUpdateOptions(options, fields, sort, arrayFilters);
|
||||
FindOneAndUpdateOptions findOneAndUpdateOptions = convertToFindOneAndUpdateOptions(options, fields, sort,
|
||||
arrayFilters);
|
||||
return collection.findOneAndUpdate(query, update, findOneAndUpdateOptions);
|
||||
}
|
||||
|
||||
private static FindOneAndUpdateOptions convertToFindOneAndUpdateOptions(FindAndModifyOptions options, Document fields,
|
||||
Document sort, List<Document> arrayFilters) {
|
||||
private static FindOneAndUpdateOptions convertToFindOneAndUpdateOptions(FindAndModifyOptions options,
|
||||
Document fields, Document sort, List<Document> arrayFilters) {
|
||||
|
||||
FindOneAndUpdateOptions result = new FindOneAndUpdateOptions();
|
||||
|
||||
@@ -3023,15 +3082,16 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
@SuppressWarnings("deprecation")
|
||||
public <T> FindPublisher<T> prepare(FindPublisher<T> findPublisher) {
|
||||
|
||||
if (query == null) {
|
||||
return findPublisher;
|
||||
}
|
||||
|
||||
FindPublisher<T> findPublisherToUse;
|
||||
|
||||
findPublisherToUse = query.getCollation().map(Collation::toMongoCollation).map(findPublisher::collation)
|
||||
FindPublisher<T> findPublisherToUse = Optionals //
|
||||
.firstNonEmpty(() -> query != null ? query.getCollation() : Optional.empty(), () -> getCollationForType(type))
|
||||
.map(Collation::toMongoCollation) //
|
||||
.map(findPublisher::collation) //
|
||||
.orElse(findPublisher);
|
||||
|
||||
if (query == null) {
|
||||
return findPublisherToUse;
|
||||
}
|
||||
|
||||
Meta meta = query.getMeta();
|
||||
if (query.getSkip() <= 0 && query.getLimit() <= 0 && ObjectUtils.isEmpty(query.getSortObject())
|
||||
&& !StringUtils.hasText(query.getHint()) && !meta.hasValues()) {
|
||||
@@ -3205,4 +3265,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Optional<Collation> getCollationForType(Class<?> type) {
|
||||
|
||||
if (type == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(type);
|
||||
return entity != null ? Optional.ofNullable(entity.getCollation()) : Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
String collectionName = getCollectionName();
|
||||
|
||||
return template.findAndModify(query, update, findAndModifyOptions, targetType, collectionName);
|
||||
return template.findAndModify(query, update, findAndModifyOptions != null ? findAndModifyOptions : FindAndModifyOptions.none(), targetType, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -133,7 +133,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
@Override
|
||||
public Mono<T> findAndReplace() {
|
||||
return template.findAndReplace(query, replacement,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : new FindAndReplaceOptions(), (Class) domainType,
|
||||
findAndReplaceOptions != null ? findAndReplaceOptions : FindAndReplaceOptions.none(), (Class) domainType,
|
||||
getCollectionName(), targetType);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
|
||||
private final @Nullable Expression expression;
|
||||
|
||||
private final @Nullable String collation;
|
||||
private final @Nullable Expression collationExpression;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BasicMongoPersistentEntity} with the given {@link TypeInformation}. Will default the
|
||||
* collection name to the entities simple type name.
|
||||
@@ -78,12 +81,16 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
|
||||
this.collection = StringUtils.hasText(document.collection()) ? document.collection() : fallback;
|
||||
this.language = StringUtils.hasText(document.language()) ? document.language() : "";
|
||||
this.expression = detectExpression(document);
|
||||
this.expression = detectExpression(document.collection());
|
||||
this.collation = document.collation();
|
||||
this.collationExpression = detectExpression(document.collation());
|
||||
} else {
|
||||
|
||||
this.collection = fallback;
|
||||
this.language = "";
|
||||
this.expression = null;
|
||||
this.collation = null;
|
||||
this.collationExpression = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +133,33 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
return getTextScoreProperty() != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getCollation()
|
||||
*/
|
||||
@Override
|
||||
public org.springframework.data.mongodb.core.query.Collation getCollation() {
|
||||
|
||||
Object collationValue = collationExpression != null ? expression.getValue(getEvaluationContext(null), String.class)
|
||||
: this.collation;
|
||||
|
||||
if (collationValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (collationValue instanceof org.bson.Document) {
|
||||
return org.springframework.data.mongodb.core.query.Collation.from((org.bson.Document) collationValue);
|
||||
}
|
||||
|
||||
if (collationValue instanceof org.springframework.data.mongodb.core.query.Collation) {
|
||||
return org.springframework.data.mongodb.core.query.Collation.class.cast(collationValue);
|
||||
}
|
||||
|
||||
return StringUtils.hasText(collationValue.toString())
|
||||
? org.springframework.data.mongodb.core.query.Collation.parse(collationValue.toString())
|
||||
: null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.BasicPersistentEntity#verify()
|
||||
@@ -246,24 +280,20 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SpEL {@link Expression} frór the collection String expressed in the given {@link Document} annotation if
|
||||
* present or {@literal null} otherwise. Will also return {@literal null} it the collection {@link String} evaluates
|
||||
* to a {@link LiteralExpression} (indicating that no subsequent evaluation is necessary).
|
||||
* Returns a SpEL {@link Expression} if the given {@link String} is actually an expression that does not evaluate to a
|
||||
* {@link LiteralExpression} (indicating that no subsequent evaluation is necessary).
|
||||
*
|
||||
* @param document can be {@literal null}
|
||||
* @param potentialExpression can be {@literal null}
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private static Expression detectExpression(Document document) {
|
||||
private static Expression detectExpression(@Nullable String potentialExpression) {
|
||||
|
||||
String collection = document.collection();
|
||||
|
||||
if (!StringUtils.hasText(collection)) {
|
||||
if (!StringUtils.hasText(potentialExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Expression expression = PARSER.parseExpression(document.collection(), ParserContext.TEMPLATE_EXPRESSION);
|
||||
|
||||
Expression expression = PARSER.parseExpression(potentialExpression, ParserContext.TEMPLATE_EXPRESSION);
|
||||
return expression instanceof LiteralExpression ? null : expression;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,9 +60,17 @@ public @interface Document {
|
||||
/**
|
||||
* Defines the default language to be used with this document.
|
||||
*
|
||||
* @since 1.6
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
String language() default "";
|
||||
|
||||
/**
|
||||
* Defines the collation to apply when executing a query or creating indexes.
|
||||
*
|
||||
* @return an empty {@link String} by default.
|
||||
* @since 2.2
|
||||
*/
|
||||
String collation() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -36,17 +36,17 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi
|
||||
/**
|
||||
* Returns the default language to be used for this entity.
|
||||
*
|
||||
* @since 1.6
|
||||
* @return
|
||||
* @since 1.6
|
||||
*/
|
||||
String getLanguage();
|
||||
|
||||
/**
|
||||
* Returns the property holding text score value.
|
||||
*
|
||||
* @since 1.6
|
||||
* @see #hasTextScoreProperty()
|
||||
* @return {@literal null} if not present.
|
||||
* @see #hasTextScoreProperty()
|
||||
* @since 1.6
|
||||
*/
|
||||
@Nullable
|
||||
MongoPersistentProperty getTextScoreProperty();
|
||||
@@ -54,9 +54,27 @@ public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersi
|
||||
/**
|
||||
* Returns whether the entity has a {@link TextScore} property.
|
||||
*
|
||||
* @since 1.6
|
||||
* @return true if property annotated with {@link TextScore} is present.
|
||||
* @since 1.6
|
||||
*/
|
||||
boolean hasTextScoreProperty();
|
||||
|
||||
/**
|
||||
* Returns the collation of the entity evaluating a potential SpEL expression within the current context.
|
||||
*
|
||||
* @return {@literal null} if not set.
|
||||
* @since 2.2
|
||||
*/
|
||||
@Nullable
|
||||
org.springframework.data.mongodb.core.query.Collation getCollation();
|
||||
|
||||
/**
|
||||
* @return {@literal true} if the entity is annotated with
|
||||
* {@link org.springframework.data.mongodb.core.mapping.Collation}.
|
||||
* @since 2.2
|
||||
*/
|
||||
default boolean hasCollation() {
|
||||
return getCollation() != null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -403,6 +403,26 @@ public class Collation {
|
||||
return toDocument().toJson();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Collation that = (Collation) o;
|
||||
return this.toDocument().equals(that.toDocument());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toDocument().hashCode();
|
||||
}
|
||||
|
||||
private Collation copy() {
|
||||
|
||||
Collation collation = new Collation(locale);
|
||||
|
||||
@@ -121,6 +121,7 @@ public @interface Query {
|
||||
* List<Entry> findAllByDynamicSpElCollation(String collation);
|
||||
* </pre>
|
||||
*
|
||||
* @return an empty {@link String} by default.
|
||||
* @since 2.2
|
||||
*/
|
||||
String collation() default "";
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -61,4 +62,23 @@ public interface MongoEntityInformation<T, ID> extends EntityInformation<T, ID>
|
||||
default Object getVersion(T entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the entity defines a specific collation.
|
||||
*
|
||||
* @return {@literal true} if the entity defines a collation.
|
||||
* @since 2.2
|
||||
*/
|
||||
default boolean hasCollation() {
|
||||
return getCollation() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the collation for the entity or {@literal null} if {@link #hasCollation() not defined}.
|
||||
*
|
||||
* @return can be {@literal null}.
|
||||
* @since 2.2
|
||||
*/
|
||||
@Nullable
|
||||
Collation getCollation();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.support;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.data.repository.core.support.PersistentEntityInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -143,4 +144,13 @@ public class MappingMongoEntityInformation<T, ID> extends PersistentEntityInform
|
||||
return accessor.getProperty(this.entityMetadata.getRequiredVersionProperty());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.repository.MongoEntityInformation#getCollation()
|
||||
*/
|
||||
@Nullable
|
||||
public Collation getCollation() {
|
||||
return this.entityMetadata.getCollation();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
Long count = count();
|
||||
List<T> list = findAll(new Query().with(pageable));
|
||||
|
||||
return new PageImpl<T>(list, pageable, count);
|
||||
return new PageImpl<>(list, pageable, count);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -282,7 +282,9 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
Assert.notNull(pageable, "Pageable must not be null!");
|
||||
|
||||
Query query = new Query(new Criteria().alike(example)).with(pageable);
|
||||
Query query = new Query(new Criteria().alike(example)) //
|
||||
.collation(entityInformation.getCollation()).with(pageable); //
|
||||
|
||||
List<S> list = mongoOperations.find(query, example.getProbeType(), entityInformation.getCollectionName());
|
||||
|
||||
return PageableExecutionUtils.getPage(list, pageable,
|
||||
@@ -299,9 +301,11 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
|
||||
Query q = new Query(new Criteria().alike(example)).with(sort);
|
||||
Query query = new Query(new Criteria().alike(example)) //
|
||||
.collation(entityInformation.getCollation()) //
|
||||
.with(sort);
|
||||
|
||||
return mongoOperations.find(q, example.getProbeType(), entityInformation.getCollectionName());
|
||||
return mongoOperations.find(query, example.getProbeType(), entityInformation.getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -322,9 +326,11 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
|
||||
Query q = new Query(new Criteria().alike(example));
|
||||
Query query = new Query(new Criteria().alike(example)) //
|
||||
.collation(entityInformation.getCollation());
|
||||
|
||||
return Optional
|
||||
.ofNullable(mongoOperations.findOne(q, example.getProbeType(), entityInformation.getCollectionName()));
|
||||
.ofNullable(mongoOperations.findOne(query, example.getProbeType(), entityInformation.getCollectionName()));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -336,8 +342,10 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
|
||||
Query q = new Query(new Criteria().alike(example));
|
||||
return mongoOperations.count(q, example.getProbeType(), entityInformation.getCollectionName());
|
||||
Query query = new Query(new Criteria().alike(example)) //
|
||||
.collation(entityInformation.getCollation());
|
||||
|
||||
return mongoOperations.count(query, example.getProbeType(), entityInformation.getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -349,8 +357,10 @@ public class SimpleMongoRepository<T, ID> implements MongoRepository<T, ID> {
|
||||
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
|
||||
Query q = new Query(new Criteria().alike(example));
|
||||
return mongoOperations.exists(q, example.getProbeType(), entityInformation.getCollectionName());
|
||||
Query query = new Query(new Criteria().alike(example)) //
|
||||
.collation(entityInformation.getCollation());
|
||||
|
||||
return mongoOperations.exists(query, example.getProbeType(), entityInformation.getCollectionName());
|
||||
}
|
||||
|
||||
private Query getIdQuery(Object id) {
|
||||
|
||||
@@ -91,10 +91,11 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
|
||||
Query q = new Query(new Criteria().alike(example));
|
||||
q.limit(2);
|
||||
Query query = new Query(new Criteria().alike(example)) //
|
||||
.collation(entityInformation.getCollation()) //
|
||||
.limit(2);
|
||||
|
||||
return mongoOperations.find(q, example.getProbeType(), entityInformation.getCollectionName()).buffer(2)
|
||||
return mongoOperations.find(query, example.getProbeType(), entityInformation.getCollectionName()).buffer(2)
|
||||
.map(vals -> {
|
||||
|
||||
if (vals.size() > 1) {
|
||||
@@ -140,8 +141,10 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
|
||||
Query q = new Query(new Criteria().alike(example));
|
||||
return mongoOperations.exists(q, example.getProbeType(), entityInformation.getCollectionName());
|
||||
Query query = new Query(new Criteria().alike(example)) //
|
||||
.collation(entityInformation.getCollation());
|
||||
|
||||
return mongoOperations.exists(query, example.getProbeType(), entityInformation.getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -200,7 +203,9 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
|
||||
Query query = new Query(new Criteria().alike(example)).with(sort);
|
||||
Query query = new Query(new Criteria().alike(example)) //
|
||||
.collation(entityInformation.getCollation()) //
|
||||
.with(sort);
|
||||
|
||||
return mongoOperations.find(query, example.getProbeType(), entityInformation.getCollectionName());
|
||||
}
|
||||
@@ -235,8 +240,10 @@ public class SimpleReactiveMongoRepository<T, ID extends Serializable> implement
|
||||
|
||||
Assert.notNull(example, "Sample must not be null!");
|
||||
|
||||
Query q = new Query(new Criteria().alike(example));
|
||||
return mongoOperations.count(q, example.getProbeType(), entityInformation.getCollectionName());
|
||||
Query query = new Query(new Criteria().alike(example)) //
|
||||
.collation(entityInformation.getCollation());
|
||||
|
||||
return mongoOperations.count(query, example.getProbeType(), entityInformation.getCollectionName());
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2019 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 static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.IndexOptions;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DefaultIndexOperationsUnitTests {
|
||||
|
||||
MongoTemplate template;
|
||||
|
||||
@Mock MongoDbFactory factory;
|
||||
@Mock MongoDatabase db;
|
||||
@Mock MongoCollection<Document> collection;
|
||||
|
||||
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
|
||||
MappingMongoConverter converter;
|
||||
MongoMappingContext mappingContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(factory.getDb()).thenReturn(db);
|
||||
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
|
||||
when(db.getCollection(any(), any(Class.class))).thenReturn(collection);
|
||||
when(collection.createIndex(any(), any(IndexOptions.class))).thenReturn("OK");
|
||||
|
||||
this.mappingContext = new MongoMappingContext();
|
||||
this.converter = spy(new MappingMongoConverter(new DefaultDbRefResolver(factory), mappingContext));
|
||||
this.template = new MongoTemplate(factory, converter);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void ensureIndexDoesNotSetCollectionIfNoDefaultDefined() {
|
||||
|
||||
indexOpsFor(Jedi.class).ensureIndex(new Index("firstname", Direction.DESC));
|
||||
|
||||
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
|
||||
verify(collection).createIndex(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation()).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void ensureIndexUsesDefaultCollationIfNoneDefinedInOptions() {
|
||||
|
||||
indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC));
|
||||
|
||||
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
|
||||
verify(collection).createIndex(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation())
|
||||
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void ensureIndexDoesNotUseDefaultCollationIfExplicitlySpecifiedInTheIndex() {
|
||||
|
||||
indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC).collation(Collation.of("en_US")));
|
||||
|
||||
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
|
||||
verify(collection).createIndex(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation())
|
||||
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build());
|
||||
}
|
||||
|
||||
private DefaultIndexOperations indexOpsFor(Class<?> type) {
|
||||
return new DefaultIndexOperations(template, template.getCollectionName(type), type);
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Jedi {
|
||||
@Field("firstname") String name;
|
||||
}
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collation = "de_AT")
|
||||
static class Sith {
|
||||
@Field("firstname") String name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2019 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 static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.index.Index;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
|
||||
import com.mongodb.client.model.IndexOptions;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DefaultReactiveIndexOperationsUnitTests {
|
||||
|
||||
ReactiveMongoTemplate template;
|
||||
|
||||
@Mock ReactiveMongoDatabaseFactory factory;
|
||||
@Mock MongoDatabase db;
|
||||
@Mock MongoCollection<Document> collection;
|
||||
@Mock Publisher publisher;
|
||||
|
||||
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
|
||||
MappingMongoConverter converter;
|
||||
MongoMappingContext mappingContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(factory.getMongoDatabase()).thenReturn(db);
|
||||
when(factory.getExceptionTranslator()).thenReturn(exceptionTranslator);
|
||||
when(db.getCollection(any(), any(Class.class))).thenReturn(collection);
|
||||
when(collection.createIndex(any(), any(IndexOptions.class))).thenReturn(publisher);
|
||||
|
||||
this.mappingContext = new MongoMappingContext();
|
||||
this.converter = spy(new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext));
|
||||
this.template = new ReactiveMongoTemplate(factory, converter);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void ensureIndexDoesNotSetCollectionIfNoDefaultDefined() {
|
||||
|
||||
indexOpsFor(Jedi.class).ensureIndex(new Index("firstname", Direction.DESC)).subscribe();
|
||||
|
||||
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
|
||||
verify(collection).createIndex(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation()).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void ensureIndexUsesDefaultCollationIfNoneDefinedInOptions() {
|
||||
|
||||
indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC)).subscribe();
|
||||
|
||||
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
|
||||
verify(collection).createIndex(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation())
|
||||
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void ensureIndexDoesNotUseDefaultCollationIfExplicitlySpecifiedInTheIndex() {
|
||||
|
||||
indexOpsFor(Sith.class).ensureIndex(new Index("firstname", Direction.DESC).collation(Collation.of("en_US")))
|
||||
.subscribe();
|
||||
|
||||
ArgumentCaptor<IndexOptions> options = ArgumentCaptor.forClass(IndexOptions.class);
|
||||
verify(collection).createIndex(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation())
|
||||
.isEqualTo(com.mongodb.client.model.Collation.builder().locale("en_US").build());
|
||||
}
|
||||
|
||||
private DefaultReactiveIndexOperations indexOpsFor(Class<?> type) {
|
||||
return new DefaultReactiveIndexOperations(template, template.getCollectionName(type),
|
||||
new QueryMapper(template.getConverter()), type);
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Jedi {
|
||||
@Field("firstname") String name;
|
||||
}
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collation = "de_AT")
|
||||
static class Sith {
|
||||
@Field("firstname") String name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,14 +88,17 @@ import com.mongodb.MongoNamespace;
|
||||
import com.mongodb.ReadPreference;
|
||||
import com.mongodb.WriteConcern;
|
||||
import com.mongodb.client.AggregateIterable;
|
||||
import com.mongodb.client.DistinctIterable;
|
||||
import com.mongodb.client.FindIterable;
|
||||
import com.mongodb.client.MapReduceIterable;
|
||||
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;
|
||||
@@ -124,6 +127,7 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
@Mock FindIterable<Document> findIterable;
|
||||
@Mock AggregateIterable aggregateIterable;
|
||||
@Mock MapReduceIterable mapReduceIterable;
|
||||
@Mock DistinctIterable distinctIterable;
|
||||
@Mock UpdateResult updateResult;
|
||||
@Mock DeleteResult deleteResult;
|
||||
|
||||
@@ -143,12 +147,13 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
when(db.runCommand(any(), any(Class.class))).thenReturn(commandResultDocument);
|
||||
when(collection.find(any(org.bson.Document.class), any(Class.class))).thenReturn(findIterable);
|
||||
when(collection.mapReduce(any(), any(), eq(Document.class))).thenReturn(mapReduceIterable);
|
||||
when(collection.count(any(Bson.class), any(CountOptions.class))).thenReturn(1L); // TODO: MongoDB 4 - fix me decprecated
|
||||
when(collection.count(any(Bson.class), any(CountOptions.class))).thenReturn(1L); // TODO: MongoDB 4 - fix me
|
||||
when(collection.getNamespace()).thenReturn(new MongoNamespace("db.mock-collection"));
|
||||
when(collection.aggregate(any(List.class), any())).thenReturn(aggregateIterable);
|
||||
when(collection.withReadPreference(any())).thenReturn(collection);
|
||||
when(collection.replaceOne(any(), any(), any(ReplaceOptions.class))).thenReturn(updateResult);
|
||||
when(collection.withWriteConcern(any())).thenReturn(collectionWithWriteConcern);
|
||||
when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctIterable);
|
||||
when(collectionWithWriteConcern.deleteOne(any(Bson.class), any())).thenReturn(deleteResult);
|
||||
when(findIterable.projection(any())).thenReturn(findIterable);
|
||||
when(findIterable.sort(any(org.bson.Document.class))).thenReturn(findIterable);
|
||||
@@ -166,6 +171,9 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
when(aggregateIterable.batchSize(anyInt())).thenReturn(aggregateIterable);
|
||||
when(aggregateIterable.map(any())).thenReturn(aggregateIterable);
|
||||
when(aggregateIterable.into(any())).thenReturn(Collections.emptyList());
|
||||
when(distinctIterable.collation(any())).thenReturn(distinctIterable);
|
||||
when(distinctIterable.map(any())).thenReturn(distinctIterable);
|
||||
when(distinctIterable.into(any())).thenReturn(Collections.emptyList());
|
||||
|
||||
this.mappingContext = new MongoMappingContext();
|
||||
mappingContext.afterPropertiesSet();
|
||||
@@ -1006,14 +1014,6 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
.containing("near.coordinates.[0]", 1D).containing("near.coordinates.[1]", 2D));
|
||||
}
|
||||
|
||||
static class WithNamedFields {
|
||||
|
||||
@Id String id;
|
||||
|
||||
String name;
|
||||
@Field("custom-named-field") String customName;
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2155
|
||||
public void saveVersionedEntityShouldCallUpdateCorrectly() {
|
||||
|
||||
@@ -1085,6 +1085,308 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
.contains(new org.bson.Document("element", new Document("$gte", 100)));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void streamQueryShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.stream(new BasicQuery("{}"), Sith.class).next();
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findShouldNotUseCollationWhenNoDefaultPresent() {
|
||||
|
||||
template.find(new BasicQuery("{'foo' : 'bar'}"), Jedi.class);
|
||||
|
||||
verify(findIterable, never()).collation(any());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.find(new BasicQuery("{'foo' : 'bar'}"), Sith.class);
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findOneShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findOne(new BasicQuery("{'foo' : 'bar'}"), Sith.class);
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void existsShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.exists(new BasicQuery("{}"), Sith.class);
|
||||
|
||||
ArgumentCaptor<CountOptions> options = ArgumentCaptor.forClass(CountOptions.class);
|
||||
verify(collection).count(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(equalTo(com.mongodb.client.model.Collation.builder().locale("de_AT").build())));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findAndModfiyShoudUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findAndModify(new BasicQuery("{}"), new Update(), Sith.class);
|
||||
|
||||
ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class);
|
||||
verify(collection).findOneAndUpdate(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findAndRemoveShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findAndRemove(new BasicQuery("{}"), Sith.class);
|
||||
|
||||
ArgumentCaptor<FindOneAndDeleteOptions> options = ArgumentCaptor.forClass(FindOneAndDeleteOptions.class);
|
||||
verify(collection).findOneAndDelete(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void createCollectionShouldNotCollationIfNotPresent() {
|
||||
|
||||
template.createCollection(AutogenerateableId.class);
|
||||
|
||||
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
|
||||
verify(db).createCollection(any(), options.capture());
|
||||
|
||||
Assertions.assertThat(options.getValue().getCollation()).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void createCollectionShouldApplyDefaultCollation() {
|
||||
|
||||
template.createCollection(Sith.class);
|
||||
|
||||
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
|
||||
verify(db).createCollection(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void createCollectionShouldFavorExplicitOptionsOverDefaultCollation() {
|
||||
|
||||
template.createCollection(Sith.class, CollectionOptions.just(Collation.of("en_US")));
|
||||
|
||||
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
|
||||
verify(db).createCollection(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("en_US").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void createCollectionShouldUseDefaultCollationIfCollectionOptionsAreNull() {
|
||||
|
||||
template.createCollection(Sith.class, null);
|
||||
|
||||
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
|
||||
verify(db).createCollection(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void aggreateShouldUseDefaultCollationIfPresent() {
|
||||
|
||||
template.aggregate(newAggregation(Sith.class, project("id")), AutogenerateableId.class, Document.class);
|
||||
|
||||
verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void aggreateShouldUseCollationFromOptionsEvenIfDefaultCollationIsPresent() {
|
||||
|
||||
template.aggregateStream(newAggregation(Sith.class, project("id")).withOptions(
|
||||
newAggregationOptions().collation(Collation.of("fr")).build()), AutogenerateableId.class, Document.class);
|
||||
|
||||
verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void aggreateStreamShouldUseDefaultCollationIfPresent() {
|
||||
|
||||
template.aggregate(newAggregation(Sith.class, project("id")), AutogenerateableId.class, Document.class);
|
||||
|
||||
verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void aggreateStreamShouldUseCollationFromOptionsEvenIfDefaultCollationIsPresent() {
|
||||
|
||||
template.aggregateStream(newAggregation(Sith.class, project("id")).withOptions(
|
||||
newAggregationOptions().collation(Collation.of("fr")).build()), AutogenerateableId.class, Document.class);
|
||||
|
||||
verify(aggregateIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findAndReplaceShouldUseCollationWhenPresent() {
|
||||
|
||||
template.findAndReplace(new BasicQuery("{}").collation(Collation.of("fr")), new AutogenerateableId());
|
||||
|
||||
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
|
||||
verify(collection).findOneAndReplace(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findOneWithSortShouldUseCollationWhenPresent() {
|
||||
|
||||
template.findOne(new BasicQuery("{}").collation(Collation.of("fr")).with(Sort.by("id")), Sith.class);
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findOneWithSortShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findOne(new BasicQuery("{}").with(Sort.by("id")), Sith.class);
|
||||
|
||||
verify(findIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findAndReplaceShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findAndReplace(new BasicQuery("{}"), new Sith());
|
||||
|
||||
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
|
||||
verify(collection).findOneAndReplace(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("de_AT"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-18545
|
||||
public void findAndReplaceShouldUseCollationEvenIfDefaultCollationIsPresent() {
|
||||
|
||||
template.findAndReplace(new BasicQuery("{}").collation(Collation.of("fr")), new Sith());
|
||||
|
||||
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
|
||||
verify(collection).findOneAndReplace(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findDistinctShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findDistinct(new BasicQuery("{}"), "name", Sith.class, String.class);
|
||||
|
||||
verify(distinctIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findDistinctPreferCollationFromQueryOverDefaultCollation() {
|
||||
|
||||
template.findDistinct(new BasicQuery("{}").collation(Collation.of("fr")), "name", Sith.class, String.class);
|
||||
|
||||
verify(distinctIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void updateFirstShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), Update.update("foo", "bar"), Sith.class);
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void updateFirstShouldPreferExplicitCollationOverDefaultCollation() {
|
||||
|
||||
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), Update.update("foo", "bar"), Sith.class);
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void updateMultiShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.updateMulti(new BasicQuery("{}"), Update.update("foo", "bar"), Sith.class);
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateMany(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void updateMultiShouldPreferExplicitCollationOverDefaultCollation() {
|
||||
|
||||
template.updateMulti(new BasicQuery("{}").collation(Collation.of("fr")), Update.update("foo", "bar"), Sith.class);
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateMany(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void removeShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.remove(new BasicQuery("{}"), Sith.class);
|
||||
|
||||
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
|
||||
verify(collection).deleteMany(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void removeShouldPreferExplicitCollationOverDefaultCollation() {
|
||||
|
||||
template.remove(new BasicQuery("{}").collation(Collation.of("fr")), Sith.class);
|
||||
|
||||
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
|
||||
verify(collection).deleteMany(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void mapReduceShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.mapReduce("", "", "", MapReduceOptions.options(), Sith.class);
|
||||
|
||||
verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void mapReduceShouldPreferExplicitCollationOverDefaultCollation() {
|
||||
|
||||
template.mapReduce("", "", "", MapReduceOptions.options().collation(Collation.of("fr")), Sith.class);
|
||||
|
||||
verify(mapReduceIterable).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
class AutogenerateableId {
|
||||
|
||||
@Id BigInteger id;
|
||||
@@ -1157,6 +1459,20 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
List<Integer> grades;
|
||||
}
|
||||
|
||||
static class WithNamedFields {
|
||||
|
||||
@Id String id;
|
||||
|
||||
String name;
|
||||
@Field("custom-named-field") String customName;
|
||||
}
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collation = "de_AT")
|
||||
static class Sith {
|
||||
|
||||
@Field("firstname") String name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks out the {@link MongoTemplate#getDb()} method to return the {@link DB} mock instead of executing the actual
|
||||
* behaviour.
|
||||
|
||||
@@ -514,7 +514,7 @@ public class ReactiveMongoTemplateTests {
|
||||
p = template.findAndModify(query, update, new FindAndModifyOptions().returnNew(true), Person.class).block();
|
||||
assertThat(p.getAge()).isEqualTo(26);
|
||||
|
||||
p = template.findAndModify(query, update, null, Person.class, "person").block();
|
||||
p = template.findAndModify(query, update, FindAndModifyOptions.none(), Person.class, "person").block();
|
||||
assertThat(p.getAge()).isEqualTo(26);
|
||||
p = template.findOne(query, Person.class).block();
|
||||
assertThat(p.getAge()).isEqualTo(27);
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
|
||||
|
||||
import lombok.Data;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -39,6 +40,7 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.MongoTemplateUnitTests.AutogenerateableId;
|
||||
@@ -56,12 +58,15 @@ import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
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.ReplaceOptions;
|
||||
import com.mongodb.client.model.UpdateOptions;
|
||||
import com.mongodb.reactivestreams.client.AggregatePublisher;
|
||||
import com.mongodb.reactivestreams.client.DistinctPublisher;
|
||||
import com.mongodb.reactivestreams.client.FindPublisher;
|
||||
import com.mongodb.reactivestreams.client.MapReducePublisher;
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
@@ -88,6 +93,8 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
@Mock Publisher runCommandPublisher;
|
||||
@Mock Publisher updatePublisher;
|
||||
@Mock Publisher findAndUpdatePublisher;
|
||||
@Mock DistinctPublisher distinctPublisher;
|
||||
@Mock Publisher deletePublisher;
|
||||
@Mock MapReducePublisher mapReducePublisher;
|
||||
|
||||
MongoExceptionTranslator exceptionTranslator = new MongoExceptionTranslator();
|
||||
@@ -102,12 +109,20 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
when(db.getCollection(any())).thenReturn(collection);
|
||||
when(db.getCollection(any(), any())).thenReturn(collection);
|
||||
when(db.runCommand(any(), any(Class.class))).thenReturn(runCommandPublisher);
|
||||
when(db.createCollection(any(), any(CreateCollectionOptions.class))).thenReturn(runCommandPublisher);
|
||||
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);
|
||||
when(collection.aggregate(anyList(), any(Class.class))).thenReturn(aggregatePublisher);
|
||||
when(collection.count(any(), any(CountOptions.class))).thenReturn(Mono.just(0L));
|
||||
when(collection.updateOne(any(), any(), any(UpdateOptions.class))).thenReturn(updatePublisher);
|
||||
when(collection.updateMany(any(Bson.class), any(), any())).thenReturn(updatePublisher);
|
||||
when(collection.findOneAndUpdate(any(), any(), any(FindOneAndUpdateOptions.class)))
|
||||
.thenReturn(findAndUpdatePublisher);
|
||||
when(collection.findOneAndReplace(any(Bson.class), any(), any())).thenReturn(findPublisher);
|
||||
when(collection.findOneAndDelete(any(), any(FindOneAndDeleteOptions.class))).thenReturn(findPublisher);
|
||||
when(collection.distinct(anyString(), any(Document.class), any())).thenReturn(distinctPublisher);
|
||||
when(collection.deleteMany(any(Bson.class), any())).thenReturn(deletePublisher);
|
||||
when(collection.findOneAndUpdate(any(), any(), any(FindOneAndUpdateOptions.class)))
|
||||
.thenReturn(findAndUpdatePublisher);
|
||||
when(collection.mapReduce(anyString(), anyString(), any())).thenReturn(mapReducePublisher);
|
||||
@@ -405,6 +420,255 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
.contains(new org.bson.Document("element", new Document("$gte", 100)));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findShouldNotUseCollationWhenNoDefaultPresent() {
|
||||
|
||||
template.find(new BasicQuery("{'foo' : 'bar'}"), Jedi.class).subscribe();
|
||||
|
||||
verify(findPublisher, never()).collation(any());
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.find(new BasicQuery("{'foo' : 'bar'}"), Sith.class).subscribe();
|
||||
|
||||
verify(findPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findOneShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findOne(new BasicQuery("{'foo' : 'bar'}"), Sith.class).subscribe();
|
||||
|
||||
verify(findPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void existsShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.exists(new BasicQuery("{}"), Sith.class).subscribe();
|
||||
|
||||
verify(findPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findAndModfiyShoudUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findAndModify(new BasicQuery("{}"), new Update(), Sith.class).subscribe();
|
||||
|
||||
ArgumentCaptor<FindOneAndUpdateOptions> options = ArgumentCaptor.forClass(FindOneAndUpdateOptions.class);
|
||||
verify(collection).findOneAndUpdate(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findAndRemoveShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findAndRemove(new BasicQuery("{}"), Sith.class).subscribe();
|
||||
|
||||
ArgumentCaptor<FindOneAndDeleteOptions> options = ArgumentCaptor.forClass(FindOneAndDeleteOptions.class);
|
||||
verify(collection).findOneAndDelete(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void createCollectionShouldNotCollationIfNotPresent() {
|
||||
|
||||
template.createCollection(AutogenerateableId.class).subscribe();
|
||||
|
||||
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
|
||||
verify(db).createCollection(any(), options.capture());
|
||||
|
||||
Assertions.assertThat(options.getValue().getCollation()).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void createCollectionShouldApplyDefaultCollation() {
|
||||
|
||||
template.createCollection(Sith.class).subscribe();
|
||||
|
||||
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
|
||||
verify(db).createCollection(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void createCollectionShouldFavorExplicitOptionsOverDefaultCollation() {
|
||||
|
||||
template.createCollection(Sith.class, CollectionOptions.just(Collation.of("en_US"))).subscribe();
|
||||
|
||||
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
|
||||
verify(db).createCollection(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("en_US").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void createCollectionShouldUseDefaultCollationIfCollectionOptionsAreNull() {
|
||||
|
||||
template.createCollection(Sith.class, null).subscribe();
|
||||
|
||||
ArgumentCaptor<CreateCollectionOptions> options = ArgumentCaptor.forClass(CreateCollectionOptions.class);
|
||||
verify(db).createCollection(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void aggreateShouldUseDefaultCollationIfPresent() {
|
||||
|
||||
template.aggregate(newAggregation(Sith.class, project("id")), AutogenerateableId.class, Document.class).subscribe();
|
||||
|
||||
verify(aggregatePublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void aggreateShouldUseCollationFromOptionsEvenIfDefaultCollationIsPresent() {
|
||||
|
||||
template
|
||||
.aggregate(
|
||||
newAggregation(Sith.class, project("id"))
|
||||
.withOptions(newAggregationOptions().collation(Collation.of("fr")).build()),
|
||||
AutogenerateableId.class, Document.class)
|
||||
.subscribe();
|
||||
|
||||
verify(aggregatePublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-18545
|
||||
public void findAndReplaceShouldUseCollationWhenPresent() {
|
||||
|
||||
template.findAndReplace(new BasicQuery("{}").collation(Collation.of("fr")), new Jedi()).subscribe();
|
||||
|
||||
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
|
||||
verify(collection).findOneAndReplace(any(Bson.class), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-18545
|
||||
public void findAndReplaceShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findAndReplace(new BasicQuery("{}"), new Sith()).subscribe();
|
||||
|
||||
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
|
||||
verify(collection).findOneAndReplace(any(Bson.class), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("de_AT"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-18545
|
||||
public void findAndReplaceShouldUseCollationEvenIfDefaultCollationIsPresent() {
|
||||
|
||||
template.findAndReplace(new BasicQuery("{}").collation(Collation.of("fr")), new MongoTemplateUnitTests.Sith())
|
||||
.subscribe();
|
||||
|
||||
ArgumentCaptor<FindOneAndReplaceOptions> options = ArgumentCaptor.forClass(FindOneAndReplaceOptions.class);
|
||||
verify(collection).findOneAndReplace(any(Bson.class), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation().getLocale(), is("fr"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findDistinctShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.findDistinct(new BasicQuery("{}"), "name", Sith.class, String.class).subscribe();
|
||||
|
||||
verify(distinctPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void findDistinctPreferCollationFromQueryOverDefaultCollation() {
|
||||
|
||||
template.findDistinct(new BasicQuery("{}").collation(Collation.of("fr")), "name", Sith.class, String.class)
|
||||
.subscribe();
|
||||
|
||||
verify(distinctPublisher).collation(eq(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void updateFirstShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), Update.update("foo", "bar"), Sith.class).subscribe();
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void updateFirstShouldPreferExplicitCollationOverDefaultCollation() {
|
||||
|
||||
template.updateFirst(new BasicQuery("{}").collation(Collation.of("fr")), Update.update("foo", "bar"), Sith.class)
|
||||
.subscribe();
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateOne(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void updateMultiShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.updateMulti(new BasicQuery("{}"), Update.update("foo", "bar"), Sith.class).subscribe();
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateMany(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void updateMultiShouldPreferExplicitCollationOverDefaultCollation() {
|
||||
|
||||
template.updateMulti(new BasicQuery("{}").collation(Collation.of("fr")), Update.update("foo", "bar"), Sith.class)
|
||||
.subscribe();
|
||||
|
||||
ArgumentCaptor<UpdateOptions> options = ArgumentCaptor.forClass(UpdateOptions.class);
|
||||
verify(collection).updateMany(any(), any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void removeShouldUseDefaultCollationWhenPresent() {
|
||||
|
||||
template.remove(new BasicQuery("{}"), Sith.class).subscribe();
|
||||
|
||||
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
|
||||
verify(collection).deleteMany(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("de_AT").build()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void removeShouldPreferExplicitCollationOverDefaultCollation() {
|
||||
|
||||
template.remove(new BasicQuery("{}").collation(Collation.of("fr")), Sith.class).subscribe();
|
||||
|
||||
ArgumentCaptor<DeleteOptions> options = ArgumentCaptor.forClass(DeleteOptions.class);
|
||||
verify(collection).deleteMany(any(), options.capture());
|
||||
|
||||
assertThat(options.getValue().getCollation(),
|
||||
is(com.mongodb.client.model.Collation.builder().locale("fr").build()));
|
||||
}
|
||||
|
||||
@Data
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collection = "star-wars")
|
||||
static class Person {
|
||||
@@ -434,6 +698,12 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
@Field("firstname") String name;
|
||||
}
|
||||
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collation = "de_AT")
|
||||
static class Sith {
|
||||
|
||||
@Field("firstname") String name;
|
||||
}
|
||||
|
||||
static class EntityWithListOfSimple {
|
||||
List<Integer> grades;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -237,6 +237,24 @@ public class BasicMongoPersistentEntityUnitTests {
|
||||
assertThat(entity.getCollection()).isEqualTo("collectionName");
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void readsSimpleCollation() {
|
||||
|
||||
BasicMongoPersistentEntity<WithSimpleCollation> entity = new BasicMongoPersistentEntity<>(
|
||||
ClassTypeInformation.from(WithSimpleCollation.class));
|
||||
|
||||
assertThat(entity.getCollation()).isEqualTo(org.springframework.data.mongodb.core.query.Collation.of("en_US"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void readsDocumentCollation() {
|
||||
|
||||
BasicMongoPersistentEntity<WithDocumentCollation> entity = new BasicMongoPersistentEntity<>(
|
||||
ClassTypeInformation.from(WithDocumentCollation.class));
|
||||
|
||||
assertThat(entity.getCollation()).isEqualTo(org.springframework.data.mongodb.core.query.Collation.of("en_US"));
|
||||
}
|
||||
|
||||
@Document("contacts")
|
||||
class Contact {}
|
||||
|
||||
@@ -283,10 +301,18 @@ public class BasicMongoPersistentEntityUnitTests {
|
||||
}
|
||||
|
||||
// DATAMONGO-1874
|
||||
|
||||
@Document("#{myProperty}")
|
||||
class MappedWithExtension {}
|
||||
|
||||
@Document(collation = "#{myCollation}")
|
||||
class WithCollationFromSpEL {}
|
||||
|
||||
@Document(collation = "en_US")
|
||||
class WithSimpleCollation {}
|
||||
|
||||
@Document(collation = "{ 'locale' : 'en_US' }")
|
||||
class WithDocumentCollation {}
|
||||
|
||||
static class SampleExtension implements EvaluationContextExtension {
|
||||
|
||||
/*
|
||||
@@ -304,7 +330,11 @@ public class BasicMongoPersistentEntityUnitTests {
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getProperties() {
|
||||
return Collections.singletonMap("myProperty", "collectionName");
|
||||
|
||||
Map<String, Object> properties = new LinkedHashMap<>();
|
||||
properties.put("myProperty", "collectionName");
|
||||
properties.put("myCollation", "en_US");
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
package org.springframework.data.mongodb.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@@ -33,7 +35,6 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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,
|
||||
@@ -30,7 +30,6 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.data.mongodb.core.Person;
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery;
|
||||
import org.springframework.data.mongodb.core.ReactiveFindOperation.ReactiveFind;
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.springframework.data.mongodb.MongoTransactionManager;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.repository.Address;
|
||||
import org.springframework.data.mongodb.repository.Person;
|
||||
import org.springframework.data.mongodb.repository.Person.Sex;
|
||||
@@ -485,6 +486,11 @@ public class SimpleMongoRepositoryTests {
|
||||
public String getIdAttribute() {
|
||||
return "id";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collation getCollation() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Document
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2019 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.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SimpleMongoRepositoryUnitTests {
|
||||
|
||||
SimpleMongoRepository<Object, Object> repository;
|
||||
@Mock MongoOperations mongoOperations;
|
||||
@Mock MongoEntityInformation<Object, Object> entityInformation;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
repository = new SimpleMongoRepository<>(entityInformation, mongoOperations);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToCountForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.count(Example.of(new TestDummy()));
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).count(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToExistsForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.exists(Example.of(new TestDummy()));
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).exists(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToFindForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.findAll(Example.of(new TestDummy()));
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).find(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToFindWithSortForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.findAll(Example.of(new TestDummy()), Sort.by("nothing"));
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).find(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToFindWithPageableForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.findAll(Example.of(new TestDummy()), PageRequest.of(1, 1, Sort.by("nothing")));
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).find(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToFindOneForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.findOne(Example.of(new TestDummy()));
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).findOne(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
static class TestDummy {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2019. 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2019 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.repository.support;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.query.Collation;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SimpleReactiveMongoRepositoryUnitTests {
|
||||
|
||||
SimpleReactiveMongoRepository<Object, String> repository;
|
||||
@Mock Mono mono;
|
||||
@Mock Flux flux;
|
||||
@Mock ReactiveMongoOperations mongoOperations;
|
||||
@Mock MongoEntityInformation<Object, String> entityInformation;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(mongoOperations.count(any(), any(), any())).thenReturn(mono);
|
||||
when(mongoOperations.exists(any(), any(), any())).thenReturn(mono);
|
||||
when(mongoOperations.find(any(), any(), any())).thenReturn(flux);
|
||||
|
||||
repository = new SimpleReactiveMongoRepository<>(entityInformation, mongoOperations);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToCountForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.count(Example.of(new TestDummy())).subscribe();
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).count(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToExistsForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.exists(Example.of(new TestDummy())).subscribe();
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).exists(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToFindForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.findAll(Example.of(new TestDummy())).subscribe();
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).find(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToFindWithSortForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.findAll(Example.of(new TestDummy()), Sort.by("nothing")).subscribe();
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).find(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-1854
|
||||
public void shouldAddDefaultCollationToFindOneForExampleIfPresent() {
|
||||
|
||||
Collation collation = Collation.of("en_US");
|
||||
|
||||
when(entityInformation.getCollation()).thenReturn(collation);
|
||||
repository.findOne(Example.of(new TestDummy())).subscribe();
|
||||
|
||||
ArgumentCaptor<Query> query = ArgumentCaptor.forClass(Query.class);
|
||||
verify(mongoOperations).find(query.capture(), any(), any());
|
||||
|
||||
assertThat(query.getValue().getCollation()).contains(collation);
|
||||
}
|
||||
|
||||
static class TestDummy {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1687,7 +1687,15 @@ Collation collation = Collation.of("fr") <1>
|
||||
<6> Specify whether to check whether text requires normalization and whether to perform normalization.
|
||||
====
|
||||
|
||||
Collations can be used to create collections and indexes. If you create a collection that specifies a collation, the collation is applied to index creation and queries unless you specify a different collation. A collation is valid for a whole operation and cannot be specified on a per-field basis, as the following example shows:
|
||||
Collations can be used to create collections and indexes. If you create a collection that specifies a collation, the
|
||||
collation is applied to index creation and queries unless you specify a different collation. A collation is valid for a
|
||||
whole operation and cannot be specified on a per-field basis.
|
||||
|
||||
Like other metadata, collations can be be derived from the domain type via the `collation` attribute of the `@Document`
|
||||
annotation and will be applied directly when executing queries, creating collections or indexes.
|
||||
|
||||
NOTE: Annotated collations will not be used when a collection is auto created by MongoDB on first interaction. This would
|
||||
require additional store interaction delaying the entire process. Please use `MongoOperations.createCollection` for those cases.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -1738,7 +1746,7 @@ WARNING: Indexes are only used if the collation used for the operation matches t
|
||||
|
||||
include::./mongo-json-schema.adoc[leveloffset=+1]
|
||||
|
||||
<<mongo.repositories>> support `Collations` via the `@org.springframework.data.mongodb.repository.Query` annotation.
|
||||
<<mongo.repositories>> support `Collations` via the `collation` attribute of the `@Query` annotation.
|
||||
|
||||
.Collation support for Repositories
|
||||
====
|
||||
@@ -1774,6 +1782,8 @@ and `Document` (eg. new Document("locale", "en_US"))
|
||||
|
||||
NOTE: In case you enabled the automatic index creation for repository finder methods a potential static collation definition,
|
||||
as shown in (1) and (2), will be included when creating the index.
|
||||
|
||||
TIP: The most specifc `Collation` outroules potentially defined others. Which means Method argument over query method annotation over doamin type annotation.
|
||||
====
|
||||
|
||||
[[mongo.jsonSchema]]
|
||||
|
||||
Reference in New Issue
Block a user