DATAMONGO-2331 - Add support for Update with an aggregation pipeline.
Now the update methods exposed by (Reactive)MongoOperations also accept an Aggregation Pipeline via AggregationUpdate.
The update can consist of the following stages:
* AggregationUpdate.set(...).toValue(...) -> $set : { ... }
* AggregationUpdate.unset(...) -> $unset : [ ... ]
* AggregationUpdate.replaceWith(...) -> $replaceWith : { ... }
AggregationUpdate update = Aggregation.newUpdate()
.set("average").toValue(ArithmeticOperators.valueOf("tests").avg())
.set("grade").toValue(ConditionalOperators.switchCases(
when(valueOf("average").greaterThanEqualToValue(90)).then("A"),
when(valueOf("average").greaterThanEqualToValue(80)).then("B"),
when(valueOf("average").greaterThanEqualToValue(70)).then("C"),
when(valueOf("average").greaterThanEqualToValue(60)).then("D"))
.defaultTo("F")
);
template.update(Student.class)
.apply(update)
.all();
Original pull request: #789.
This commit is contained in:
committed by
Mark Paluch
parent
9eaf67148d
commit
32cbae0e5f
@@ -19,6 +19,7 @@ import java.util.Optional;
|
||||
|
||||
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.lang.Nullable;
|
||||
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
@@ -151,13 +152,26 @@ public interface ExecutableUpdateOperation {
|
||||
interface UpdateWithUpdate<T> {
|
||||
|
||||
/**
|
||||
* Set the {@link Update} to be applied.
|
||||
* Set the {@link UpdateDefinition} to be applied.
|
||||
*
|
||||
* @param update must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingUpdate}.
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
*/
|
||||
TerminatingUpdate<T> apply(Update update);
|
||||
TerminatingUpdate<T> apply(UpdateDefinition update);
|
||||
|
||||
/**
|
||||
* Set the {@link Update} to be applied.
|
||||
*
|
||||
* @param update must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingUpdate}.
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
* @deprecated since 2.3 in favor of {@link #apply(UpdateDefinition)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default TerminatingUpdate<T> apply(Update update) {
|
||||
return apply((UpdateDefinition) update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify {@code replacement} object.
|
||||
|
||||
@@ -21,7 +21,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
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.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -67,7 +67,7 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
@NonNull MongoTemplate template;
|
||||
@NonNull Class domainType;
|
||||
Query query;
|
||||
@Nullable Update update;
|
||||
@Nullable UpdateDefinition update;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@@ -76,10 +76,10 @@ class ExecutableUpdateOperationSupport implements ExecutableUpdateOperation {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateWithUpdate#apply(Update)
|
||||
* @see org.springframework.data.mongodb.core.ExecutableUpdateOperation.UpdateWithUpdate#apply(org.springframework.data.mongodb.core.query.UpdateDefinition)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingUpdate<T> apply(Update update) {
|
||||
public TerminatingUpdate<T> apply(UpdateDefinition update) {
|
||||
|
||||
Assert.notNull(update, "Update must not be null!");
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
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.util.CloseableIterator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -57,7 +58,7 @@ import com.mongodb.client.result.UpdateResult;
|
||||
* Interface that specifies a basic set of MongoDB operations. Implemented by {@link MongoTemplate}. Not often used but
|
||||
* a useful option for extensibility and testability (as it can be easily mocked, stubbed, or be the target of a JDK
|
||||
* proxy).
|
||||
* <p />
|
||||
* <p/>
|
||||
* <strong>NOTE:</strong> Some operations cannot be executed within a MongoDB transaction. Please refer to the MongoDB
|
||||
* specific documentation to learn more about <a href="https://docs.mongodb.com/manual/core/transactions/">Multi
|
||||
* Document Transactions</a>.
|
||||
@@ -175,7 +176,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
/**
|
||||
* Obtain a {@link ClientSession session} bound instance of {@link SessionScoped} binding the {@link ClientSession}
|
||||
* provided by the given {@link Supplier} to each and every command issued against MongoDB.
|
||||
* <p />
|
||||
* <p/>
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle. Use the
|
||||
* {@link SessionScoped#execute(SessionCallback, Consumer)} hook to potentially close the {@link ClientSession}.
|
||||
*
|
||||
@@ -211,7 +212,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
|
||||
/**
|
||||
* Obtain a {@link ClientSession} bound instance of {@link MongoOperations}.
|
||||
* <p />
|
||||
* <p/>
|
||||
* <strong>Note:</strong> It is up to the caller to manage the {@link ClientSession} lifecycle.
|
||||
*
|
||||
* @param session must not be {@literal null}.
|
||||
@@ -653,7 +654,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* {@code $geoNear} aggregation command to emulate {@code geoNear} command functionality. We recommend using
|
||||
* aggregations directly:
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <pre class="code">
|
||||
* TypedAggregation<T> geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
|
||||
* .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
|
||||
@@ -678,7 +679,7 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* {@code $geoNear} aggregation command to emulate {@code geoNear} command functionality. We recommend using
|
||||
* aggregations directly:
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <pre class="code">
|
||||
* TypedAggregation<T> geoNear = TypedAggregation.newAggregation(entityClass, Aggregation.geoNear(near, "dis"))
|
||||
* .withOptions(AggregationOptions.builder().collation(near.getCollation()).build());
|
||||
@@ -877,6 +878,20 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
return findDistinct(query, field, collection, Object.class, resultClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @return the converted object that was updated before it was updated or {@literal null}, if not found.
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
@@ -886,9 +901,28 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @param update the {@link Update} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @return the converted object that was updated before it was updated or {@literal null}, if not found.
|
||||
* @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, Class)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
default <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
|
||||
return findAndModify(query, (UpdateDefinition) update, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated before it was updated or {@literal null}, if not found.
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findAndModify(Query query, Update update, Class<T> entityClass);
|
||||
<T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
@@ -900,9 +934,31 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated before it was updated or {@literal null}, if not found.
|
||||
* @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, Class, String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
default <T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName) {
|
||||
return findAndModify(query, (UpdateDefinition) update, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information.
|
||||
* @param entityClass the parametrized type.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndModifyOptions#isReturnNew()} this will either be the object as it was before the update or as
|
||||
* it is after the update.
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName);
|
||||
<T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
@@ -917,9 +973,33 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndModifyOptions#isReturnNew()} this will either be the object as it was before the update or as
|
||||
* it is after the update.
|
||||
* @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class)}
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass);
|
||||
@Deprecated
|
||||
default <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass) {
|
||||
return findAndModify(query, (UpdateDefinition) update, options, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndModifyOptions#isReturnNew()} this will either be the object as it was before the update or as
|
||||
* it is after the update.
|
||||
* @since 2.3
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify <a/>
|
||||
@@ -935,10 +1015,15 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @return the converted object that was updated or {@literal null}, if not found. Depending on the value of
|
||||
* {@link FindAndModifyOptions#isReturnNew()} this will either be the object as it was before the update or as
|
||||
* it is after the update.
|
||||
* @deprecated since 2.3 in favor of
|
||||
* {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class, String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
<T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
default <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName) {
|
||||
return findAndModify(query, (UpdateDefinition) update, options, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
@@ -1294,8 +1379,26 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* object. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
UpdateResult upsert(Query query, Update update, Class<?> entityClass);
|
||||
UpdateResult upsert(Query query, UpdateDefinition update, Class<?> entityClass);
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
* combining the query document and the update document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ operators to manipulate the existing
|
||||
* object. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, Class)}
|
||||
*/
|
||||
@Deprecated
|
||||
default UpdateResult upsert(Query query, Update update, Class<?> entityClass) {
|
||||
return upsert(query, (UpdateDefinition) update, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
@@ -1312,8 +1415,43 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* object. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
UpdateResult upsert(Query query, Update update, String collectionName);
|
||||
UpdateResult upsert(Query query, UpdateDefinition update, String collectionName);
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
* combining the query document and the update 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 #upsert(Query, Update, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ operators to manipulate the existing
|
||||
* object. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
default UpdateResult upsert(Query query, Update update, String collectionName) {
|
||||
return upsert(query, (UpdateDefinition) update, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
* combining the query document and the update document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ operators to manipulate the existing
|
||||
* object. Must not be {@literal null}.
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
UpdateResult upsert(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
@@ -1328,8 +1466,26 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, Class, String)}
|
||||
*/
|
||||
UpdateResult upsert(Query query, Update update, Class<?> entityClass, String collectionName);
|
||||
@Deprecated
|
||||
default UpdateResult upsert(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
return upsert(query, (UpdateDefinition) update, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the collection of the entity class that matches the query document with
|
||||
* the provided update document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
|
||||
* not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
UpdateResult updateFirst(Query query, UpdateDefinition update, Class<?> entityClass);
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the collection of the entity class that matches the query document with
|
||||
@@ -1343,8 +1499,12 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, Class)}.
|
||||
*/
|
||||
UpdateResult updateFirst(Query query, Update update, Class<?> entityClass);
|
||||
@Deprecated
|
||||
default UpdateResult updateFirst(Query query, Update update, Class<?> entityClass) {
|
||||
return updateFirst(query, (UpdateDefinition) update, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
@@ -1361,8 +1521,43 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
UpdateResult updateFirst(Query query, Update update, String collectionName);
|
||||
UpdateResult updateFirst(Query query, UpdateDefinition update, String collectionName);
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
* 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, Update, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default UpdateResult updateFirst(Query query, Update update, String collectionName) {
|
||||
return updateFirst(query, (UpdateDefinition) update, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
* the provided updated document. <br />
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
UpdateResult updateFirst(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
@@ -1377,8 +1572,12 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, Class, String)}.
|
||||
*/
|
||||
UpdateResult updateFirst(Query query, Update update, Class<?> entityClass, String collectionName);
|
||||
@Deprecated
|
||||
default UpdateResult updateFirst(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
return updateFirst(query, (UpdateDefinition) update, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
@@ -1390,8 +1589,26 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* not be {@literal null}.
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
UpdateResult updateMulti(Query query, Update update, Class<?> entityClass);
|
||||
UpdateResult updateMulti(Query query, UpdateDefinition update, Class<?> entityClass);
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
* with the provided updated document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, Class)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default UpdateResult updateMulti(Query query, Update update, Class<?> entityClass) {
|
||||
return updateMulti(query, (UpdateDefinition) update, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the specified collection that matches the query document criteria with the
|
||||
@@ -1405,8 +1622,28 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
UpdateResult updateMulti(Query query, Update update, String collectionName);
|
||||
UpdateResult updateMulti(Query query, UpdateDefinition update, String collectionName);
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the specified collection that matches the query document criteria with 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 #updateMulti(Query, Update, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default UpdateResult updateMulti(Query query, Update update, String collectionName) {
|
||||
return updateMulti(query, (UpdateDefinition) update, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
@@ -1419,8 +1656,27 @@ public interface MongoOperations extends FluentMongoOperations {
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
UpdateResult updateMulti(Query query, Update update, Class<?> entityClass, String collectionName);
|
||||
UpdateResult updateMulti(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
* with the provided updated document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, Class, String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default UpdateResult updateMulti(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
return updateMulti(query, (UpdateDefinition) update, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given object from the collection by {@literal id} and (if applicable) its
|
||||
|
||||
@@ -69,7 +69,9 @@ import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.Fields;
|
||||
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
@@ -109,7 +111,6 @@ import org.springframework.data.mongodb.core.query.Meta;
|
||||
import org.springframework.data.mongodb.core.query.Meta.CursorOption;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
||||
import org.springframework.data.mongodb.core.validation.Validator;
|
||||
@@ -1046,25 +1047,25 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
|
||||
public <T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass) {
|
||||
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, getCollectionName(entityClass));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T findAndModify(Query query, Update update, Class<T> entityClass, String collectionName) {
|
||||
public <T> T findAndModify(Query query, UpdateDefinition update, Class<T> entityClass, String collectionName) {
|
||||
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass) {
|
||||
public <T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass) {
|
||||
return findAndModify(query, update, options, entityClass, getCollectionName(entityClass));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T> T findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
public <T> T findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null!");
|
||||
@@ -1564,17 +1565,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult upsert(Query query, Update update, Class<?> entityClass) {
|
||||
public UpdateResult upsert(Query query, UpdateDefinition update, Class<?> entityClass) {
|
||||
return doUpdate(getCollectionName(entityClass), query, update, entityClass, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult upsert(Query query, Update update, String collectionName) {
|
||||
public UpdateResult upsert(Query query, UpdateDefinition update, String collectionName) {
|
||||
return doUpdate(collectionName, query, update, null, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult upsert(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
public UpdateResult upsert(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName) {
|
||||
|
||||
Assert.notNull(entityClass, "EntityClass must not be null!");
|
||||
|
||||
@@ -1582,17 +1583,17 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult updateFirst(Query query, Update update, Class<?> entityClass) {
|
||||
public UpdateResult updateFirst(Query query, UpdateDefinition update, Class<?> entityClass) {
|
||||
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult updateFirst(final Query query, final Update update, final String collectionName) {
|
||||
public UpdateResult updateFirst(final Query query, final UpdateDefinition update, final String collectionName) {
|
||||
return doUpdate(collectionName, query, update, null, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult updateFirst(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
public UpdateResult updateFirst(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName) {
|
||||
|
||||
Assert.notNull(entityClass, "EntityClass must not be null!");
|
||||
|
||||
@@ -1600,17 +1601,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult updateMulti(Query query, Update update, Class<?> entityClass) {
|
||||
public UpdateResult updateMulti(Query query, UpdateDefinition update, Class<?> entityClass) {
|
||||
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult updateMulti(final Query query, final Update update, String collectionName) {
|
||||
public UpdateResult updateMulti(final Query query, final UpdateDefinition update, String collectionName) {
|
||||
return doUpdate(collectionName, query, update, null, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateResult updateMulti(final Query query, final Update update, Class<?> entityClass, String collectionName) {
|
||||
public UpdateResult updateMulti(final Query query, final UpdateDefinition update, Class<?> entityClass,
|
||||
String collectionName) {
|
||||
|
||||
Assert.notNull(entityClass, "EntityClass must not be null!");
|
||||
|
||||
@@ -1631,25 +1633,53 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
upsert ? "Upsert" : "UpdateFirst", serializeToJsonSafely(query.getSortObject()));
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
|
||||
increaseVersionForUpdateIfNecessary(entity, update);
|
||||
|
||||
UpdateOptions opts = new UpdateOptions();
|
||||
opts.upsert(upsert);
|
||||
|
||||
if (update.hasArrayFilters()) {
|
||||
opts.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
Document queryObj = new Document();
|
||||
|
||||
if (query != null) {
|
||||
queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
|
||||
}
|
||||
|
||||
if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) {
|
||||
queryObj.put("$isolated", 1);
|
||||
}
|
||||
|
||||
if (update instanceof AggregationUpdate) {
|
||||
|
||||
AggregationOperationContext context = entityClass != null
|
||||
? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
|
||||
: Aggregation.DEFAULT_CONTEXT;
|
||||
|
||||
AggregationUpdate aUppdate = ((AggregationUpdate) update);
|
||||
List<Document> pipeline = new AggregationUtil(queryMapper, mappingContext).createPipeline(aUppdate, context);
|
||||
|
||||
return execute(collectionName, collection -> {
|
||||
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName,
|
||||
entityClass, update.getUpdateObject(), queryObj);
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
|
||||
collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
|
||||
|
||||
if (multi) {
|
||||
return collection.updateMany(queryObj, pipeline, opts);
|
||||
}
|
||||
|
||||
return collection.updateOne(queryObj, pipeline, opts);
|
||||
});
|
||||
}
|
||||
|
||||
return execute(collectionName, collection -> {
|
||||
|
||||
MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
|
||||
|
||||
increaseVersionForUpdateIfNecessary(entity, update);
|
||||
|
||||
UpdateOptions opts = new UpdateOptions();
|
||||
opts.upsert(upsert);
|
||||
|
||||
if (update.hasArrayFilters()) {
|
||||
opts.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
Document queryObj = new Document();
|
||||
|
||||
if (query != null) {
|
||||
queryObj.putAll(queryMapper.getMappedObject(query.getQueryObject(), entity));
|
||||
}
|
||||
|
||||
operations.forType(entityClass) //
|
||||
.getCollation(query) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
@@ -1658,10 +1688,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
Document updateObj = update instanceof MappedUpdate ? update.getUpdateObject()
|
||||
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
|
||||
if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) {
|
||||
queryObj.put("$isolated", 1);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Calling update using query: {} and update: {} in collection: {}", serializeToJsonSafely(queryObj),
|
||||
serializeToJsonSafely(updateObj), collectionName);
|
||||
@@ -2653,7 +2679,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
protected <T> T doFindAndModify(String collectionName, Document query, Document fields, Document sort,
|
||||
Class<T> entityClass, Update update, @Nullable FindAndModifyOptions options) {
|
||||
Class<T> entityClass, UpdateDefinition update, @Nullable FindAndModifyOptions options) {
|
||||
|
||||
EntityReader<? super T, Bson> readerToUse = this.mongoConverter;
|
||||
|
||||
@@ -2666,7 +2692,18 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
increaseVersionForUpdateIfNecessary(entity, update);
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(query, entity);
|
||||
Document mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
|
||||
Object mappedUpdate = new Document();
|
||||
if (update instanceof AggregationUpdate) {
|
||||
|
||||
AggregationOperationContext context = entityClass != null
|
||||
? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
|
||||
: Aggregation.DEFAULT_CONTEXT;
|
||||
|
||||
mappedUpdate = new AggregationUtil(queryMapper, mappingContext).createPipeline((Aggregation) update, context);
|
||||
} else {
|
||||
mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(
|
||||
@@ -3040,11 +3077,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final Document sort;
|
||||
private final Document update;
|
||||
private final Object update;
|
||||
private final List<Document> arrayFilters;
|
||||
private final FindAndModifyOptions options;
|
||||
|
||||
public FindAndModifyCallback(Document query, Document fields, Document sort, Document update,
|
||||
public FindAndModifyCallback(Document query, Document fields, Document sort, Object update,
|
||||
List<Document> arrayFilters, FindAndModifyOptions options) {
|
||||
this.query = query;
|
||||
this.fields = fields;
|
||||
@@ -3072,7 +3109,12 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
|
||||
opts.arrayFilters(arrayFilters);
|
||||
}
|
||||
|
||||
return collection.findOneAndUpdate(query, update, opts);
|
||||
if (update instanceof Document) {
|
||||
return collection.findOneAndUpdate(query, (Document) update, opts);
|
||||
} else if (update instanceof List) {
|
||||
return collection.findOneAndUpdate(query, (List<Document>) update, opts);
|
||||
}
|
||||
throw new IllegalArgumentException("doh - that does not work");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
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.lang.Nullable;
|
||||
import org.springframework.transaction.reactive.TransactionalOperator;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -672,6 +673,19 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
@Deprecated
|
||||
<T> Flux<GeoResult<T>> geoNear(NearQuery near, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @return the converted object that was updated before it was updated.
|
||||
* @since 2.3
|
||||
*/
|
||||
<T> Mono<T> findAndModify(Query query, UpdateDefinition update, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
@@ -681,8 +695,26 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* @param update the {@link Update} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @return the converted object that was updated before it was updated.
|
||||
* @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, Class)}.
|
||||
*/
|
||||
<T> Mono<T> findAndModify(Query query, Update update, Class<T> entityClass);
|
||||
@Deprecated
|
||||
default <T> Mono<T> findAndModify(Query query, Update update, Class<T> entityClass) {
|
||||
return findAndModify(query, (UpdateDefinition) update, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query}.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated before it was updated.
|
||||
* @since 2.3
|
||||
*/
|
||||
<T> Mono<T> findAndModify(Query query, UpdateDefinition update, Class<T> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
@@ -694,8 +726,28 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated before it was updated.
|
||||
* @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, Class, String)}.
|
||||
*/
|
||||
<T> Mono<T> findAndModify(Query query, Update update, Class<T> entityClass, String collectionName);
|
||||
@Deprecated
|
||||
default <T> Mono<T> findAndModify(Query query, Update update, Class<T> entityClass, String collectionName) {
|
||||
return findAndModify(query, (UpdateDefinition) update, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information.
|
||||
* @param entityClass the parametrized type.
|
||||
* @return the converted object that was updated. Depending on the value of {@link FindAndModifyOptions#isReturnNew()}
|
||||
* this will either be the object as it was before the update or as it is after the update.
|
||||
* @since 2.3
|
||||
*/
|
||||
<T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
@@ -709,8 +761,30 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* @param entityClass the parametrized type.
|
||||
* @return the converted object that was updated. Depending on the value of {@link FindAndModifyOptions#isReturnNew()}
|
||||
* this will either be the object as it was before the update or as it is after the update.
|
||||
* @deprecated since 2.3 in favor of {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class)}.
|
||||
*/
|
||||
<T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass);
|
||||
@Deprecated
|
||||
default <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass) {
|
||||
return findAndModify(query, (UpdateDefinition) update, options, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
* to apply provided {@link Update} on documents matching {@link Criteria} of given {@link Query} taking
|
||||
* {@link FindAndModifyOptions} into account.
|
||||
*
|
||||
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record and also an optional
|
||||
* fields specification. Must not be {@literal null}.
|
||||
* @param update the {@link UpdateDefinition} to apply on matching documents. Must not be {@literal null}.
|
||||
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
|
||||
* @param entityClass the parametrized type. Must not be {@literal null}.
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated. Depending on the value of {@link FindAndModifyOptions#isReturnNew()}
|
||||
* this will either be the object as it was before the update or as it is after the update.
|
||||
* @since 2.3
|
||||
*/
|
||||
<T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
|
||||
/**
|
||||
* Triggers <a href="https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/">findAndModify<a/>
|
||||
@@ -725,9 +799,14 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* @param collectionName the collection to query. Must not be {@literal null}.
|
||||
* @return the converted object that was updated. Depending on the value of {@link FindAndModifyOptions#isReturnNew()}
|
||||
* this will either be the object as it was before the update or as it is after the update.
|
||||
* @deprecated since 2.3 in favor of
|
||||
* {@link #findAndModify(Query, UpdateDefinition, FindAndModifyOptions, Class, String)}.
|
||||
*/
|
||||
<T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName);
|
||||
@Deprecated
|
||||
default <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName) {
|
||||
return findAndModify(query, (UpdateDefinition) update, options, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers
|
||||
@@ -1157,8 +1236,42 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* object. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<UpdateResult> upsert(Query query, Update update, Class<?> entityClass);
|
||||
Mono<UpdateResult> upsert(Query query, UpdateDefinition update, Class<?> entityClass);
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
* combining the query document and the update document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ operators to manipulate the existing
|
||||
* object. Must not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, Class)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<UpdateResult> upsert(Query query, Update update, Class<?> entityClass) {
|
||||
return upsert(query, (UpdateDefinition) update, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
* combining the query document and the update 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 #upsert(Query, Update, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ operators to manipulate the existing
|
||||
* object. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<UpdateResult> upsert(Query query, UpdateDefinition update, String collectionName);
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
@@ -1175,8 +1288,27 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* object. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, String)}.
|
||||
*/
|
||||
Mono<UpdateResult> upsert(Query query, Update update, String collectionName);
|
||||
@Deprecated
|
||||
default Mono<UpdateResult> upsert(Query query, Update update, String collectionName) {
|
||||
return upsert(query, (UpdateDefinition) update, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
* combining the query document and the update document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be upserted. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ operators to manipulate the existing
|
||||
* object. Must not be {@literal null}.
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<UpdateResult> upsert(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Performs an upsert. If no document is found that matches the query, a new document is created and inserted by
|
||||
@@ -1191,8 +1323,12 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #upsert(Query, UpdateDefinition, Class, String)}.
|
||||
*/
|
||||
Mono<UpdateResult> upsert(Query query, Update update, Class<?> entityClass, String collectionName);
|
||||
@Deprecated
|
||||
default Mono<UpdateResult> upsert(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
return upsert(query, (UpdateDefinition) update, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the collection of the entity class that matches the query document with
|
||||
@@ -1206,8 +1342,26 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<UpdateResult> updateFirst(Query query, Update update, Class<?> entityClass);
|
||||
Mono<UpdateResult> updateFirst(Query query, UpdateDefinition update, Class<?> entityClass);
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the collection of the entity class that matches the query document with
|
||||
* the provided update document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ operators to manipulate the existing. Must
|
||||
* not be {@literal null}.
|
||||
* @param entityClass class that determines the collection to use.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, Class)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<UpdateResult> updateFirst(Query query, Update update, Class<?> entityClass) {
|
||||
return updateFirst(query, (UpdateDefinition) update, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
@@ -1224,8 +1378,28 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<UpdateResult> updateFirst(Query query, Update update, String collectionName);
|
||||
Mono<UpdateResult> updateFirst(Query query, UpdateDefinition update, String collectionName);
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
* 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, Update, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<UpdateResult> updateFirst(Query query, Update update, String collectionName) {
|
||||
return updateFirst(query, (UpdateDefinition) update, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
@@ -1240,8 +1414,27 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<UpdateResult> updateFirst(Query query, Update update, Class<?> entityClass, String collectionName);
|
||||
Mono<UpdateResult> updateFirst(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Updates the first object that is found in the specified collection that matches the query document criteria with
|
||||
* the provided updated document. <br />
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateFirst(Query, UpdateDefinition, Class, String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<UpdateResult> updateFirst(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
return updateFirst(query, (UpdateDefinition) update, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
@@ -1253,8 +1446,26 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* not be {@literal null}.
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<UpdateResult> updateMulti(Query query, Update update, Class<?> entityClass);
|
||||
Mono<UpdateResult> updateMulti(Query query, UpdateDefinition update, Class<?> entityClass);
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
* with the provided updated document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, Class)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<UpdateResult> updateMulti(Query query, Update update, Class<?> entityClass) {
|
||||
return updateMulti(query, (UpdateDefinition) update, entityClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the specified collection that matches the query document criteria with the
|
||||
@@ -1268,8 +1479,28 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<UpdateResult> updateMulti(Query query, Update update, String collectionName);
|
||||
Mono<UpdateResult> updateMulti(Query query, UpdateDefinition update, String collectionName);
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the specified collection that matches the query document criteria with 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 #updateMulti(Query, Update, Class, String)} to get full type specific support.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<UpdateResult> updateMulti(Query query, Update update, String collectionName) {
|
||||
return updateMulti(query, (UpdateDefinition) update, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
@@ -1282,8 +1513,27 @@ public interface ReactiveMongoOperations extends ReactiveFluentMongoOperations {
|
||||
* @param entityClass class of the pojo to be operated on. Must not be {@literal null}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @since 2.3
|
||||
*/
|
||||
Mono<UpdateResult> updateMulti(Query query, Update update, Class<?> entityClass, String collectionName);
|
||||
Mono<UpdateResult> updateMulti(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName);
|
||||
|
||||
/**
|
||||
* Updates all objects that are found in the collection for the entity class that matches the query document criteria
|
||||
* with the provided updated document.
|
||||
*
|
||||
* @param query the query document that specifies the criteria used to select a record to be updated. Must not be
|
||||
* {@literal null}.
|
||||
* @param update the update document that contains the updated object or $ 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}.
|
||||
* @param collectionName name of the collection to update the object in. Must not be {@literal null}.
|
||||
* @return the {@link UpdateResult} which lets you access the results of the previous write.
|
||||
* @deprecated since 2.3 in favor of {@link #updateMulti(Query, UpdateDefinition, Class, String)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default Mono<UpdateResult> updateMulti(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
return updateMulti(query, (UpdateDefinition) update, entityClass, collectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given object from the collection by id.
|
||||
|
||||
@@ -69,7 +69,9 @@ import org.springframework.data.mongodb.core.EntityOperations.AdaptibleEntity;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.PrefixingDelegatingAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
|
||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||
@@ -105,7 +107,6 @@ import org.springframework.data.mongodb.core.query.Meta;
|
||||
import org.springframework.data.mongodb.core.query.Meta.CursorOption;
|
||||
import org.springframework.data.mongodb.core.query.NearQuery;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition.ArrayFilter;
|
||||
import org.springframework.data.mongodb.core.validation.Validator;
|
||||
@@ -1125,34 +1126,35 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class)
|
||||
*/
|
||||
public <T> Mono<T> findAndModify(Query query, Update update, Class<T> entityClass) {
|
||||
public <T> Mono<T> findAndModify(Query query, UpdateDefinition update, Class<T> entityClass) {
|
||||
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, getCollectionName(entityClass));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
public <T> Mono<T> findAndModify(Query query, Update update, Class<T> entityClass, String collectionName) {
|
||||
public <T> Mono<T> findAndModify(Query query, UpdateDefinition update, Class<T> entityClass, String collectionName) {
|
||||
return findAndModify(query, update, new FindAndModifyOptions(), entityClass, collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class)
|
||||
*/
|
||||
public <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass) {
|
||||
public <T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
|
||||
Class<T> entityClass) {
|
||||
return findAndModify(query, update, options, entityClass, getCollectionName(entityClass));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class, java.lang.String)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#findAndModify(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, org.springframework.data.mongodb.core.FindAndModifyOptions, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
public <T> Mono<T> findAndModify(Query query, Update update, FindAndModifyOptions options, Class<T> entityClass,
|
||||
String collectionName) {
|
||||
public <T> Mono<T> findAndModify(Query query, UpdateDefinition update, FindAndModifyOptions options,
|
||||
Class<T> entityClass, String collectionName) {
|
||||
|
||||
Assert.notNull(options, "Options must not be null! ");
|
||||
|
||||
@@ -1165,7 +1167,6 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
if (!optionsToUse.getCollation().isPresent()) {
|
||||
operations.forType(entityClass).getCollation(query).ifPresent(optionsToUse::collation);
|
||||
;
|
||||
}
|
||||
|
||||
return doFindAndModify(collectionName, query.getQueryObject(), query.getFieldsObject(),
|
||||
@@ -1687,73 +1688,75 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class)
|
||||
*/
|
||||
public Mono<UpdateResult> upsert(Query query, Update update, Class<?> entityClass) {
|
||||
public Mono<UpdateResult> upsert(Query query, UpdateDefinition update, Class<?> entityClass) {
|
||||
return doUpdate(getCollectionName(entityClass), query, update, entityClass, true, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.String)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.String)
|
||||
*/
|
||||
public Mono<UpdateResult> upsert(Query query, Update update, String collectionName) {
|
||||
public Mono<UpdateResult> upsert(Query query, UpdateDefinition update, String collectionName) {
|
||||
return doUpdate(collectionName, query, update, null, true, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#upsert(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
public Mono<UpdateResult> upsert(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
public Mono<UpdateResult> upsert(Query query, UpdateDefinition update, Class<?> entityClass, String collectionName) {
|
||||
return doUpdate(collectionName, query, update, entityClass, true, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc))
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class)
|
||||
*/
|
||||
public Mono<UpdateResult> updateFirst(Query query, Update update, Class<?> entityClass) {
|
||||
public Mono<UpdateResult> updateFirst(Query query, UpdateDefinition update, Class<?> entityClass) {
|
||||
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.String)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.String)
|
||||
*/
|
||||
public Mono<UpdateResult> updateFirst(Query query, Update update, String collectionName) {
|
||||
public Mono<UpdateResult> updateFirst(Query query, UpdateDefinition update, String collectionName) {
|
||||
return doUpdate(collectionName, query, update, null, false, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateFirst(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
public Mono<UpdateResult> updateFirst(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
public Mono<UpdateResult> updateFirst(Query query, UpdateDefinition update, Class<?> entityClass,
|
||||
String collectionName) {
|
||||
return doUpdate(collectionName, query, update, entityClass, false, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class)
|
||||
*/
|
||||
public Mono<UpdateResult> updateMulti(Query query, Update update, Class<?> entityClass) {
|
||||
public Mono<UpdateResult> updateMulti(Query query, UpdateDefinition update, Class<?> entityClass) {
|
||||
return doUpdate(getCollectionName(entityClass), query, update, entityClass, false, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.String)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.String)
|
||||
*/
|
||||
public Mono<UpdateResult> updateMulti(Query query, Update update, String collectionName) {
|
||||
public Mono<UpdateResult> updateMulti(Query query, UpdateDefinition update, String collectionName) {
|
||||
return doUpdate(collectionName, query, update, null, false, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.Update, java.lang.Class, java.lang.String)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveMongoOperations#updateMulti(org.springframework.data.mongodb.core.query.Query, org.springframework.data.mongodb.core.query.UpdateDefinition, java.lang.Class, java.lang.String)
|
||||
*/
|
||||
public Mono<UpdateResult> updateMulti(Query query, Update update, Class<?> entityClass, String collectionName) {
|
||||
public Mono<UpdateResult> updateMulti(Query query, UpdateDefinition update, Class<?> entityClass,
|
||||
String collectionName) {
|
||||
return doUpdate(collectionName, query, update, entityClass, false, true);
|
||||
}
|
||||
|
||||
@@ -1767,54 +1770,86 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);
|
||||
increaseVersionForUpdateIfNecessary(entity, update);
|
||||
|
||||
Flux<UpdateResult> result = execute(collectionName, collection -> {
|
||||
Document queryObj = queryMapper.getMappedObject(query.getQueryObject(), entity);
|
||||
|
||||
increaseVersionForUpdateIfNecessary(entity, update);
|
||||
UpdateOptions updateOptions = new UpdateOptions().upsert(upsert);
|
||||
operations.forType(entityClass).getCollation(query) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(updateOptions::collation);
|
||||
|
||||
Document queryObj = queryMapper.getMappedObject(query.getQueryObject(), entity);
|
||||
Document updateObj = update == null ? new Document()
|
||||
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
if (update.hasArrayFilters()) {
|
||||
updateOptions.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument)
|
||||
.map(it -> queryMapper.getMappedObject(it, entity)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("Calling update using query: %s and update: %s in collection: %s",
|
||||
serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName));
|
||||
}
|
||||
if (multi && update.isIsolated() && !queryObj.containsKey("$isolated")) {
|
||||
queryObj.put("$isolated", 1);
|
||||
}
|
||||
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, entityClass,
|
||||
updateObj, queryObj);
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
|
||||
Flux<UpdateResult> result = Flux.empty();
|
||||
|
||||
UpdateOptions updateOptions = new UpdateOptions().upsert(upsert);
|
||||
operations.forType(entityClass).getCollation(query) //
|
||||
.map(Collation::toMongoCollation) //
|
||||
.ifPresent(updateOptions::collation);
|
||||
if (update instanceof AggregationUpdate) {
|
||||
|
||||
if (update.hasArrayFilters()) {
|
||||
updateOptions.arrayFilters(update.getArrayFilters().stream().map(ArrayFilter::asDocument)
|
||||
.map(it -> queryMapper.getMappedObject(it, entity)).collect(Collectors.toList()));
|
||||
}
|
||||
AggregationOperationContext context = entityClass != null
|
||||
? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
|
||||
: Aggregation.DEFAULT_CONTEXT;
|
||||
|
||||
if (!UpdateMapper.isUpdateObject(updateObj)) {
|
||||
AggregationUpdate aUppdate = ((AggregationUpdate) update);
|
||||
List<Document> pipeline = new AggregationUtil(queryMapper, mappingContext).createPipeline(aUppdate, context);
|
||||
|
||||
ReplaceOptions replaceOptions = new ReplaceOptions();
|
||||
replaceOptions.upsert(updateOptions.isUpsert());
|
||||
replaceOptions.collation(updateOptions.getCollation());
|
||||
result = execute(collectionName, collection -> {
|
||||
|
||||
return collectionToUse.replaceOne(queryObj, updateObj, replaceOptions);
|
||||
}
|
||||
if (multi) {
|
||||
return collectionToUse.updateMany(queryObj, updateObj, updateOptions);
|
||||
}
|
||||
return collectionToUse.updateOne(queryObj, updateObj, updateOptions);
|
||||
}).doOnNext(updateResult -> {
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName,
|
||||
entityClass, update.getUpdateObject(), queryObj);
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
|
||||
collection = writeConcernToUse != null ? collection.withWriteConcern(writeConcernToUse) : collection;
|
||||
|
||||
if (multi) {
|
||||
return collection.updateMany(queryObj, pipeline, updateOptions);
|
||||
}
|
||||
|
||||
return collection.updateOne(queryObj, pipeline, updateOptions);
|
||||
});
|
||||
} else {
|
||||
|
||||
result = execute(collectionName, collection -> {
|
||||
|
||||
Document updateObj = update == null ? new Document()
|
||||
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format("Calling update using query: %s and update: %s in collection: %s",
|
||||
serializeToJsonSafely(queryObj), serializeToJsonSafely(updateObj), collectionName));
|
||||
}
|
||||
|
||||
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName,
|
||||
entityClass, updateObj, queryObj);
|
||||
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
|
||||
MongoCollection<Document> collectionToUse = prepareCollection(collection, writeConcernToUse);
|
||||
|
||||
if (!UpdateMapper.isUpdateObject(updateObj)) {
|
||||
|
||||
ReplaceOptions replaceOptions = new ReplaceOptions();
|
||||
replaceOptions.upsert(updateOptions.isUpsert());
|
||||
replaceOptions.collation(updateOptions.getCollation());
|
||||
|
||||
return collectionToUse.replaceOne(queryObj, updateObj, replaceOptions);
|
||||
}
|
||||
if (multi) {
|
||||
return collectionToUse.updateMany(queryObj, updateObj, updateOptions);
|
||||
}
|
||||
return collectionToUse.updateOne(queryObj, updateObj, updateOptions);
|
||||
});
|
||||
}
|
||||
|
||||
result = result.doOnNext(updateResult -> {
|
||||
|
||||
if (entity != null && entity.hasVersionProperty() && !multi) {
|
||||
if (updateResult.wasAcknowledged() && updateResult.getMatchedCount() == 0) {
|
||||
|
||||
Document queryObj = query == null ? new Document()
|
||||
: queryMapper.getMappedObject(query.getQueryObject(), entity);
|
||||
Document updateObj = update == null ? new Document()
|
||||
: updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
if (containsVersionProperty(queryObj, entity))
|
||||
@@ -2542,16 +2577,26 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
}
|
||||
|
||||
protected <T> Mono<T> doFindAndModify(String collectionName, Document query, Document fields, Document sort,
|
||||
Class<T> entityClass, Update update, FindAndModifyOptions options) {
|
||||
Class<T> entityClass, UpdateDefinition update, FindAndModifyOptions options) {
|
||||
|
||||
MongoPersistentEntity<?> entity = mappingContext.getPersistentEntity(entityClass);
|
||||
increaseVersionForUpdateIfNecessary(entity, update);
|
||||
|
||||
return Mono.defer(() -> {
|
||||
|
||||
increaseVersionForUpdateIfNecessary(entity, update);
|
||||
|
||||
Document mappedQuery = queryMapper.getMappedObject(query, entity);
|
||||
Document mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
|
||||
Object mappedUpdate = new Document();
|
||||
if (update instanceof AggregationUpdate) {
|
||||
|
||||
AggregationOperationContext context = entityClass != null
|
||||
? new RelaxedTypeBasedAggregationOperationContext(entityClass, mappingContext, queryMapper)
|
||||
: Aggregation.DEFAULT_CONTEXT;
|
||||
|
||||
mappedUpdate = new AggregationUtil(queryMapper, mappingContext).createPipeline((Aggregation) update, context);
|
||||
} else {
|
||||
mappedUpdate = updateMapper.getMappedObject(update.getUpdateObject(), entity);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(String.format(
|
||||
@@ -2928,7 +2973,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
private final Document query;
|
||||
private final Document fields;
|
||||
private final Document sort;
|
||||
private final Document update;
|
||||
private final Object update;
|
||||
private final List<Document> arrayFilters;
|
||||
private final FindAndModifyOptions options;
|
||||
|
||||
@@ -2947,7 +2992,12 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
|
||||
|
||||
FindOneAndUpdateOptions findOneAndUpdateOptions = convertToFindOneAndUpdateOptions(options, fields, sort,
|
||||
arrayFilters);
|
||||
return collection.findOneAndUpdate(query, update, findOneAndUpdateOptions);
|
||||
if (update instanceof Document) {
|
||||
return collection.findOneAndUpdate(query, (Document) update, findOneAndUpdateOptions);
|
||||
} else if (update instanceof List) {
|
||||
return collection.findOneAndUpdate(query, (List<Document>) update, findOneAndUpdateOptions);
|
||||
}
|
||||
return Flux.error(new IllegalArgumentException("doh - that does not work"));
|
||||
}
|
||||
|
||||
private static FindOneAndUpdateOptions convertToFindOneAndUpdateOptions(FindAndModifyOptions options,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mongodb.core;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
@@ -123,8 +124,22 @@ public interface ReactiveUpdateOperation {
|
||||
* @param update must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingUpdate}. Never {@literal null}.
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
* @since 2.3
|
||||
*/
|
||||
TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.Update update);
|
||||
TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.UpdateDefinition update);
|
||||
|
||||
/**
|
||||
* Set the {@link org.springframework.data.mongodb.core.query.Update} to be applied.
|
||||
*
|
||||
* @param update must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingUpdate}. Never {@literal null}.
|
||||
* @throws IllegalArgumentException if update is {@literal null}.
|
||||
* @deprecated since 2.3 in favor of {@link #apply(UpdateDefinition)}.
|
||||
*/
|
||||
@Deprecated
|
||||
default TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.Update update) {
|
||||
return apply((UpdateDefinition) update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify {@code replacement} object.
|
||||
|
||||
@@ -63,7 +63,7 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
@NonNull ReactiveMongoTemplate template;
|
||||
@NonNull Class<?> domainType;
|
||||
Query query;
|
||||
org.springframework.data.mongodb.core.query.Update update;
|
||||
org.springframework.data.mongodb.core.query.UpdateDefinition update;
|
||||
@Nullable String collection;
|
||||
@Nullable FindAndModifyOptions findAndModifyOptions;
|
||||
@Nullable FindAndReplaceOptions findAndReplaceOptions;
|
||||
@@ -72,10 +72,10 @@ class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithUpdate#apply(org.springframework.data.mongodb.core.query.Update)
|
||||
* @see org.springframework.data.mongodb.core.ReactiveUpdateOperation.UpdateWithUpdate#apply(org.springframework.data.mongodb.core.query.UpdateDefinition)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.Update update) {
|
||||
public TerminatingUpdate<T> apply(org.springframework.data.mongodb.core.query.UpdateDefinition update) {
|
||||
|
||||
Assert.notNull(update, "Update must not be null!");
|
||||
|
||||
|
||||
@@ -115,6 +115,17 @@ public class Aggregation {
|
||||
return new Aggregation(operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AggregationUpdate} from the given {@link AggregationOperation}s.
|
||||
*
|
||||
* @param operations can be {@literal empty} but must not be {@literal null}.
|
||||
* @return new instance of {@link AggregationUpdate}.
|
||||
* @since 2.3
|
||||
*/
|
||||
public static AggregationUpdate newUpdate(AggregationOperation... operations) {
|
||||
return AggregationUpdate.from(Arrays.asList(operations));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this {@link Aggregation} with the given {@link AggregationOptions} set. Note that options are
|
||||
* supported in MongoDB version 2.6+.
|
||||
@@ -181,13 +192,12 @@ public class Aggregation {
|
||||
/**
|
||||
* Creates a new {@link Aggregation} from the given {@link AggregationOperation}s.
|
||||
*
|
||||
* @param aggregationOperations must not be {@literal null} or empty.
|
||||
* @param aggregationOperations must not be {@literal null}.
|
||||
* @param options must not be {@literal null} or empty.
|
||||
*/
|
||||
protected Aggregation(List<AggregationOperation> aggregationOperations, AggregationOptions options) {
|
||||
|
||||
Assert.notNull(aggregationOperations, "AggregationOperations must not be null!");
|
||||
Assert.isTrue(!aggregationOperations.isEmpty(), "At least one AggregationOperation has to be provided");
|
||||
Assert.notNull(options, "AggregationOptions must not be null!");
|
||||
|
||||
// check $out is the last operation if it exists
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.SerializationUtils;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.data.mongodb.core.query.UpdateDefinition;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstraction for an {@code db.collection.update()} using an aggregation pipeline for a more expressive update
|
||||
* statement expressing conditional updates based on current field values or updating one field using the value of
|
||||
* another field(s).
|
||||
*
|
||||
* <pre class="code">
|
||||
* AggregationUpdate update = AggregationUpdate.update().set("average")
|
||||
* .toValue(ArithmeticOperators.valueOf("tests").avg()).set("grade")
|
||||
* .toValue(ConditionalOperators
|
||||
* .switchCases(CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(90)).then("A"),
|
||||
* CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(80)).then("B"),
|
||||
* CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(70)).then("C"),
|
||||
* CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(60)).then("D"))
|
||||
* .defaultTo("F"));
|
||||
* </pre>
|
||||
*
|
||||
* The above sample is equivalent to the JSON update statement
|
||||
*
|
||||
* <pre class="code">
|
||||
* db.collection.update(
|
||||
* { },
|
||||
* [
|
||||
* { $set: { average : { $avg: "$tests" } } },
|
||||
* { $set: { grade: { $switch: {
|
||||
* branches: [
|
||||
* { case: { $gte: [ "$average", 90 ] }, then: "A" },
|
||||
* { case: { $gte: [ "$average", 80 ] }, then: "B" },
|
||||
* { case: { $gte: [ "$average", 70 ] }, then: "C" },
|
||||
* { case: { $gte: [ "$average", 60 ] }, then: "D" }
|
||||
* ],
|
||||
* default: "F"
|
||||
* } } } }
|
||||
* ],
|
||||
* { multi: true }
|
||||
* )
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @see <a href=
|
||||
* "https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-with-aggregation-pipeline">MongoDB
|
||||
* Reference Documentation</a>
|
||||
* @since 2.3
|
||||
*/
|
||||
public class AggregationUpdate extends Aggregation implements UpdateDefinition {
|
||||
|
||||
private boolean isolated = false;
|
||||
private Set<String> keysTouched = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Create new {@link AggregationUpdate}.
|
||||
*/
|
||||
public AggregationUpdate() {
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link AggregationUpdate} with the given aggregation pipeline to apply.
|
||||
*
|
||||
* @param pipeline must not be {@literal null}.
|
||||
*/
|
||||
private AggregationUpdate(List<AggregationOperation> pipeline) {
|
||||
|
||||
super(pipeline);
|
||||
|
||||
for (AggregationOperation operation : pipeline) {
|
||||
if (operation instanceof FieldsExposingAggregationOperation) {
|
||||
((FieldsExposingAggregationOperation) operation).getFields().forEach(it -> {
|
||||
if (it instanceof Field) {
|
||||
keysTouched.add(((Field) it).getName());
|
||||
} else {
|
||||
keysTouched.add(it.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start defining the update pipeline to execute.
|
||||
*
|
||||
* @return new instance of {@link AggregationUpdate}.
|
||||
*/
|
||||
public static AggregationUpdate update() {
|
||||
return new AggregationUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new AggregationUpdate from the given {@link AggregationOperation}s.
|
||||
*
|
||||
* @return new instance of {@link AggregationUpdate}.
|
||||
*/
|
||||
public static AggregationUpdate from(List<AggregationOperation> pipeline) {
|
||||
return new AggregationUpdate(pipeline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new fields to documents. {@code $set} outputs documents that contain all existing fields from the input
|
||||
* documents and newly added fields.
|
||||
*
|
||||
* @param setOperation must not be {@literal null}.
|
||||
* @return this.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/set/">$set Aggregation Reference</a>
|
||||
*/
|
||||
public AggregationUpdate set(SetOperation setOperation) {
|
||||
|
||||
Assert.notNull(setOperation, "SetOperation must not be null!");
|
||||
|
||||
setOperation.getFields().forEach(it -> {
|
||||
if (it instanceof Field) {
|
||||
keysTouched.add(((Field) it).getName());
|
||||
} else {
|
||||
keysTouched.add(it.toString());
|
||||
}
|
||||
});
|
||||
operations.add(setOperation);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code $unset} removes/excludes fields from documents.
|
||||
*
|
||||
* @param unsetOperation must not be {@literal null}.
|
||||
* @return this.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/unset/">$unset Aggregation
|
||||
* Reference</a>
|
||||
*/
|
||||
public AggregationUpdate unset(UnsetOperation unsetOperation) {
|
||||
|
||||
Assert.notNull(unsetOperation, "UnsetOperation must not be null!");
|
||||
|
||||
operations.add(unsetOperation);
|
||||
keysTouched.addAll(unsetOperation.removedFieldNames());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code $replaceWith} replaces the input document with the specified document. The operation replaces all existing
|
||||
* fields in the input document, including the <strong>_id</strong> field.
|
||||
*
|
||||
* @param replaceWithOperation
|
||||
* @return this.
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/replaceWith/">$replaceWith Aggregation
|
||||
* Reference</a>
|
||||
*/
|
||||
public AggregationUpdate replaceWith(ReplaceWithOperation replaceWithOperation) {
|
||||
|
||||
Assert.notNull(replaceWithOperation, "ReplaceWithOperation must not be null!");
|
||||
operations.add(replaceWithOperation);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code $replaceWith} replaces the input document with the value.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return this.
|
||||
*/
|
||||
public AggregationUpdate replaceWith(Object value) {
|
||||
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
return replaceWith(ReplaceWithOperation.replaceWithValue(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent API variant for {@code $set} adding a single {@link SetOperation pipeline operation} every time. To update
|
||||
* multiple fields within one {@link SetOperation} use {@link #set(SetOperation)}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return new instance of {@link SetValueAppender}.
|
||||
* @see #set(SetOperation)
|
||||
*/
|
||||
public SetValueAppender set(String key) {
|
||||
|
||||
Assert.notNull(key, "Key must not be null!");
|
||||
|
||||
return new SetValueAppender() {
|
||||
|
||||
@Override
|
||||
public AggregationUpdate toValue(@Nullable Object value) {
|
||||
return set(SetOperation.builder().set(key).toValue(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregationUpdate toValueOf(Object value) {
|
||||
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
return set(SetOperation.builder().set(key).toValueOf(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Short for {@link #unset(UnsetOperation)}.
|
||||
*
|
||||
* @param keys
|
||||
* @return
|
||||
*/
|
||||
public AggregationUpdate unset(String... keys) {
|
||||
|
||||
Assert.notNull(keys, "Keys must not be null!");
|
||||
Assert.noNullElements(keys, "Keys must not contain null elements.");
|
||||
|
||||
return unset(new UnsetOperation(Arrays.stream(keys).map(Fields::field).collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents a write operation that affects <strong>multiple</strong> documents from yielding to other reads or writes
|
||||
* once the first document is written. <br />
|
||||
* Use with {@link org.springframework.data.mongodb.core.MongoOperations#updateMulti(Query, Update, Class)}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
public AggregationUpdate isolated() {
|
||||
|
||||
isolated = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.UpdateDefinition#isIsolated()
|
||||
*/
|
||||
@Override
|
||||
public Boolean isIsolated() {
|
||||
return isolated;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.UpdateDefinition#getUpdateObject()
|
||||
*/
|
||||
@Override
|
||||
public Document getUpdateObject() {
|
||||
return new Document("", toPipeline(Aggregation.DEFAULT_CONTEXT));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.UpdateDefinition#modifies(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean modifies(String key) {
|
||||
return keysTouched.contains(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.UpdateDefinition#inc(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void inc(String key) {
|
||||
set(new SetOperation(key, ArithmeticOperators.valueOf(key).add(1)));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.query.UpdateDefinition#getArrayFilters()
|
||||
*/
|
||||
@Override
|
||||
public List<ArrayFilter> getArrayFilters() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
String target = "[\n";
|
||||
target += StringUtils.collectionToDelimitedString(toPipeline(Aggregation.DEFAULT_CONTEXT).stream()
|
||||
.map(SerializationUtils::serializeToJsonSafely).collect(Collectors.toList()), ",\n");
|
||||
target += "\n]";
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fluent API AggregationUpdate builder.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
*/
|
||||
public interface SetValueAppender {
|
||||
|
||||
/**
|
||||
* Define the target value as is.
|
||||
*
|
||||
* @param value can be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
AggregationUpdate toValue(@Nullable Object value);
|
||||
|
||||
/**
|
||||
* Define the target value as value, an {@link AggregationExpression} or a {@link Field} reference.
|
||||
*
|
||||
* @param value can be {@literal null}.
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
AggregationUpdate toValueOf(Object value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import org.springframework.data.mapping.context.InvalidPersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
|
||||
import org.springframework.data.mongodb.core.convert.QueryMapper;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
|
||||
/**
|
||||
* A {@link TypeBasedAggregationOperationContext} with less restrictive field reference handling, suppressing
|
||||
* {@link InvalidPersistentPropertyPath} exceptions when resolving mapped field names.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
*/
|
||||
public class RelaxedTypeBasedAggregationOperationContext extends TypeBasedAggregationOperationContext {
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypeBasedAggregationOperationContext} for the given type, {@link MappingContext} and
|
||||
* {@link QueryMapper}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
* @param mapper must not be {@literal null}.
|
||||
*/
|
||||
public RelaxedTypeBasedAggregationOperationContext(Class<?> type,
|
||||
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext, QueryMapper mapper) {
|
||||
super(type, mappingContext, mapper);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext#getReferenceFor(rg.springframework.data.mongodb.core.aggregation.Field)
|
||||
*/
|
||||
@Override
|
||||
protected FieldReference getReferenceFor(Field field) {
|
||||
|
||||
try {
|
||||
return super.getReferenceFor(field);
|
||||
} catch (InvalidPersistentPropertyPath e) {
|
||||
return new DirectFieldReference(new ExposedField(field, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,8 @@ import org.springframework.util.Assert;
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 1.10
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/">MongoDB Aggregation Framework: $replaceRoot</a>
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/replaceRoot/">MongoDB Aggregation
|
||||
* Framework: $replaceRoot</a>
|
||||
*/
|
||||
public class ReplaceRootOperation implements FieldsExposingAggregationOperation {
|
||||
|
||||
@@ -82,7 +83,7 @@ public class ReplaceRootOperation implements FieldsExposingAggregationOperation
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
return new Document("$replaceRoot", new Document("newRoot", replacement.toDocumentExpression(context)));
|
||||
return new Document("$replaceRoot", new Document("newRoot", getReplacement().toDocumentExpression(context)));
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
@@ -93,6 +94,16 @@ public class ReplaceRootOperation implements FieldsExposingAggregationOperation
|
||||
return ExposedFields.from();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the {@link Replacement}.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
* @since 2.3
|
||||
*/
|
||||
protected Replacement getReplacement() {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link ReplaceRootOperation}.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Encapsulates the aggregation framework {@code $replaceRoot}-operation. <br />
|
||||
* The operation replaces all existing fields including the {@code id} field with @{code $replaceWith}. This way it is
|
||||
* possible to promote an embedded document to the top-level or specify a new document.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/replaceWith/">MongoDB Aggregation
|
||||
* Framework: $replaceWith</a>
|
||||
*/
|
||||
public class ReplaceWithOperation extends ReplaceRootOperation {
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link ReplaceWithOperation}.
|
||||
*
|
||||
* @param replacement must not be {@literal null}.
|
||||
*/
|
||||
public ReplaceWithOperation(Replacement replacement) {
|
||||
super(replacement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link ReplaceWithOperation}.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link ReplaceWithOperation}.
|
||||
*/
|
||||
public static ReplaceWithOperation replaceWithValue(Object value) {
|
||||
return new ReplaceWithOperation((ctx) -> value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of {@link ReplaceWithOperation} treating a given {@link String} {@literal value} as a
|
||||
* {@link Field field reference}.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ReplaceWithOperation replaceWithValueOf(Object value) {
|
||||
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
return new ReplaceWithOperation((ctx) -> {
|
||||
|
||||
Object target = value instanceof String ? Fields.field((String) value) : value;
|
||||
return computeValue(target, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
private static Object computeValue(Object value, AggregationOperationContext context) {
|
||||
|
||||
if (value instanceof Field) {
|
||||
return context.getReference((Field) value).toString();
|
||||
}
|
||||
if (value instanceof AggregationExpression) {
|
||||
return ((AggregationExpression) value).toDocument(context);
|
||||
}
|
||||
if (value instanceof Collection) {
|
||||
return ((Collection) value).stream().map(it -> computeValue(it, context)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
return context.getMappedObject(new Document("$replaceWith", getReplacement().toDocumentExpression(context)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
|
||||
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.SetOperation.FieldAppender.ValueAppender;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Adds new fields to documents. {@code $set} outputs documents that contain all existing fields from the input
|
||||
* documents and newly added fields.
|
||||
*
|
||||
* <pre class="code">
|
||||
* SetOperation.set("totalHomework").toValue("A+").and().set("totalQuiz").toValue("B-")
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/set/">MongoDB Aggregation Framework:
|
||||
* $set</a>
|
||||
*/
|
||||
public class SetOperation implements InheritsFieldsAggregationOperation {
|
||||
|
||||
private Map<Object, Object> valueMap;
|
||||
private ExposedFields exposedFields = ExposedFields.empty();
|
||||
|
||||
/**
|
||||
* Create new instance of {@link SetOperation} adding map keys as exposed fields.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
*/
|
||||
private SetOperation(Map<Object, Object> source) {
|
||||
|
||||
this.valueMap = new LinkedHashMap<>(source);
|
||||
for (Object key : source.keySet()) {
|
||||
this.exposedFields = add(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance of {@link SetOperation}
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @param value can be {@literal null}.
|
||||
*/
|
||||
public SetOperation(Object field, @Nullable Object value) {
|
||||
this(Collections.singletonMap(field, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the {@link SetOperation} via {@link FieldAppender}.
|
||||
*
|
||||
* @return new instance of {@link FieldAppender}.
|
||||
*/
|
||||
public static FieldAppender builder() {
|
||||
return new FieldAppender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate another field to set.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @return new instance of {@link ValueAppender}.
|
||||
*/
|
||||
public static ValueAppender set(String field) {
|
||||
return new FieldAppender().set(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the value for a specific field to the operation.
|
||||
*
|
||||
* @param field the target field to set.
|
||||
* @param value the value to assign.
|
||||
* @return new instance of {@link SetOperation}.
|
||||
*/
|
||||
public SetOperation set(Object field, Object value) {
|
||||
|
||||
LinkedHashMap<Object, Object> target = new LinkedHashMap<>(this.valueMap);
|
||||
target.put(field, value);
|
||||
|
||||
return new SetOperation(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate additional fields to set.
|
||||
*
|
||||
* @return new instance of {@link FieldAppender}.
|
||||
*/
|
||||
public FieldAppender and() {
|
||||
return new FieldAppender(this.valueMap);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
|
||||
InheritingExposedFieldsAggregationOperationContext operationContext = new InheritingExposedFieldsAggregationOperationContext(
|
||||
exposedFields, context);
|
||||
|
||||
if (valueMap.size() == 1) {
|
||||
return context
|
||||
.getMappedObject(new Document("$set", toSetEntry(valueMap.entrySet().iterator().next(), operationContext)));
|
||||
}
|
||||
|
||||
Document $set = new Document();
|
||||
valueMap.entrySet().stream().map(it -> toSetEntry(it, operationContext)).forEach($set::putAll);
|
||||
return context.getMappedObject(new Document("$set", $set));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields()
|
||||
*/
|
||||
@Override
|
||||
public ExposedFields getFields() {
|
||||
return exposedFields;
|
||||
}
|
||||
|
||||
private ExposedFields add(Object field) {
|
||||
|
||||
if (field instanceof Field) {
|
||||
return exposedFields.and(new ExposedField((Field) field, true));
|
||||
}
|
||||
if (field instanceof String) {
|
||||
return exposedFields.and(new ExposedField(Fields.field((String) field), true));
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Expected %s to be a field/property.", field));
|
||||
}
|
||||
|
||||
private static Document toSetEntry(Entry<Object, Object> entry, AggregationOperationContext context) {
|
||||
|
||||
String field = entry.getKey() instanceof String ? context.getReference((String) entry.getKey()).getRaw()
|
||||
: context.getReference((Field) entry.getKey()).getRaw();
|
||||
|
||||
Object value = computeValue(entry.getValue(), context);
|
||||
|
||||
return new Document(field, value);
|
||||
}
|
||||
|
||||
private static Object computeValue(Object value, AggregationOperationContext context) {
|
||||
|
||||
if (value instanceof Field) {
|
||||
return context.getReference((Field) value).toString();
|
||||
}
|
||||
if (value instanceof AggregationExpression) {
|
||||
return ((AggregationExpression) value).toDocument(context);
|
||||
}
|
||||
if (value instanceof Collection) {
|
||||
return ((Collection) value).stream().map(it -> computeValue(it, context)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
*/
|
||||
public static class FieldAppender {
|
||||
|
||||
private final Map<Object, Object> valueMap;
|
||||
|
||||
private FieldAppender() {
|
||||
this.valueMap = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
private FieldAppender(Map<Object, Object> source) {
|
||||
this.valueMap = new LinkedHashMap<>(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the field to set.
|
||||
*
|
||||
* @param field must not be {@literal null}.
|
||||
* @return new instance of {@link ValueAppender}.
|
||||
*/
|
||||
public ValueAppender set(String field) {
|
||||
|
||||
return new ValueAppender() {
|
||||
|
||||
@Override
|
||||
public SetOperation toValue(Object value) {
|
||||
|
||||
valueMap.put(field, value);
|
||||
return FieldAppender.this.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SetOperation toValueOf(Object value) {
|
||||
|
||||
valueMap.put(field, value instanceof String ? Fields.fields((String) value) : value);
|
||||
return FieldAppender.this.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private SetOperation build() {
|
||||
return new SetOperation(valueMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
*/
|
||||
public interface ValueAppender {
|
||||
|
||||
/**
|
||||
* Define the value to assign as is.
|
||||
*
|
||||
* @param value can be {@literal null}.
|
||||
* @return new instance of {@link SetOperation}.
|
||||
*/
|
||||
SetOperation toValue(@Nullable Object value);
|
||||
|
||||
/**
|
||||
* Define the value to assign. Plain {@link String} values are treated as {@link Field field references}.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return new instance of {@link SetOperation}.
|
||||
*/
|
||||
SetOperation toValueOf(Object value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
|
||||
@@ -128,7 +127,7 @@ public class TypeBasedAggregationOperationContext implements AggregationOperatio
|
||||
return Fields.fields(fields.toArray(new String[0]));
|
||||
}
|
||||
|
||||
private FieldReference getReferenceFor(Field field) {
|
||||
protected FieldReference getReferenceFor(Field field) {
|
||||
|
||||
PersistentPropertyPath<MongoPersistentProperty> propertyPath = mappingContext
|
||||
.getPersistentPropertyPath(field.getTarget(), type);
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Removes fields from documents.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 2.3
|
||||
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/unset/">MongoDB Aggregation Framework:
|
||||
* $unset</a>
|
||||
*/
|
||||
public class UnsetOperation implements InheritsFieldsAggregationOperation {
|
||||
|
||||
private final Collection<Object> fields;
|
||||
|
||||
/**
|
||||
* Create new instance of {@link UnsetOperation}.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
*/
|
||||
public UnsetOperation(Collection<Object> fields) {
|
||||
|
||||
Assert.notNull(fields, "Fields must not be null!");
|
||||
Assert.noNullElements(fields, "Fields must not contain null values.");
|
||||
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new instance of {@link UnsetOperation}.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return new instance of {@link UnsetOperation}.
|
||||
*/
|
||||
public static UnsetOperation unset(String... fields) {
|
||||
return new UnsetOperation(Arrays.asList(fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Also unset the given fields.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return new instance of {@link UnsetOperation}.
|
||||
*/
|
||||
public UnsetOperation and(String... fields) {
|
||||
|
||||
List<Object> target = new ArrayList<>(this.fields);
|
||||
CollectionUtils.mergeArrayIntoCollection(fields, target);
|
||||
return new UnsetOperation(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Also unset the given fields.
|
||||
*
|
||||
* @param fields must not be {@literal null}.
|
||||
* @return new instance of {@link UnsetOperation}.
|
||||
*/
|
||||
public UnsetOperation and(Field... fields) {
|
||||
|
||||
List<Object> target = new ArrayList<>(this.fields);
|
||||
CollectionUtils.mergeArrayIntoCollection(fields, target);
|
||||
return new UnsetOperation(target);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation#getFields()
|
||||
*/
|
||||
@Override
|
||||
public ExposedFields getFields() {
|
||||
return ExposedFields.from();
|
||||
}
|
||||
|
||||
Collection<String> removedFieldNames() {
|
||||
|
||||
List<String> fieldNames = new ArrayList<>(fields.size());
|
||||
for (Object it : fields) {
|
||||
if (it instanceof Field) {
|
||||
fieldNames.add(((Field) it).getName());
|
||||
} else {
|
||||
fieldNames.add(it.toString());
|
||||
}
|
||||
}
|
||||
return fieldNames;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mongodb.core.aggregation.AggregationOperation#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
|
||||
*/
|
||||
@Override
|
||||
public Document toDocument(AggregationOperationContext context) {
|
||||
|
||||
if (fields.size() == 1) {
|
||||
return new Document("$unset", computeFieldName(fields.iterator().next(), context));
|
||||
}
|
||||
|
||||
return new Document("$unset",
|
||||
fields.stream().map(it -> computeFieldName(it, context)).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private Object computeFieldName(Object field, AggregationOperationContext context) {
|
||||
|
||||
if (field instanceof Field) {
|
||||
return context.getReference((Field) field).getRaw();
|
||||
}
|
||||
|
||||
if (field instanceof AggregationExpression) {
|
||||
return ((AggregationExpression) field).toDocument(context);
|
||||
}
|
||||
|
||||
if (field instanceof String) {
|
||||
return context.getReference((String) field).getRaw();
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,6 +64,13 @@ import org.springframework.data.mapping.callback.EntityCallbacks;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.aggregation.Aggregation;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.ComparisonOperators.Gte;
|
||||
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator;
|
||||
import org.springframework.data.mongodb.core.aggregation.Fields;
|
||||
import org.springframework.data.mongodb.core.aggregation.SetOperation;
|
||||
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
|
||||
@@ -1713,6 +1720,101 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
|
||||
verify(collection).withReadPreference(eq(ReadPreference.primaryPreferred()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldAllowAggregationExpressions() {
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().set("total")
|
||||
.toValue(ArithmeticOperators.valueOf("val1").sum().and("val2"));
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Wrapper.class);
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).isEqualTo(
|
||||
Collections.singletonList(Document.parse("{ $set : { total : { $sum : [ \"$val1\",\"$val2\" ] } } }")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldAllowMultipleAggregationExpressions() {
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate() //
|
||||
.set("average").toValue(ArithmeticOperators.valueOf("tests").avg()) //
|
||||
.set("grade").toValue(ConditionalOperators.switchCases( //
|
||||
CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(90)).then("A"), //
|
||||
CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(80)).then("B"), //
|
||||
CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(70)).then("C"), //
|
||||
CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(60)).then("D") //
|
||||
) //
|
||||
.defaultTo("F"));//
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Wrapper.class);
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).containsExactly(Document.parse("{ $set: { average : { $avg: \"$tests\" } } }"),
|
||||
Document.parse("{ $set: { grade: { $switch: {\n" + " branches: [\n"
|
||||
+ " { case: { $gte: [ \"$average\", 90 ] }, then: \"A\" },\n"
|
||||
+ " { case: { $gte: [ \"$average\", 80 ] }, then: \"B\" },\n"
|
||||
+ " { case: { $gte: [ \"$average\", 70 ] }, then: \"C\" },\n"
|
||||
+ " { case: { $gte: [ \"$average\", 60 ] }, then: \"D\" }\n"
|
||||
+ " ],\n" + " default: \"F\"\n" + " } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldMapAggregationExpressionToDomainType() {
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().set("name")
|
||||
.toValue(ArithmeticOperators.valueOf("val1").sum().and("val2"));
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Jedi.class);
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).isEqualTo(
|
||||
Collections.singletonList(Document.parse("{ $set : { firstname : { $sum:[ \"$val1\",\"$val2\" ] } } }")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldPassOnUnsetCorrectly() {
|
||||
|
||||
SetOperation setOperation = SetOperation.builder().set("status").toValue("Modified").and().set("comments")
|
||||
.toValue(Fields.fields("misc1").and("misc2").asList());
|
||||
AggregationUpdate update = new AggregationUpdate();
|
||||
update.set(setOperation);
|
||||
update.unset("misc1", "misc2");
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Wrapper.class);
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).isEqualTo(
|
||||
Arrays.asList(Document.parse("{ $set: { status: \"Modified\", comments: [ \"$misc1\", \"$misc2\" ] } }"),
|
||||
Document.parse("{ $unset: [ \"misc1\", \"misc2\" ] }")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldMapAggregationUnsetToDomainType() {
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate();
|
||||
update.unset("name");
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Jedi.class);
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).isEqualTo(Collections.singletonList(Document.parse("{ $unset : \"firstname\" }")));
|
||||
}
|
||||
|
||||
class AutogenerateableId {
|
||||
|
||||
@Id BigInteger id;
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* 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 java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.ReplaceWithOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.SetOperation;
|
||||
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.test.util.MongoTestUtils;
|
||||
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class MongoTemplateUpdateTests {
|
||||
|
||||
static final String DB_NAME = "update-test";
|
||||
|
||||
MongoClient client;
|
||||
MongoTemplate template;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
client = MongoTestUtils.replSetClient();
|
||||
template = new MongoTemplate(new SimpleMongoDbFactory(client, DB_NAME));
|
||||
|
||||
MongoTestUtils.createOrReplaceCollection(DB_NAME, template.getCollectionName(Score.class), client);
|
||||
MongoTestUtils.createOrReplaceCollection(DB_NAME, template.getCollectionName(Versioned.class), client);
|
||||
MongoTestUtils.createOrReplaceCollection(DB_NAME, template.getCollectionName(Book.class), client);
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateWithSet() {
|
||||
|
||||
Score score1 = new Score(1, "Maya", Arrays.asList(10, 5, 10), Arrays.asList(10, 8), 0);
|
||||
Score score2 = new Score(2, "Ryan", Arrays.asList(5, 6, 5), Arrays.asList(8, 8), 8);
|
||||
|
||||
template.insertAll(Arrays.asList(score1, score2));
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().set(SetOperation.builder() //
|
||||
.set("totalHomework").toValueOf(ArithmeticOperators.valueOf("homework").sum()).and() //
|
||||
.set("totalQuiz").toValueOf(ArithmeticOperators.valueOf("quiz").sum())) //
|
||||
.set(SetOperation.builder() //
|
||||
.set("totalScore")
|
||||
.toValueOf(ArithmeticOperators.valueOf("totalHomework").add("totalQuiz").add("extraCredit")));
|
||||
|
||||
template.update(Score.class).apply(update).all();
|
||||
|
||||
assertThat(collection(Score.class).find(new org.bson.Document()).into(new ArrayList<>())).containsExactlyInAnyOrder( //
|
||||
org.bson.Document.parse(
|
||||
"{\"_id\" : 1, \"student\" : \"Maya\", \"homework\" : [ 10, 5, 10 ], \"quiz\" : [ 10, 8 ], \"extraCredit\" : 0, \"totalHomework\" : 25, \"totalQuiz\" : 18, \"totalScore\" : 43, \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Score\"}"),
|
||||
org.bson.Document.parse(
|
||||
"{ \"_id\" : 2, \"student\" : \"Ryan\", \"homework\" : [ 5, 6, 5 ], \"quiz\" : [ 8, 8 ], \"extraCredit\" : 8, \"totalHomework\" : 16, \"totalQuiz\" : 16, \"totalScore\" : 40, \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Score\"}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateWithSetToValue() {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.author = new Author("John", "Backus");
|
||||
|
||||
template.insertAll(Arrays.asList(one));
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().set("author").toValue(new Author("Ada", "Lovelace"));
|
||||
|
||||
template.update(Book.class).matching(Query.query(Criteria.where("id").is(one.id))).apply(update).all();
|
||||
|
||||
assertThat(all(Book.class)).containsExactlyInAnyOrder(org.bson.Document.parse(
|
||||
"{\"_id\" : 1, \"author\" : {\"first\" : \"Ada\", \"last\" : \"Lovelace\"}, \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Book\"}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void versionedAggregateUpdateWithSet() {
|
||||
|
||||
Versioned source = template.insert(Versioned.class).one(new Versioned("id-1", "value-0"));
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().set("value").toValue("changed");
|
||||
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", 1L).append("value", "changed")
|
||||
.append("_class", "org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Versioned"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void versionedAggregateUpdateTouchingVersionProperty() {
|
||||
|
||||
Versioned source = template.insert(Versioned.class).one(new Versioned("id-1", "value-0"));
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate()
|
||||
.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"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateWithUnset() {
|
||||
|
||||
Book antelopeAntics = new Book();
|
||||
antelopeAntics.id = 1;
|
||||
antelopeAntics.title = "Antelope Antics";
|
||||
antelopeAntics.isbn = "0001122223334";
|
||||
antelopeAntics.author = new Author("Auntie", "An");
|
||||
antelopeAntics.stock = new ArrayList<>();
|
||||
antelopeAntics.stock.add(new Warehouse("A", 5));
|
||||
antelopeAntics.stock.add(new Warehouse("B", 15));
|
||||
|
||||
Book beesBabble = new Book();
|
||||
beesBabble.id = 2;
|
||||
beesBabble.title = "Bees Babble";
|
||||
beesBabble.isbn = "999999999333";
|
||||
beesBabble.author = new Author("Bee", "Bumble");
|
||||
beesBabble.stock = new ArrayList<>();
|
||||
beesBabble.stock.add(new Warehouse("A", 2));
|
||||
beesBabble.stock.add(new Warehouse("B", 5));
|
||||
|
||||
template.insertAll(Arrays.asList(antelopeAntics, beesBabble));
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().unset("isbn", "stock");
|
||||
template.update(Book.class).apply(update).all();
|
||||
|
||||
assertThat(all(Book.class)).containsExactlyInAnyOrder( //
|
||||
org.bson.Document.parse(
|
||||
"{ \"_id\" : 1, \"title\" : \"Antelope Antics\", \"author\" : { \"last\" : \"An\", \"first\" : \"Auntie\" }, \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Book\" }"),
|
||||
org.bson.Document.parse(
|
||||
"{ \"_id\" : 2, \"title\" : \"Bees Babble\", \"author\" : { \"last\" : \"Bumble\", \"first\" : \"Bee\" }, \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Book\" }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateWithReplaceWith() {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.author = new Author("John", "Backus");
|
||||
|
||||
Book two = new Book();
|
||||
two.id = 2;
|
||||
two.author = new Author("Grace", "Hopper");
|
||||
|
||||
template.insertAll(Arrays.asList(one, two));
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().replaceWith(ReplaceWithOperation.replaceWithValueOf("author"));
|
||||
|
||||
template.update(Book.class).apply(update).all();
|
||||
|
||||
assertThat(all(Book.class)).containsExactlyInAnyOrder(
|
||||
org.bson.Document.parse("{\"_id\" : 1, \"first\" : \"John\", \"last\" : \"Backus\"}"),
|
||||
org.bson.Document.parse("{\"_id\" : 2, \"first\" : \"Grace\", \"last\" : \"Hopper\"}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateWithReplaceWithNewObject() {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.author = new Author("John", "Backus");
|
||||
|
||||
Book two = new Book();
|
||||
two.id = 2;
|
||||
two.author = new Author("Grace", "Hopper");
|
||||
|
||||
template.insertAll(Arrays.asList(one, two));
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().replaceWith(new Author("Ada", "Lovelace"));
|
||||
|
||||
template.update(Book.class).matching(Query.query(Criteria.where("id").is(one.id))).apply(update).all();
|
||||
|
||||
assertThat(all(Book.class)).containsExactlyInAnyOrder(org.bson.Document.parse(
|
||||
"{\"_id\" : 1, \"first\" : \"Ada\", \"last\" : \"Lovelace\", \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Author\"}"),
|
||||
org.bson.Document.parse(
|
||||
"{\"_id\" : 2, \"author\" : {\"first\" : \"Grace\", \"last\" : \"Hopper\"}, \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Book\"}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregationUpdateUpsertsCorrectly() {
|
||||
|
||||
AggregationUpdate update = AggregationUpdate.update().set("title").toValue("The Burning White");
|
||||
|
||||
template.update(Book.class).matching(Query.query(Criteria.where("id").is(1))).apply(update).upsert();
|
||||
|
||||
assertThat(all(Book.class))
|
||||
.containsExactly(org.bson.Document.parse("{\"_id\" : 1, \"title\" : \"The Burning White\" }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateFirstMatch() {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.title = "The Blood Mirror";
|
||||
|
||||
Book two = new Book();
|
||||
two.id = 2;
|
||||
two.title = "The Broken Eye";
|
||||
|
||||
template.insertAll(Arrays.asList(one, two));
|
||||
|
||||
template.update(Book.class).apply(AggregationUpdate.update().set("title").toValue("The Blinding Knife")).first();
|
||||
|
||||
assertThat(all(Book.class)).containsExactly(org.bson.Document.parse(
|
||||
"{\"_id\" : 1, \"title\" : \"The Blinding Knife\", \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Book\"}"),
|
||||
org.bson.Document.parse(
|
||||
"{\"_id\" : 2, \"title\" : \"The Broken Eye\", \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Book\"}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
@Ignore("https://jira.mongodb.org/browse/JAVA-3432")
|
||||
public void findAndModifyAppliesAggregationUpdateCorrectly() {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.title = "The Blood Mirror";
|
||||
|
||||
Book two = new Book();
|
||||
two.id = 2;
|
||||
two.title = "The Broken Eye";
|
||||
|
||||
template.insertAll(Arrays.asList(one, two));
|
||||
|
||||
Book retrieved = template.update(Book.class).matching(Query.query(Criteria.where("id").is(one.id)))
|
||||
.apply(AggregationUpdate.update().set("title").toValue("The Blinding Knife")).findAndModifyValue();
|
||||
assertThat(retrieved).isEqualTo(one);
|
||||
|
||||
assertThat(all(Book.class)).containsExactly(org.bson.Document.parse(
|
||||
"{\"_id\" : 1, \"title\" : \"The Blinding Knife\", \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Book\"}"),
|
||||
org.bson.Document.parse(
|
||||
"{\"_id\" : 2, \"title\" : \"The Broken Eye\", \"_class\" : \"org.springframework.data.mongodb.core.MongoTemplateUpdateTests$Book\"}"));
|
||||
|
||||
}
|
||||
|
||||
private List<org.bson.Document> all(Class<?> type) {
|
||||
return collection(type).find(new org.bson.Document()).into(new ArrayList<>());
|
||||
}
|
||||
|
||||
private MongoCollection<org.bson.Document> collection(Class<?> type) {
|
||||
return client.getDatabase(DB_NAME).getCollection(template.getCollectionName(type));
|
||||
}
|
||||
|
||||
@Document("scores")
|
||||
static class Score {
|
||||
|
||||
Integer id;
|
||||
String student;
|
||||
List<Integer> homework;
|
||||
List<Integer> quiz;
|
||||
Integer extraCredit;
|
||||
|
||||
public Score(Integer id, String student, List<Integer> homework, List<Integer> quiz, Integer extraCredit) {
|
||||
|
||||
this.id = id;
|
||||
this.student = student;
|
||||
this.homework = homework;
|
||||
this.quiz = quiz;
|
||||
this.extraCredit = extraCredit;
|
||||
}
|
||||
}
|
||||
|
||||
static class Versioned {
|
||||
|
||||
String id;
|
||||
@Version Long version;
|
||||
String value;
|
||||
|
||||
public Versioned(String id, String value) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static class Book {
|
||||
|
||||
@Id Integer id;
|
||||
String title;
|
||||
String isbn;
|
||||
Author author;
|
||||
@Field("copies") Collection<Warehouse> stock;
|
||||
}
|
||||
|
||||
static class Author {
|
||||
|
||||
@Field("first") String firstname;
|
||||
@Field("last") String lastname;
|
||||
|
||||
public Author(String firstname, String lastname) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
}
|
||||
|
||||
static class Warehouse {
|
||||
|
||||
public Warehouse(String location, Integer qty) {
|
||||
this.location = location;
|
||||
this.qty = qty;
|
||||
}
|
||||
|
||||
@Field("warehouse") String location;
|
||||
Integer qty;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import reactor.test.StepVerifier;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -51,6 +52,13 @@ import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.data.mongodb.core.MongoTemplateUnitTests.AutogenerateableId;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.ComparisonOperators.Gte;
|
||||
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Switch.CaseOperator;
|
||||
import org.springframework.data.mongodb.core.aggregation.Fields;
|
||||
import org.springframework.data.mongodb.core.aggregation.SetOperation;
|
||||
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
|
||||
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
@@ -134,6 +142,8 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
when(collection.countDocuments(any(), any(CountOptions.class))).thenReturn(Mono.just(0L));
|
||||
when(collection.updateOne(any(), any(Bson.class), any(UpdateOptions.class))).thenReturn(updateResultPublisher);
|
||||
when(collection.updateMany(any(Bson.class), any(Bson.class), any())).thenReturn(updateResultPublisher);
|
||||
when(collection.updateOne(any(), anyList(), any())).thenReturn(updateResultPublisher);
|
||||
when(collection.updateMany(any(), anyList(), any())).thenReturn(updateResultPublisher);
|
||||
when(collection.findOneAndUpdate(any(), any(Bson.class), any(FindOneAndUpdateOptions.class)))
|
||||
.thenReturn(findAndUpdatePublisher);
|
||||
when(collection.findOneAndReplace(any(Bson.class), any(), any())).thenReturn(findPublisher);
|
||||
@@ -881,6 +891,101 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
verify(collection).withReadPreference(eq(ReadPreference.primaryPreferred()));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldAllowAggregationExpressions() {
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().set("total")
|
||||
.toValue(ArithmeticOperators.valueOf("val1").sum().and("val2"));
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Wrapper.class).subscribe();
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).isEqualTo(
|
||||
Collections.singletonList(Document.parse("{ $set : { total : { $sum : [ \"$val1\",\"$val2\" ] } } }")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldAllowMultipleAggregationExpressions() {
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate() //
|
||||
.set("average").toValue(ArithmeticOperators.valueOf("tests").avg()) //
|
||||
.set("grade").toValue(ConditionalOperators.switchCases( //
|
||||
CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(90)).then("A"), //
|
||||
CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(80)).then("B"), //
|
||||
CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(70)).then("C"), //
|
||||
CaseOperator.when(Gte.valueOf("average").greaterThanEqualToValue(60)).then("D") //
|
||||
) //
|
||||
.defaultTo("F"));//
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Wrapper.class).subscribe();
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).containsExactly(Document.parse("{ $set: { average : { $avg: \"$tests\" } } }"),
|
||||
Document.parse("{ $set: { grade: { $switch: {\n" + " branches: [\n"
|
||||
+ " { case: { $gte: [ \"$average\", 90 ] }, then: \"A\" },\n"
|
||||
+ " { case: { $gte: [ \"$average\", 80 ] }, then: \"B\" },\n"
|
||||
+ " { case: { $gte: [ \"$average\", 70 ] }, then: \"C\" },\n"
|
||||
+ " { case: { $gte: [ \"$average\", 60 ] }, then: \"D\" }\n"
|
||||
+ " ],\n" + " default: \"F\"\n" + " } } } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldMapAggregationExpressionToDomainType() {
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().set("name")
|
||||
.toValue(ArithmeticOperators.valueOf("val1").sum().and("val2"));
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Jedi.class).subscribe();
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).isEqualTo(
|
||||
Collections.singletonList(Document.parse("{ $set : { firstname : { $sum:[ \"$val1\",\"$val2\" ] } } }")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldPassOnUnsetCorrectly() {
|
||||
|
||||
SetOperation setOperation = SetOperation.builder().set("status").toValue("Modified").and().set("comments")
|
||||
.toValue(Fields.fields("misc1").and("misc2").asList());
|
||||
AggregationUpdate update = new AggregationUpdate();
|
||||
update.set(setOperation);
|
||||
update.unset("misc1", "misc2");
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Wrapper.class).subscribe();
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).isEqualTo(
|
||||
Arrays.asList(Document.parse("{ $set: { status: \"Modified\", comments: [ \"$misc1\", \"$misc2\" ] } }"),
|
||||
Document.parse("{ $unset: [ \"misc1\", \"misc2\" ] }")));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void updateShouldMapAggregationUnsetToDomainType() {
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate();
|
||||
update.unset("name");
|
||||
|
||||
template.updateFirst(new BasicQuery("{}"), update, Jedi.class).subscribe();
|
||||
|
||||
ArgumentCaptor<List<Document>> captor = ArgumentCaptor.forClass(List.class);
|
||||
|
||||
verify(collection, times(1)).updateOne(any(org.bson.Document.class), captor.capture(), any(UpdateOptions.class));
|
||||
|
||||
assertThat(captor.getValue()).isEqualTo(Collections.singletonList(Document.parse("{ $unset : \"firstname\" }")));
|
||||
}
|
||||
|
||||
@Data
|
||||
@org.springframework.data.mongodb.core.mapping.Document(collection = "star-wars")
|
||||
static class Person {
|
||||
@@ -889,6 +994,11 @@ public class ReactiveMongoTemplateUnitTests {
|
||||
String firstname;
|
||||
}
|
||||
|
||||
class Wrapper {
|
||||
|
||||
AutogenerateableId foo;
|
||||
}
|
||||
|
||||
static class PersonExtended extends Person {
|
||||
|
||||
String lastname;
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* 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 reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.mongodb.core.aggregation.AggregationUpdate;
|
||||
import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators;
|
||||
import org.springframework.data.mongodb.core.aggregation.ReplaceWithOperation;
|
||||
import org.springframework.data.mongodb.core.aggregation.SetOperation;
|
||||
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.test.util.MongoTestUtils;
|
||||
|
||||
import com.mongodb.reactivestreams.client.MongoClient;
|
||||
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class ReactiveMongoTemplateUpdateTests {
|
||||
|
||||
static final String DB_NAME = "reactive-update-test";
|
||||
|
||||
MongoClient client;
|
||||
ReactiveMongoTemplate template;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
client = MongoTestUtils.reactiveReplSetClient();
|
||||
template = new ReactiveMongoTemplate(new SimpleReactiveMongoDatabaseFactory(client, DB_NAME));
|
||||
|
||||
MongoTestUtils.createOrReplaceCollection(DB_NAME, template.getCollectionName(Score.class), client).then()
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
MongoTestUtils.createOrReplaceCollection(DB_NAME, template.getCollectionName(Versioned.class), client).then()
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
MongoTestUtils.createOrReplaceCollection(DB_NAME, template.getCollectionName(Book.class), client).then()
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateWithSet() {
|
||||
|
||||
Score score1 = new Score(1, "Maya", Arrays.asList(10, 5, 10), Arrays.asList(10, 8), 0);
|
||||
Score score2 = new Score(2, "Ryan", Arrays.asList(5, 6, 5), Arrays.asList(8, 8), 8);
|
||||
|
||||
template.insertAll(Arrays.asList(score1, score2)).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().set(SetOperation.builder() //
|
||||
.set("totalHomework").toValueOf(ArithmeticOperators.valueOf("homework").sum()).and() //
|
||||
.set("totalQuiz").toValueOf(ArithmeticOperators.valueOf("quiz").sum())) //
|
||||
.set(SetOperation.builder() //
|
||||
.set("totalScore")
|
||||
.toValueOf(ArithmeticOperators.valueOf("totalHomework").add("totalQuiz").add("extraCredit")));
|
||||
|
||||
template.update(Score.class).apply(update).all().then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
Flux.from(collection(Score.class).find(new org.bson.Document())).collectList().as(StepVerifier::create)
|
||||
.consumeNextWith(it -> {
|
||||
|
||||
assertThat(it).containsExactlyInAnyOrder( //
|
||||
org.bson.Document.parse(
|
||||
"{\"_id\" : 1, \"student\" : \"Maya\", \"homework\" : [ 10, 5, 10 ], \"quiz\" : [ 10, 8 ], \"extraCredit\" : 0, \"totalHomework\" : 25, \"totalQuiz\" : 18, \"totalScore\" : 43, \"_class\" : \"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Score\"}"),
|
||||
org.bson.Document.parse(
|
||||
"{ \"_id\" : 2, \"student\" : \"Ryan\", \"homework\" : [ 5, 6, 5 ], \"quiz\" : [ 8, 8 ], \"extraCredit\" : 8, \"totalHomework\" : 16, \"totalQuiz\" : 16, \"totalScore\" : 40, \"_class\" : \"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Score\"}"));
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void versionedAggregateUpdateWithSet() {
|
||||
|
||||
Versioned source = new Versioned("id-1", "value-0");
|
||||
template.insert(Versioned.class).one(source).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().set("value").toValue("changed");
|
||||
template.update(Versioned.class).matching(Query.query(Criteria.where("id").is(source.id))).apply(update).first()
|
||||
.then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
Flux.from(collection(Versioned.class).find(new org.bson.Document("_id", source.id)).limit(1)).collectList()
|
||||
.as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it).containsExactly(
|
||||
new org.bson.Document("_id", source.id).append("version", 1L).append("value", "changed").append("_class",
|
||||
"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Versioned"));
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void versionedAggregateUpdateTouchingVersionProperty() {
|
||||
|
||||
Versioned source = new Versioned("id-1", "value-0");
|
||||
template.insert(Versioned.class).one(source).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate()
|
||||
.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()
|
||||
.then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
Flux.from(collection(Versioned.class).find(new org.bson.Document("_id", source.id)).limit(1)).collectList()
|
||||
.as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it).containsExactly(
|
||||
new org.bson.Document("_id", source.id).append("version", 10L).append("value", "changed").append("_class",
|
||||
"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Versioned"));
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateWithUnset() {
|
||||
|
||||
Book antelopeAntics = new Book();
|
||||
antelopeAntics.id = 1;
|
||||
antelopeAntics.title = "Antelope Antics";
|
||||
antelopeAntics.isbn = "0001122223334";
|
||||
antelopeAntics.author = new Author("Auntie", "An");
|
||||
antelopeAntics.stock = new ArrayList<>();
|
||||
antelopeAntics.stock.add(new Warehouse("A", 5));
|
||||
antelopeAntics.stock.add(new Warehouse("B", 15));
|
||||
|
||||
Book beesBabble = new Book();
|
||||
beesBabble.id = 2;
|
||||
beesBabble.title = "Bees Babble";
|
||||
beesBabble.isbn = "999999999333";
|
||||
beesBabble.author = new Author("Bee", "Bumble");
|
||||
beesBabble.stock = new ArrayList<>();
|
||||
beesBabble.stock.add(new Warehouse("A", 2));
|
||||
beesBabble.stock.add(new Warehouse("B", 5));
|
||||
|
||||
template.insertAll(Arrays.asList(antelopeAntics, beesBabble)).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().unset("isbn", "stock");
|
||||
template.update(Book.class).apply(update).all().then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
all(Book.class).collectList().as(StepVerifier::create).consumeNextWith(it -> {
|
||||
|
||||
assertThat(it).containsExactlyInAnyOrder( //
|
||||
org.bson.Document.parse(
|
||||
"{ \"_id\" : 1, \"title\" : \"Antelope Antics\", \"author\" : { \"last\" : \"An\", \"first\" : \"Auntie\" }, \"_class\" : \"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Book\" }"),
|
||||
org.bson.Document.parse(
|
||||
"{ \"_id\" : 2, \"title\" : \"Bees Babble\", \"author\" : { \"last\" : \"Bumble\", \"first\" : \"Bee\" }, \"_class\" : \"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Book\" }"));
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateWithReplaceWith() {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.author = new Author("John", "Backus");
|
||||
|
||||
Book two = new Book();
|
||||
two.id = 2;
|
||||
two.author = new Author("Grace", "Hopper");
|
||||
|
||||
template.insertAll(Arrays.asList(one, two)).then().as(StepVerifier::create).verifyComplete();
|
||||
;
|
||||
|
||||
AggregationUpdate update = new AggregationUpdate().replaceWith(ReplaceWithOperation.replaceWithValueOf("author"));
|
||||
|
||||
template.update(Book.class).apply(update).all().then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
all(Book.class).collectList().as(StepVerifier::create).consumeNextWith(it -> {
|
||||
|
||||
assertThat(it).containsExactlyInAnyOrder(
|
||||
org.bson.Document.parse("{\"_id\" : 1, \"first\" : \"John\", \"last\" : \"Backus\"}"),
|
||||
org.bson.Document.parse("{\"_id\" : 2, \"first\" : \"Grace\", \"last\" : \"Hopper\"}"));
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregationUpdateUpsertsCorrectly() {
|
||||
|
||||
AggregationUpdate update = AggregationUpdate.update().set("title").toValue("The Burning White");
|
||||
|
||||
template.update(Book.class).matching(Query.query(Criteria.where("id").is(1))).apply(update).upsert().then()
|
||||
.as(StepVerifier::create).verifyComplete();
|
||||
|
||||
all(Book.class).collectList().as(StepVerifier::create).consumeNextWith(it -> {
|
||||
assertThat(it).containsExactly(org.bson.Document.parse("{\"_id\" : 1, \"title\" : \"The Burning White\" }"));
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void aggregateUpdateFirstMatch() {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.title = "The Blood Mirror";
|
||||
|
||||
Book two = new Book();
|
||||
two.id = 2;
|
||||
two.title = "The Broken Eye";
|
||||
|
||||
template.insertAll(Arrays.asList(one, two)).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
template.update(Book.class).apply(AggregationUpdate.update().set("title").toValue("The Blinding Knife")).first()
|
||||
.then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
all(Book.class).collectList().as(StepVerifier::create).consumeNextWith(it -> {
|
||||
|
||||
assertThat(it).containsExactly(org.bson.Document.parse(
|
||||
"{\"_id\" : 1, \"title\" : \"The Blinding Knife\", \"_class\" : \"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Book\"}"),
|
||||
org.bson.Document.parse(
|
||||
"{\"_id\" : 2, \"title\" : \"The Broken Eye\", \"_class\" : \"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Book\"}"));
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
@Ignore("https://jira.mongodb.org/browse/JAVA-3432")
|
||||
public void findAndModifyAppliesAggregationUpdateCorrectly() {
|
||||
|
||||
Book one = new Book();
|
||||
one.id = 1;
|
||||
one.title = "The Blood Mirror";
|
||||
|
||||
Book two = new Book();
|
||||
two.id = 2;
|
||||
two.title = "The Broken Eye";
|
||||
|
||||
template.insertAll(Arrays.asList(one, two)).then().as(StepVerifier::create).verifyComplete();
|
||||
|
||||
template.update(Book.class) //
|
||||
.matching(Query.query(Criteria.where("id").is(one.id))) //
|
||||
.apply(AggregationUpdate.update().set("title").toValue("The Blinding Knife")) //
|
||||
.findAndModify() //
|
||||
.as(StepVerifier::create) //
|
||||
.expectNext(one) //
|
||||
.verifyComplete();
|
||||
|
||||
all(Book.class).collectList().as(StepVerifier::create).consumeNextWith(it -> {
|
||||
|
||||
assertThat(it).containsExactly(org.bson.Document.parse(
|
||||
"{\"_id\" : 1, \"title\" : \"The Blinding Knife\", \"_class\" : \"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Book\"}"),
|
||||
org.bson.Document.parse(
|
||||
"{\"_id\" : 2, \"title\" : \"The Broken Eye\", \"_class\" : \"org.springframework.data.mongodb.core.ReactiveMongoTemplateUpdateTests$Book\"}"));
|
||||
}).verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
private Flux<org.bson.Document> all(Class<?> type) {
|
||||
return Flux.from(collection(type).find(new org.bson.Document()));
|
||||
}
|
||||
|
||||
private MongoCollection<org.bson.Document> collection(Class<?> type) {
|
||||
return client.getDatabase(DB_NAME).getCollection(template.getCollectionName(type));
|
||||
}
|
||||
|
||||
@Document("scores")
|
||||
static class Score {
|
||||
|
||||
Integer id;
|
||||
String student;
|
||||
List<Integer> homework;
|
||||
List<Integer> quiz;
|
||||
Integer extraCredit;
|
||||
|
||||
public Score(Integer id, String student, List<Integer> homework, List<Integer> quiz, Integer extraCredit) {
|
||||
|
||||
this.id = id;
|
||||
this.student = student;
|
||||
this.homework = homework;
|
||||
this.quiz = quiz;
|
||||
this.extraCredit = extraCredit;
|
||||
}
|
||||
}
|
||||
|
||||
static class Versioned {
|
||||
|
||||
String id;
|
||||
@Version Long version;
|
||||
String value;
|
||||
|
||||
public Versioned(String id, String value) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
static class Book {
|
||||
|
||||
@Id Integer id;
|
||||
String title;
|
||||
String isbn;
|
||||
Author author;
|
||||
@Field("copies") Collection<Warehouse> stock;
|
||||
}
|
||||
|
||||
static class Author {
|
||||
|
||||
@Field("first") String firstname;
|
||||
@Field("last") String lastname;
|
||||
|
||||
public Author(String firstname, String lastname) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
}
|
||||
|
||||
static class Warehouse {
|
||||
|
||||
public Warehouse(String location, Integer qty) {
|
||||
this.location = location;
|
||||
this.qty = qty;
|
||||
}
|
||||
|
||||
@Field("warehouse") String location;
|
||||
Integer qty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class AggregationUpdateUnitTests {
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void createPipelineWithMultipleStages() {
|
||||
|
||||
assertThat(AggregationUpdate.update() //
|
||||
.set("stage-1").toValue("value-1") //
|
||||
.unset("stage-2") //
|
||||
.set("stage-3").toValue("value-3") //
|
||||
.toPipeline(Aggregation.DEFAULT_CONTEXT)) //
|
||||
.containsExactly(new Document("$set", new Document("stage-1", "value-1")),
|
||||
new Document("$unset", "stage-2"), new Document("$set", new Document("stage-3", "value-3")));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ReplaceRootOperation}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class ReplaceWithOperationUnitTests {
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rejectsNullField() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ReplaceWithOperation(null));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void shouldRenderValueCorrectly() {
|
||||
|
||||
ReplaceWithOperation operation = ReplaceWithOperation.replaceWithValue(new Document("hello", "world"));
|
||||
Document dbObject = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(dbObject).isEqualTo(Document.parse("{ $replaceWith : { hello: \"world\" } }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void shouldRenderExpressionCorrectly() {
|
||||
|
||||
ReplaceWithOperation operation = ReplaceWithOperation.replaceWithValueOf(VariableOperators //
|
||||
.mapItemsOf("array") //
|
||||
.as("element") //
|
||||
.andApply(AggregationFunctionExpressions.MULTIPLY.of("$$element", 10)));
|
||||
|
||||
Document dbObject = operation.toDocument(Aggregation.DEFAULT_CONTEXT);
|
||||
|
||||
assertThat(dbObject).isEqualTo(Document.parse("{ $replaceWith : { "
|
||||
+ "$map : { input : \"$array\" , as : \"element\" , in : { $multiply : [ \"$$element\" , 10]} } " + "} }"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Test;
|
||||
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.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class SetOperationUnitTests {
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void raisesErrorOnNullField() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> new SetOperation(null, "value"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersFieldReferenceCorrectly() {
|
||||
|
||||
assertThat(new SetOperation("name", "value").toPipelineStages(contextFor(Scores.class)))
|
||||
.containsExactly(Document.parse("{\"$set\" : {\"name\":\"value\"}}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersMappedFieldReferenceCorrectly() {
|
||||
|
||||
assertThat(new SetOperation("student", "value").toPipelineStages(contextFor(ScoresWithMappedField.class)))
|
||||
.containsExactly(Document.parse("{\"$set\" : {\"student_name\":\"value\"}}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersNestedMappedFieldReferenceCorrectly() {
|
||||
|
||||
assertThat(
|
||||
new SetOperation("scoresWithMappedField.student", "value").toPipelineStages(contextFor(ScoresWrapper.class)))
|
||||
.containsExactly(Document.parse("{\"$set\" : {\"scoresWithMappedField.student_name\":\"value\"}}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersTargetValueFieldReferenceCorrectly() {
|
||||
|
||||
assertThat(new SetOperation("name", Fields.field("value")).toPipelineStages(contextFor(Scores.class)))
|
||||
.containsExactly(Document.parse("{\"$set\" : {\"name\":\"$value\"}}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersMappedTargetValueFieldReferenceCorrectly() {
|
||||
|
||||
assertThat(
|
||||
new SetOperation("student", Fields.field("homework")).toPipelineStages(contextFor(ScoresWithMappedField.class)))
|
||||
.containsExactly(Document.parse("{\"$set\" : {\"student_name\":\"$home_work\"}}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersNestedMappedTargetValueFieldReferenceCorrectly() {
|
||||
|
||||
assertThat(new SetOperation("scoresWithMappedField.student", Fields.field("scoresWithMappedField.homework"))
|
||||
.toPipelineStages(contextFor(ScoresWrapper.class)))
|
||||
.containsExactly(Document
|
||||
.parse("{\"$set\" : {\"scoresWithMappedField.student_name\":\"$scoresWithMappedField.home_work\"}}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersTargetValueExpressionCorrectly() {
|
||||
|
||||
assertThat(SetOperation.builder().set("totalHomework").toValueOf(ArithmeticOperators.valueOf("homework").sum())
|
||||
.toPipelineStages(contextFor(Scores.class)))
|
||||
.containsExactly(Document.parse("{\"$set\" : {\"totalHomework\": { \"$sum\" : \"$homework\" }}}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void exposesFieldsCorrectly() {
|
||||
|
||||
ExposedFields fields = SetOperation.builder().set("totalHomework").toValue("A+") //
|
||||
.and() //
|
||||
.set("totalQuiz").toValue("B-") //
|
||||
.getFields();
|
||||
|
||||
assertThat(fields.getField("totalHomework")).isNotNull();
|
||||
assertThat(fields.getField("totalQuiz")).isNotNull();
|
||||
assertThat(fields.getField("does-not-exist")).isNull();
|
||||
}
|
||||
|
||||
private static AggregationOperationContext contextFor(@Nullable Class<?> type) {
|
||||
|
||||
if (type == null) {
|
||||
return Aggregation.DEFAULT_CONTEXT;
|
||||
}
|
||||
|
||||
MappingMongoConverter mongoConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE,
|
||||
new MongoMappingContext());
|
||||
mongoConverter.afterPropertiesSet();
|
||||
|
||||
return new RelaxedTypeBasedAggregationOperationContext(type, mongoConverter.getMappingContext(),
|
||||
new QueryMapper(mongoConverter));
|
||||
}
|
||||
|
||||
static class Scores {
|
||||
|
||||
String student;
|
||||
List<Integer> homework;
|
||||
}
|
||||
|
||||
static class ScoresWithMappedField {
|
||||
|
||||
@Field("student_name") String student;
|
||||
@Field("home_work") List<Integer> homework;
|
||||
}
|
||||
|
||||
static class ScoresWrapper {
|
||||
|
||||
Scores scores;
|
||||
ScoresWithMappedField scoresWithMappedField;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.aggregation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.bson.Document;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.Id;
|
||||
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.mapping.Field;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class UnsetOperationUnitTests {
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void raisesErrorOnNullField() {
|
||||
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> new UnsetOperation(null));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersSingleFieldReferenceCorrectly() {
|
||||
|
||||
assertThat(new UnsetOperation(Collections.singletonList("title")).toPipelineStages(contextFor(Book.class)))
|
||||
.containsExactly(Document.parse("{\"$unset\" : \"title\" }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersSingleMappedFieldReferenceCorrectly() {
|
||||
|
||||
assertThat(new UnsetOperation(Collections.singletonList("stock")).toPipelineStages(contextFor(Book.class)))
|
||||
.containsExactly(Document.parse("{\"$unset\" : \"copies\" }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersSingleNestedMappedFieldReferenceCorrectly() {
|
||||
|
||||
assertThat(
|
||||
new UnsetOperation(Collections.singletonList("author.firstname")).toPipelineStages(contextFor(Book.class)))
|
||||
.containsExactly(Document.parse("{\"$unset\" : \"author.first\"}"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void rendersMultipleFieldReferencesCorrectly() {
|
||||
|
||||
assertThat(new UnsetOperation(Arrays.asList("title", "author.firstname", "stock.location"))
|
||||
.toPipelineStages(contextFor(Book.class)))
|
||||
.containsExactly(Document.parse("{\"$unset\" : [\"title\", \"author.first\", \"copies.warehouse\"] }"));
|
||||
}
|
||||
|
||||
@Test // DATAMONGO-2331
|
||||
public void exposesFieldsCorrectly() {
|
||||
assertThat(UnsetOperation.unset("title").and("isbn").getFields()).isEqualTo(ExposedFields.from());
|
||||
}
|
||||
|
||||
private static AggregationOperationContext contextFor(@Nullable Class<?> type) {
|
||||
|
||||
if (type == null) {
|
||||
return Aggregation.DEFAULT_CONTEXT;
|
||||
}
|
||||
|
||||
MappingMongoConverter mongoConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE,
|
||||
new MongoMappingContext());
|
||||
mongoConverter.afterPropertiesSet();
|
||||
|
||||
return new TypeBasedAggregationOperationContext(type, mongoConverter.getMappingContext(),
|
||||
new QueryMapper(mongoConverter));
|
||||
}
|
||||
|
||||
static class Book {
|
||||
|
||||
@Id Integer id;
|
||||
String title;
|
||||
String isbn;
|
||||
Author author;
|
||||
@Field("copies") Collection<Warehouse> stock;
|
||||
}
|
||||
|
||||
static class Author {
|
||||
|
||||
@Field("first") String firstname;
|
||||
@Field("last") String lastname;
|
||||
}
|
||||
|
||||
static class Warehouse {
|
||||
|
||||
@Field("warehouse") String location;
|
||||
Integer qty;
|
||||
}
|
||||
}
|
||||
@@ -996,6 +996,61 @@ assertThat(p.getFirstName(), is("Mary"));
|
||||
assertThat(p.getAge(), is(1));
|
||||
----
|
||||
|
||||
[[mongo-template.aggregation-update]]
|
||||
=== Aggregation Pipeline Updates
|
||||
|
||||
The update methods exposed by `MongoOperations` and `ReactiveMongoOperations` also accept an <<mongo.aggregation, Aggregation Pipeline>> via `AggregationUpdate`.
|
||||
This allows to leverage https://docs.mongodb.com/manual/reference/method/db.collection.update/#update-with-aggregation-pipeline[MongoDB 4.2 aggregations] in an update operation.
|
||||
|
||||
The update can consist of the following stages:
|
||||
|
||||
* `AggregationUpdate.set(...).toValue(...)` -> `$set : { ... }`
|
||||
* `AggregationUpdate.unset(...)` -> `$unset : [ ... ]`
|
||||
* `AggregationUpdate.replaceWith(...)` -> `$replaceWith : { ... }`
|
||||
|
||||
.Update Aggregation
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
AggregationUpdate update = Aggregation.newUpdate()
|
||||
.set("average").toValue(ArithmeticOperators.valueOf("tests").avg()) <1>
|
||||
.set("grade").toValue(ConditionalOperators.switchCases( <2>
|
||||
when(valueOf("average").greaterThanEqualToValue(90)).then("A"),
|
||||
when(valueOf("average").greaterThanEqualToValue(80)).then("B"),
|
||||
when(valueOf("average").greaterThanEqualToValue(70)).then("C"),
|
||||
when(valueOf("average").greaterThanEqualToValue(60)).then("D"))
|
||||
.defaultTo("F")
|
||||
);
|
||||
|
||||
template.update(Student.class) <3>
|
||||
.apply(update)
|
||||
.all(); <4>
|
||||
----
|
||||
[source,javascript]
|
||||
----
|
||||
db.students.update( <3>
|
||||
{ },
|
||||
[
|
||||
{ $set: { average : { $avg: "$tests" } } }, <1>
|
||||
{ $set: { grade: { $switch: { <2>
|
||||
branches: [
|
||||
{ case: { $gte: [ "$average", 90 ] }, then: "A" },
|
||||
{ case: { $gte: [ "$average", 80 ] }, then: "B" },
|
||||
{ case: { $gte: [ "$average", 70 ] }, then: "C" },
|
||||
{ case: { $gte: [ "$average", 60 ] }, then: "D" }
|
||||
],
|
||||
default: "F"
|
||||
} } } }
|
||||
],
|
||||
{ multi: true } <4>
|
||||
)
|
||||
----
|
||||
<1> The 1st `$set` stage calculates a new field _average_ based on the average of the _tests_ field.
|
||||
<2> The 2nd `$set` stage calculates a new field _grade_ based on the _average_ field calculated by the first aggregation stage.
|
||||
<3> The pipeline is executed on the _students_ collection and uses `Student` for the aggregation field mapping.
|
||||
<4> Apply the update to all documents within the collection.
|
||||
====
|
||||
|
||||
[[mongo-template.find-and-replace]]
|
||||
=== Finding and Replacing Documents
|
||||
|
||||
|
||||
Reference in New Issue
Block a user