Apply sort to replace and bulk operation updates
Allow using sort parameter from the query for template replace as well as bulk update & replace operations. We now also mapped fields used in sort to the domain type considering field annotations. Also updated javadoc and reference documentation. Original Pull Request: #4888
This commit is contained in:
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
@@ -81,7 +82,8 @@ public interface BulkOperations {
|
||||
/**
|
||||
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
|
||||
*
|
||||
* @param query update criteria, must not be {@literal null}.
|
||||
* @param query update criteria, must not be {@literal null}. The {@link Query} may define a {@link Query#with(Sort)
|
||||
* sort order} to influence which document to update when potentially matching multiple candidates.
|
||||
* @param update {@link Update} operation to perform, must not be {@literal null}.
|
||||
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
|
||||
*/
|
||||
@@ -92,7 +94,8 @@ public interface BulkOperations {
|
||||
/**
|
||||
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
|
||||
*
|
||||
* @param query update criteria, must not be {@literal null}.
|
||||
* @param query update criteria, must not be {@literal null}. The {@link Query} may define a {@link Query#with(Sort)
|
||||
* sort order} to influence which document to update when potentially matching multiple candidates.
|
||||
* @param update {@link Update} operation to perform, must not be {@literal null}.
|
||||
* @return the current {@link BulkOperations} instance with the update added, will never be {@literal null}.
|
||||
* @since 4.1
|
||||
@@ -187,7 +190,8 @@ public interface BulkOperations {
|
||||
/**
|
||||
* Add a single replace operation to the bulk operation.
|
||||
*
|
||||
* @param query Update criteria.
|
||||
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
|
||||
* which document to replace when potentially matching multiple candidates.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the current {@link BulkOperations} instance with the replacement added, will never be {@literal null}.
|
||||
* @since 2.2
|
||||
@@ -199,7 +203,8 @@ public interface BulkOperations {
|
||||
/**
|
||||
* Add a single replace operation to the bulk operation.
|
||||
*
|
||||
* @param query Update criteria.
|
||||
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
|
||||
* which document to replace when potentially matching multiple candidates.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the current {@link BulkOperations} instance with the replacement added, will never be {@literal null}.
|
||||
|
||||
@@ -106,6 +106,11 @@ abstract class BulkOperationsSupport {
|
||||
|
||||
if (writeModel instanceof UpdateOneModel<Document> model) {
|
||||
|
||||
Bson sort = model.getOptions().getSort();
|
||||
if (sort instanceof Document sortDocument) {
|
||||
model.getOptions().sort(updateMapper().getMappedSort(sortDocument, entity().orElse(null)));
|
||||
}
|
||||
|
||||
if (source instanceof AggregationUpdate aggregationUpdate) {
|
||||
|
||||
List<Document> pipeline = mapUpdatePipeline(aggregationUpdate);
|
||||
@@ -136,6 +141,17 @@ abstract class BulkOperationsSupport {
|
||||
return new DeleteManyModel<>(getMappedQuery(model.getFilter()), model.getOptions());
|
||||
}
|
||||
|
||||
if (writeModel instanceof ReplaceOneModel<Document> model) {
|
||||
|
||||
Bson sort = model.getReplaceOptions().getSort();
|
||||
|
||||
if (sort instanceof Document sortDocument) {
|
||||
model.getReplaceOptions().sort(updateMapper().getMappedSort(sortDocument, entity().orElse(null)));
|
||||
}
|
||||
return new ReplaceOneModel<>(getMappedQuery(model.getFilter()), model.getReplacement(),
|
||||
model.getReplaceOptions());
|
||||
}
|
||||
|
||||
return writeModel;
|
||||
}
|
||||
|
||||
@@ -192,9 +208,11 @@ abstract class BulkOperationsSupport {
|
||||
* @param filterQuery The {@link Query} to read a potential {@link Collation} from. Must not be {@literal null}.
|
||||
* @param update The {@link Update} to apply
|
||||
* @param upsert flag to indicate if document should be upserted.
|
||||
* @param multi flag to indicate if update might affect multiple documents.
|
||||
* @return new instance of {@link UpdateOptions}.
|
||||
*/
|
||||
protected static UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDefinition update, boolean upsert) {
|
||||
protected UpdateOptions computeUpdateOptions(Query filterQuery, UpdateDefinition update, boolean upsert,
|
||||
boolean multi) {
|
||||
|
||||
UpdateOptions options = new UpdateOptions();
|
||||
options.upsert(upsert);
|
||||
@@ -207,6 +225,10 @@ abstract class BulkOperationsSupport {
|
||||
options.arrayFilters(list);
|
||||
}
|
||||
|
||||
if (!multi && filterQuery.isSorted()) {
|
||||
options.sort(filterQuery.getSortObject());
|
||||
}
|
||||
|
||||
filterQuery.getCollation().map(Collation::toMongoCollation).ifPresent(options::collation);
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -229,12 +229,14 @@ class DefaultBulkOperations extends BulkOperationsSupport implements BulkOperati
|
||||
|
||||
ReplaceOptions replaceOptions = new ReplaceOptions();
|
||||
replaceOptions.upsert(options.isUpsert());
|
||||
if (query.isSorted()) {
|
||||
replaceOptions.sort(query.getSortObject());
|
||||
}
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(replaceOptions::collation);
|
||||
|
||||
maybeEmitEvent(new BeforeConvertEvent<>(replacement, collectionName));
|
||||
Object source = maybeInvokeBeforeConvertCallback(replacement);
|
||||
addModel(source,
|
||||
new ReplaceOneModel<>(getMappedQuery(query.getQueryObject()), getMappedObject(source), replaceOptions));
|
||||
addModel(source, new ReplaceOneModel<>(query.getQueryObject(), getMappedObject(source), replaceOptions));
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -315,7 +317,7 @@ class DefaultBulkOperations extends BulkOperationsSupport implements BulkOperati
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notNull(update, "Update must not be null");
|
||||
|
||||
UpdateOptions options = computeUpdateOptions(query, update, upsert);
|
||||
UpdateOptions options = computeUpdateOptions(query, update, upsert, multi);
|
||||
|
||||
if (multi) {
|
||||
addModel(update, new UpdateManyModel<>(query.getQueryObject(), update.getUpdateObject(), options));
|
||||
|
||||
@@ -189,13 +189,16 @@ class DefaultReactiveBulkOperations extends BulkOperationsSupport implements Rea
|
||||
|
||||
ReplaceOptions replaceOptions = new ReplaceOptions();
|
||||
replaceOptions.upsert(options.isUpsert());
|
||||
if (query.isSorted()) {
|
||||
replaceOptions.sort(query.getSortObject());
|
||||
}
|
||||
query.getCollation().map(Collation::toMongoCollation).ifPresent(replaceOptions::collation);
|
||||
|
||||
this.models.add(Mono.just(replacement).flatMap(it -> {
|
||||
maybeEmitEvent(new BeforeConvertEvent<>(it, collectionName));
|
||||
return maybeInvokeBeforeConvertCallback(it);
|
||||
}).map(it -> new SourceAwareWriteModelHolder(it,
|
||||
new ReplaceOneModel<>(getMappedQuery(query.getQueryObject()), getMappedObject(it), replaceOptions))));
|
||||
new ReplaceOneModel<>(query.getQueryObject(), getMappedObject(it), replaceOptions))));
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -218,13 +221,13 @@ class DefaultReactiveBulkOperations extends BulkOperationsSupport implements Rea
|
||||
|
||||
Flux<SourceAwareWriteModelHolder> concat = Flux.concat(models).flatMapSequential(it -> {
|
||||
|
||||
if (it.model()instanceof InsertOneModel<Document> iom) {
|
||||
if (it.model() instanceof InsertOneModel<Document> iom) {
|
||||
|
||||
Document target = iom.getDocument();
|
||||
maybeEmitBeforeSaveEvent(it);
|
||||
return maybeInvokeBeforeSaveCallback(it.source(), target)
|
||||
.map(afterCallback -> new SourceAwareWriteModelHolder(afterCallback, mapWriteModel(afterCallback, iom)));
|
||||
} else if (it.model()instanceof ReplaceOneModel<Document> rom) {
|
||||
} else if (it.model() instanceof ReplaceOneModel<Document> rom) {
|
||||
|
||||
Document target = rom.getReplacement();
|
||||
maybeEmitBeforeSaveEvent(it);
|
||||
@@ -265,7 +268,7 @@ class DefaultReactiveBulkOperations extends BulkOperationsSupport implements Rea
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notNull(update, "Update must not be null");
|
||||
|
||||
UpdateOptions options = computeUpdateOptions(query, update, upsert);
|
||||
UpdateOptions options = computeUpdateOptions(query, update, upsert, multi);
|
||||
|
||||
this.models.add(Mono.just(update).map(it -> {
|
||||
if (multi) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
|
||||
@@ -1604,8 +1605,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be
|
||||
* auto-incremented if not explicitly specified in the update.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. The
|
||||
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
|
||||
* potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use.
|
||||
@@ -1623,12 +1625,11 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* the provided updated document. <br />
|
||||
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
|
||||
* domain type information. Use {@link #updateFirst(Query, UpdateDefinition, Class, String)} to get full type specific
|
||||
* support. <br />
|
||||
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
|
||||
* Use {@link #findAndModify(Query, UpdateDefinition, Class, String)} instead.
|
||||
* support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. The
|
||||
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
|
||||
* potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
@@ -1646,8 +1647,9 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be auto
|
||||
* incremented if not explicitly specified in the update.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. The
|
||||
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
|
||||
* potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
@@ -1833,7 +1835,8 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
|
||||
* replace when potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
|
||||
@@ -1850,7 +1853,8 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
|
||||
* replace when potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
@@ -1866,7 +1870,8 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document.The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
|
||||
* replace when potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
@@ -1884,7 +1889,8 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may *
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
|
||||
* replace when potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
|
||||
@@ -765,7 +765,7 @@ class QueryOperations {
|
||||
}
|
||||
|
||||
if (query != null && query.isSorted()) {
|
||||
options.sort(query.getSortObject());
|
||||
options.sort(getMappedSort(domainType != null ? mappingContext.getPersistentEntity(domainType) : null));
|
||||
}
|
||||
|
||||
HintFunction.from(getQuery().getHint()).ifPresent(codecRegistryProvider, options::hintString, options::hint);
|
||||
@@ -799,6 +799,9 @@ class QueryOperations {
|
||||
options.collation(updateOptions.getCollation());
|
||||
options.upsert(updateOptions.isUpsert());
|
||||
applyHint(options::hintString, options::hint);
|
||||
if (!isMulti() && getQuery().isSorted()) {
|
||||
options.sort(getMappedSort(domainType != null ? mappingContext.getPersistentEntity(domainType) : null));
|
||||
}
|
||||
|
||||
if (callback != null) {
|
||||
callback.accept(options);
|
||||
|
||||
@@ -19,6 +19,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
|
||||
@@ -67,7 +68,8 @@ public interface ReactiveBulkOperations {
|
||||
/**
|
||||
* Add a single update to the bulk operation. For the update request, only the first matching document is updated.
|
||||
*
|
||||
* @param query update criteria, must not be {@literal null}.
|
||||
* @param query update criteria, must not be {@literal null}. The {@link Query} may define a {@link Query#with(Sort)
|
||||
* sort order} to influence which document to update when potentially matching multiple candidates.
|
||||
* @param update {@link UpdateDefinition} operation to perform, must not be {@literal null}.
|
||||
* @return the current {@link ReactiveBulkOperations} instance with the update added, will never be {@literal null}.
|
||||
*/
|
||||
@@ -111,8 +113,11 @@ public interface ReactiveBulkOperations {
|
||||
/**
|
||||
* Add a single replace operation to the bulk operation.
|
||||
*
|
||||
* @param query Update criteria.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
|
||||
* which document to replace when potentially matching multiple candidates.
|
||||
* @param replacement the replacement document. Must not be {@literal null}. The {@link Query} may define a
|
||||
* {@link Query#with(Sort) sort order} to influence which document to replace when potentially matching
|
||||
* multiple candidates.
|
||||
* @return the current {@link ReactiveBulkOperations} instance with the replace added, will never be {@literal null}.
|
||||
*/
|
||||
default ReactiveBulkOperations replaceOne(Query query, Object replacement) {
|
||||
@@ -122,7 +127,8 @@ public interface ReactiveBulkOperations {
|
||||
/**
|
||||
* Add a single replace operation to the bulk operation.
|
||||
*
|
||||
* @param query Update criteria.
|
||||
* @param query Replace criteria. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence
|
||||
* which document to replace when potentially matching multiple candidates.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the current {@link ReactiveBulkOperations} instance with the replace added, will never be {@literal null}.
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.bson.Document;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscription;
|
||||
import org.springframework.data.domain.KeysetScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.geo.GeoResult;
|
||||
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
|
||||
@@ -1502,12 +1503,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* the provided update document.
|
||||
* <p>
|
||||
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be
|
||||
* auto-incremented if not explicitly specified in the update. <strong>NOTE:</strong> {@link Query#getSortObject()
|
||||
* sorting} is not supported by {@code db.collection.updateOne}. Use
|
||||
* {@link #findAndModify(Query, UpdateDefinition, Class)} instead.
|
||||
* auto-incremented if not explicitly specified in the update.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. The
|
||||
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
|
||||
* potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use.
|
||||
@@ -1525,12 +1525,11 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* the provided updated document. <br />
|
||||
* <strong>NOTE:</strong> Any additional support for field mapping, versions, etc. is not available due to the lack of
|
||||
* domain type information. Use {@link #updateFirst(Query, UpdateDefinition, Class, String)} to get full type specific
|
||||
* support. <br />
|
||||
* <strong>NOTE:</strong> {@link Query#getSortObject() sorting} is not supported by {@code db.collection.updateOne}.
|
||||
* Use {@link #findAndModify(Query, UpdateDefinition, Class, String)} instead.
|
||||
* support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. The
|
||||
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
|
||||
* potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
@@ -1548,8 +1547,9 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* A potential {@link org.springframework.data.annotation.Version} property of the {@literal entityClass} will be
|
||||
* auto-incremented if not explicitly specified in the update.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param query the query document that specifies the criteria used to select a document to be updated. The
|
||||
* {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to update when
|
||||
* potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} that contains the updated object or {@code $} operators to manipulate
|
||||
* the existing. Must not be {@literal null}.
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
@@ -1745,7 +1745,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
|
||||
* replace when potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
|
||||
@@ -1762,7 +1763,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
|
||||
* replace when potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
@@ -1778,7 +1780,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document.The query may
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
|
||||
* replace when potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
@@ -1796,7 +1799,8 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a document. The query may *
|
||||
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
|
||||
* to use. Must not be {@literal null}.
|
||||
* to use. The {@link Query} may define a {@link Query#with(Sort) sort order} to influence which document to
|
||||
* replace when potentially matching multiple candidates. Must not be {@literal null}.
|
||||
* @param replacement the replacement document. Must not be {@literal null}.
|
||||
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -30,17 +31,20 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.BulkOperationException;
|
||||
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
|
||||
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.convert.UpdateMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
|
||||
import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
|
||||
import org.springframework.data.mongodb.test.util.MongoTestTemplate;
|
||||
import org.springframework.data.mongodb.test.util.Template;
|
||||
@@ -323,6 +327,39 @@ public class DefaultBulkOperationsIntegrationTests {
|
||||
assertThat(doc).isInstanceOf(SpecialDoc.class);
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
|
||||
public void updateShouldConsiderSorting() {
|
||||
|
||||
insertSomeDocuments();
|
||||
|
||||
BulkWriteResult result = createBulkOps(BulkMode.ORDERED, BaseDocWithRenamedField.class)
|
||||
.updateOne(new Query().with(Sort.by(DESC, "renamedField")), new Update().set("bsky", "altnps")).execute();
|
||||
|
||||
assertThat(result.getModifiedCount()).isOne();
|
||||
|
||||
Document raw = operations.execute(COLLECTION_NAME, col -> col.find(new Document("_id", "4")).first());
|
||||
assertThat(raw).containsEntry("bsky", "altnps");
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
|
||||
public void replaceShouldConsiderSorting() {
|
||||
|
||||
insertSomeDocuments();
|
||||
|
||||
BaseDocWithRenamedField target = new BaseDocWithRenamedField();
|
||||
target.value = "replacement";
|
||||
|
||||
BulkWriteResult result = createBulkOps(BulkMode.ORDERED, BaseDocWithRenamedField.class)
|
||||
.replaceOne(new Query().with(Sort.by(DESC, "renamedField")), target).execute();
|
||||
|
||||
assertThat(result.getModifiedCount()).isOne();
|
||||
|
||||
Document raw = operations.execute(COLLECTION_NAME, col -> col.find(new Document("_id", "4")).first());
|
||||
assertThat(raw).containsEntry("value", target.value);
|
||||
}
|
||||
|
||||
private void testUpdate(BulkMode mode, boolean multi, int expectedUpdates) {
|
||||
|
||||
BulkOperations bulkOps = createBulkOps(mode);
|
||||
@@ -384,10 +421,10 @@ public class DefaultBulkOperationsIntegrationTests {
|
||||
|
||||
final MongoCollection<Document> coll = operations.getCollection(COLLECTION_NAME);
|
||||
|
||||
coll.insertOne(rawDoc("1", "value1"));
|
||||
coll.insertOne(rawDoc("2", "value1"));
|
||||
coll.insertOne(rawDoc("3", "value2"));
|
||||
coll.insertOne(rawDoc("4", "value2"));
|
||||
coll.insertOne(rawDoc("1", "value1").append("rn_f", "001"));
|
||||
coll.insertOne(rawDoc("2", "value1").append("rn_f", "002"));
|
||||
coll.insertOne(rawDoc("3", "value2").append("rn_f", "003"));
|
||||
coll.insertOne(rawDoc("4", "value2").append("rn_f", "004"));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> upsertArguments() {
|
||||
@@ -421,4 +458,10 @@ public class DefaultBulkOperationsIntegrationTests {
|
||||
private static Document rawDoc(String id, String value) {
|
||||
return new Document("_id", id).append("value", value);
|
||||
}
|
||||
|
||||
static class BaseDocWithRenamedField extends BaseDoc {
|
||||
|
||||
@Field("rn_f")
|
||||
String renamedField;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.domain.Sort.Direction.DESC;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
@@ -289,11 +293,48 @@ class DefaultReactiveBulkOperationsTests {
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
|
||||
public void updateShouldConsiderSorting() {
|
||||
|
||||
insertSomeDocuments();
|
||||
|
||||
createBulkOps(BulkMode.ORDERED, BaseDocWithRenamedField.class) //
|
||||
.updateOne(new Query().with(Sort.by(DESC, "renamedField")), new Update().set("bsky", "altnps")).execute() //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(result -> assertThat(result.getModifiedCount()).isOne()) //
|
||||
.verifyComplete();
|
||||
|
||||
template.execute(COLLECTION_NAME, col -> col.find(new Document("_id", "4")).first()).as(StepVerifier::create) //
|
||||
.consumeNextWith(raw -> assertThat(raw).containsEntry("bsky", "altnps")) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
|
||||
public void replaceShouldConsiderSorting() {
|
||||
|
||||
insertSomeDocuments();
|
||||
|
||||
BaseDocWithRenamedField target = new BaseDocWithRenamedField();
|
||||
target.value = "replacement";
|
||||
|
||||
createBulkOps(BulkMode.ORDERED, BaseDocWithRenamedField.class) //
|
||||
.replaceOne(new Query().with(Sort.by(DESC, "renamedField")), target).execute() //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(result -> assertThat(result.getModifiedCount()).isOne()) //
|
||||
.verifyComplete();
|
||||
|
||||
template.execute(COLLECTION_NAME, col -> col.find(new Document("_id", "4")).first()).as(StepVerifier::create) //
|
||||
.consumeNextWith(raw -> assertThat(raw).containsEntry("value", target.value)) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
private void insertSomeDocuments() {
|
||||
|
||||
template.execute(COLLECTION_NAME, collection -> {
|
||||
return Flux.from(collection.insertMany(
|
||||
List.of(rawDoc("1", "value1"), rawDoc("2", "value1"), rawDoc("3", "value2"), rawDoc("4", "value2"))));
|
||||
List.of(rawDoc("1", "value1").append("rn_f", "001"), rawDoc("2", "value1").append("rn_f", "002"), rawDoc("3", "value2").append("rn_f", "003"), rawDoc("4", "value2").append("rn_f", "004"))));
|
||||
}).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
}
|
||||
@@ -345,4 +386,10 @@ class DefaultReactiveBulkOperationsTests {
|
||||
private static Document rawDoc(String id, String value) {
|
||||
return new Document("_id", id).append("value", value);
|
||||
}
|
||||
|
||||
static class BaseDocWithRenamedField extends BaseDoc {
|
||||
|
||||
@Field("rn_f")
|
||||
String renamedField;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,13 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.test.util.Client;
|
||||
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
|
||||
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
||||
|
||||
import com.mongodb.client.MongoClient;
|
||||
@@ -171,6 +175,17 @@ public class MongoTemplateReplaceTests {
|
||||
assertThat(document).containsEntry("r-name", "Pizza Rat's Pizzaria");
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
|
||||
void replaceConsidersSort() {
|
||||
|
||||
UpdateResult result = template.replace(new Query().with(Sort.by(Direction.DESC, "name")), new Restaurant("resist", "Manhattan"));
|
||||
|
||||
assertThat(result.getModifiedCount()).isOne();
|
||||
Document document = retrieve(collection -> collection.find(Filters.eq("_id", 2)).first());
|
||||
assertThat(document).containsEntry("r-name", "resist");
|
||||
}
|
||||
|
||||
void initTestData() {
|
||||
|
||||
List<Document> testData = Stream.of( //
|
||||
|
||||
@@ -4066,64 +4066,6 @@ public class MongoTemplateTests {
|
||||
assertThat(loaded.mapValue).isEqualTo(sourceMap);
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
public void updateFirstWithSortingAscendingShouldUpdateCorrectEntities() {
|
||||
|
||||
PersonWithIdPropertyOfTypeObjectId youngPerson = new PersonWithIdPropertyOfTypeObjectId();
|
||||
youngPerson.setId(new ObjectId());
|
||||
youngPerson.setAge(27);
|
||||
youngPerson.setFirstName("Dave");
|
||||
template.save(youngPerson);
|
||||
|
||||
PersonWithIdPropertyOfTypeObjectId oldPerson = new PersonWithIdPropertyOfTypeObjectId();
|
||||
oldPerson.setId(new ObjectId());
|
||||
oldPerson.setAge(34);
|
||||
oldPerson.setFirstName("Dave");
|
||||
template.save(oldPerson);
|
||||
|
||||
template.updateFirst(query(where("firstName").is("Dave")).with(Sort.by(Direction.ASC, "age")),
|
||||
update("firstName", "Mike"), PersonWithIdPropertyOfTypeObjectId.class);
|
||||
|
||||
PersonWithIdPropertyOfTypeObjectId oldPersonResult = template.findById(oldPerson.getId(),
|
||||
PersonWithIdPropertyOfTypeObjectId.class);
|
||||
assertThat(oldPersonResult).isNotNull();
|
||||
assertThat(oldPersonResult.getFirstName()).isEqualTo("Dave");
|
||||
|
||||
PersonWithIdPropertyOfTypeObjectId youngPersonResult = template.findById(youngPerson.getId(),
|
||||
PersonWithIdPropertyOfTypeObjectId.class);
|
||||
assertThat(youngPersonResult).isNotNull();
|
||||
assertThat(youngPersonResult.getFirstName()).isEqualTo("Mike");
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
public void updateFirstWithSortingDescendingShouldUpdateCorrectEntities() {
|
||||
|
||||
PersonWithIdPropertyOfTypeObjectId youngPerson = new PersonWithIdPropertyOfTypeObjectId();
|
||||
youngPerson.setId(new ObjectId());
|
||||
youngPerson.setAge(27);
|
||||
youngPerson.setFirstName("Dave");
|
||||
template.save(youngPerson);
|
||||
|
||||
PersonWithIdPropertyOfTypeObjectId oldPerson = new PersonWithIdPropertyOfTypeObjectId();
|
||||
oldPerson.setId(new ObjectId());
|
||||
oldPerson.setAge(34);
|
||||
oldPerson.setFirstName("Dave");
|
||||
template.save(oldPerson);
|
||||
|
||||
template.updateFirst(query(where("firstName").is("Dave")).with(Sort.by(Direction.DESC, "age")),
|
||||
update("firstName", "Mike"), PersonWithIdPropertyOfTypeObjectId.class);
|
||||
|
||||
PersonWithIdPropertyOfTypeObjectId oldPersonResult = template.findById(oldPerson.getId(),
|
||||
PersonWithIdPropertyOfTypeObjectId.class);
|
||||
assertThat(oldPersonResult).isNotNull();
|
||||
assertThat(oldPersonResult.getFirstName()).isEqualTo("Mike");
|
||||
|
||||
PersonWithIdPropertyOfTypeObjectId youngPersonResult = template.findById(youngPerson.getId(),
|
||||
PersonWithIdPropertyOfTypeObjectId.class);
|
||||
assertThat(youngPersonResult).isNotNull();
|
||||
assertThat(youngPersonResult.getFirstName()).isEqualTo("Dave");
|
||||
}
|
||||
|
||||
private AtomicReference<ImmutableVersioned> createAfterSaveReference() {
|
||||
|
||||
AtomicReference<ImmutableVersioned> saved = new AtomicReference<>();
|
||||
|
||||
@@ -15,19 +15,26 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.ReplaceWithOperation;
|
||||
@@ -37,6 +44,7 @@ import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
|
||||
import org.springframework.data.mongodb.test.util.MongoTemplateExtension;
|
||||
import org.springframework.data.mongodb.test.util.MongoTestTemplate;
|
||||
@@ -123,13 +131,13 @@ class MongoTemplateUpdateTests {
|
||||
Versioned source = template.insert(Versioned.class).one(new Versioned("id-1", "value-0"));
|
||||
|
||||
AggregationUpdate update = AggregationUpdate.update()
|
||||
.set(SetOperation.builder().set("value").toValue("changed").and().set("version").toValue(10L));
|
||||
.set(SetOperation.builder().set("value").toValue("changed").and().set("version").toValue(10L));
|
||||
template.update(Versioned.class).matching(Query.query(Criteria.where("id").is(source.id))).apply(update).first();
|
||||
|
||||
assertThat(
|
||||
collection(Versioned.class).find(new org.bson.Document("_id", source.id)).limit(1).into(new ArrayList<>()))
|
||||
.containsExactly(new org.bson.Document("_id", source.id).append("version", 10L).append("value", "changed")
|
||||
.append("_class", "org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Versioned"));
|
||||
collection(Versioned.class).find(new org.bson.Document("_id", source.id)).limit(1).into(new ArrayList<>()))
|
||||
.containsExactly(new org.bson.Document("_id", source.id).append("version", 10L).append("value", "changed")
|
||||
.append("_class", "org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Versioned"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
@@ -289,6 +297,35 @@ class MongoTemplateUpdateTests {
|
||||
null);
|
||||
}
|
||||
|
||||
@ParameterizedTest // GH-4797
|
||||
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
|
||||
@MethodSource("sortedUpdateBookArgs")
|
||||
void updateFirstWithSort(Class<?> domainType, Sort sort, UpdateDefinition update) {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.isbn = "001 001 300";
|
||||
one.title = "News isn't fake";
|
||||
one.author = new Author("John", "Backus");
|
||||
|
||||
Book two = new Book();
|
||||
two.id = 2;
|
||||
two.title = "love is love";
|
||||
two.isbn = "001 001 100";
|
||||
two.author = new Author("Grace", "Hopper");
|
||||
|
||||
template.insertAll(Arrays.asList(one, two));
|
||||
|
||||
UpdateResult result = template.update(domainType) //
|
||||
.inCollection(template.getCollectionName(Book.class))//
|
||||
.matching(new Query().with(sort)).apply(update) //
|
||||
.first();
|
||||
|
||||
assertThat(result.getModifiedCount()).isOne();
|
||||
assertThat(collection(Book.class).find(new org.bson.Document("_id", two.id)).first()).containsEntry("title",
|
||||
"Science is real!");
|
||||
}
|
||||
|
||||
private List<org.bson.Document> all(Class<?> type) {
|
||||
return collection(type).find(new org.bson.Document()).into(new ArrayList<>());
|
||||
}
|
||||
@@ -297,6 +334,21 @@ class MongoTemplateUpdateTests {
|
||||
return template.getCollection(template.getCollectionName(type));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> sortedUpdateBookArgs() {
|
||||
|
||||
Update update = new Update().set("title", "Science is real!");
|
||||
AggregationUpdate aggUpdate = AggregationUpdate.update().set("title").toValue("Science is real!");
|
||||
|
||||
return Stream.of( //
|
||||
Arguments.of(Book.class, Sort.by(Direction.ASC, "isbn"), update), // typed, no field mapping
|
||||
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.lastname"), update), // typed, map `lastname`
|
||||
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.last"), update), // typed, raw field name
|
||||
Arguments.of(Object.class, Sort.by(Direction.ASC, "isbn"), update), // untyped, requires raw field name
|
||||
Arguments.of(Book.class, Sort.by(Direction.ASC, "isbn"), aggUpdate), // aggregation, no field mapping
|
||||
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.last"), aggUpdate) // aggregation, map `lastname`
|
||||
);
|
||||
}
|
||||
|
||||
@Document("scores")
|
||||
static class Score {
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.springframework.data.mongodb.core.ReplaceOptions.*;
|
||||
import static org.springframework.data.mongodb.core.query.Criteria.*;
|
||||
import static org.springframework.data.mongodb.core.query.Query.*;
|
||||
|
||||
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
@@ -37,7 +38,10 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.test.util.Client;
|
||||
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
||||
|
||||
@@ -198,7 +202,23 @@ public class ReactiveMongoTemplateReplaceTests {
|
||||
retrieve(collection -> collection.find(Filters.eq("_id", 4)).first()).as(StepVerifier::create)
|
||||
.consumeNextWith(document -> {
|
||||
assertThat(document).containsEntry("r-name", "Pizza Rat's Pizzaria");
|
||||
});
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
|
||||
void replaceConsidersSort() {
|
||||
|
||||
template.replace(new Query().with(Sort.by(Direction.DESC, "name")), new Restaurant("resist", "Manhattan")) //
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(result -> assertThat(result.getModifiedCount()).isOne()) //
|
||||
.verifyComplete();
|
||||
|
||||
retrieve(collection -> collection.find(Filters.eq("_id", 2)).first()).as(StepVerifier::create)
|
||||
.consumeNextWith(document -> {
|
||||
assertThat(document).containsEntry("r-name", "resist");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
void initTestData() {
|
||||
|
||||
@@ -1846,42 +1846,6 @@ public class ReactiveMongoTemplateTests {
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
public void updateFirstWithSortingAscendingShouldUpdateCorrectEntities() {
|
||||
|
||||
Person youngPerson = new Person("Dave", 27);
|
||||
Person oldPerson = new Person("Dave", 34);
|
||||
|
||||
template.insertAll(List.of(youngPerson, oldPerson))
|
||||
.then(template.updateFirst(new Query(where("firstName").is("Dave")).with(Sort.by(Direction.ASC, "age")),
|
||||
new Update().set("firstName", "Carter"), Person.class))
|
||||
.flatMapMany(p -> template.find(new Query().with(Sort.by(Direction.ASC, "age")), Person.class))
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
assertThat(actual.getFirstName()).isEqualTo("Carter");
|
||||
}).consumeNextWith(actual -> {
|
||||
assertThat(actual.getFirstName()).isEqualTo("Dave");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-4797
|
||||
public void updateFirstWithSortingDescendingShouldUpdateCorrectEntities() {
|
||||
|
||||
Person youngPerson = new Person("Dave", 27);
|
||||
Person oldPerson = new Person("Dave", 34);
|
||||
|
||||
template.insertAll(List.of(youngPerson, oldPerson))
|
||||
.then(template.updateFirst(new Query(where("firstName").is("Dave")).with(Sort.by(Direction.DESC, "age")),
|
||||
new Update().set("firstName", "Carter"), Person.class))
|
||||
.flatMapMany(p -> template.find(new Query().with(Sort.by(Direction.ASC, "age")), Person.class))
|
||||
.as(StepVerifier::create) //
|
||||
.consumeNextWith(actual -> {
|
||||
assertThat(actual.getFirstName()).isEqualTo("Dave");
|
||||
}).consumeNextWith(actual -> {
|
||||
assertThat(actual.getFirstName()).isEqualTo("Carter");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
private PersonWithAList createPersonWithAList(String firstname, int age) {
|
||||
|
||||
PersonWithAList p = new PersonWithAList();
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.core;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -25,12 +26,18 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.ReplaceWithOperation;
|
||||
@@ -39,6 +46,8 @@ import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.test.util.Client;
|
||||
import org.springframework.data.mongodb.test.util.EnableIfMongoServerVersion;
|
||||
import org.springframework.data.mongodb.test.util.MongoClientExtension;
|
||||
@@ -269,6 +278,39 @@ public class ReactiveMongoTemplateUpdateTests {
|
||||
|
||||
}
|
||||
|
||||
@ParameterizedTest // GH-4797
|
||||
@EnableIfMongoServerVersion(isGreaterThanEqual = "8.0")
|
||||
@MethodSource("sortedUpdateBookArgs")
|
||||
void updateFirstWithSort(Class<?> domainType, Sort sort, UpdateDefinition update) {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.isbn = "001 001 300";
|
||||
one.title = "News isn't fake";
|
||||
one.author = new Author("John", "Backus");
|
||||
|
||||
Book two = new Book();
|
||||
two.id = 2;
|
||||
two.title = "love is love";
|
||||
two.isbn = "001 001 100";
|
||||
two.author = new Author("Grace", "Hopper");
|
||||
|
||||
template.insertAll(Arrays.asList(one, two)).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
template.update(domainType) //
|
||||
.inCollection(template.getCollectionName(Book.class))//
|
||||
.matching(new Query().with(sort)).apply(update) //
|
||||
.first().as(StepVerifier::create) //
|
||||
.assertNext(result -> assertThat(result.getModifiedCount()).isOne()) //
|
||||
.verifyComplete();
|
||||
|
||||
Mono.from(collection(Book.class).find(new org.bson.Document("_id", two.id)).first()) //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(document -> assertThat(document).containsEntry("title", "Science is real!")) //
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
|
||||
private Flux<org.bson.Document> all(Class<?> type) {
|
||||
return Flux.from(collection(type).find(new org.bson.Document()));
|
||||
}
|
||||
@@ -277,6 +319,21 @@ public class ReactiveMongoTemplateUpdateTests {
|
||||
return client.getDatabase(DB_NAME).getCollection(template.getCollectionName(type));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> sortedUpdateBookArgs() {
|
||||
|
||||
Update update = new Update().set("title", "Science is real!");
|
||||
AggregationUpdate aggUpdate = AggregationUpdate.update().set("title").toValue("Science is real!");
|
||||
|
||||
return Stream.of( //
|
||||
Arguments.of(Book.class, Sort.by(Direction.ASC, "isbn"), update), // typed, no field mapping
|
||||
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.lastname"), update), // typed, map `lastname`
|
||||
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.last"), update), // typed, raw field name
|
||||
Arguments.of(Object.class, Sort.by(Direction.ASC, "isbn"), update), // untyped, requires raw field name
|
||||
Arguments.of(Book.class, Sort.by(Direction.ASC, "isbn"), aggUpdate), // aggregation, no field mapping
|
||||
Arguments.of(Book.class, Sort.by(Direction.DESC, "author.last"), aggUpdate) // aggregation, map `lastname`
|
||||
);
|
||||
}
|
||||
|
||||
@Document("scores")
|
||||
static class Score {
|
||||
|
||||
|
||||
@@ -356,7 +356,7 @@ Read more in the see xref:mongodb/template-crud-operations.adoc#mongo-template.o
|
||||
* *updateFirst*: Updates the first document that matches the query document criteria with the updated document.
|
||||
* *updateMulti*: Updates all objects that match the query document criteria with the updated document.
|
||||
|
||||
WARNING: `updateFirst` does not support ordering. Please use xref:mongodb/template-crud-operations.adoc#mongo-template.find-and-upsert[findAndModify] to apply `Sort`.
|
||||
WARNING: `updateFirst` does not support ordering for MongoDB Versions below 8.0. Running one of the older versions, please use xref:mongodb/template-crud-operations.adoc#mongo-template.find-and-upsert[findAndModify] to apply `Sort`.
|
||||
|
||||
NOTE: Index hints for the update operation can be provided via `Query.withHint(...)`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user