Skip to content

Commit

Permalink
Add fluent & reactive API for replace operation.
Browse files Browse the repository at this point in the history
...and update the documentation.

See: #4462
Original Pull Request: #4463
  • Loading branch information
christophstrobl committed Sep 11, 2023
1 parent f7549f7 commit 49ff6f1
Show file tree
Hide file tree
Showing 17 changed files with 1,171 additions and 159 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ default Optional<T> findAndModify() {
T findAndModifyValue();
}

/**
* Trigger <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a>
* execution by calling one of the terminating methods./** Trigger replace execution by calling one of the terminating
* methods.
*
* @author Christoph Strobl
* @since 4.2
*/
interface TerminatingReplace {

/**
* Find first and replace/upsert.
*
* @return never {@literal null}.
*/
UpdateResult replaceFirst();
}

/**
* Trigger
* <a href="https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/">findOneAndReplace</a>
Expand All @@ -95,7 +113,7 @@ default Optional<T> findAndModify() {
* @author Mark Paluch
* @since 2.1
*/
interface TerminatingFindAndReplace<T> {
interface TerminatingFindAndReplace<T> extends TerminatingReplace {

/**
* Find, replace and return the first matching document.
Expand Down Expand Up @@ -243,14 +261,30 @@ interface FindAndModifyWithOptions<T> {
TerminatingFindAndModify<T> withOptions(FindAndModifyOptions options);
}

/**
* @author Christoph Strobl
* @since 4.2
*/
interface ReplaceWithOptions extends TerminatingReplace {

/**
* Explicitly define {@link ReplaceOptions}.
*
* @param options must not be {@literal null}.
* @return new instance of {@link FindAndReplaceOptions}.
* @throws IllegalArgumentException if options is {@literal null}.
*/
TerminatingReplace withOptions(ReplaceOptions options);
}

/**
* Define {@link FindAndReplaceOptions}.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.1
*/
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T> {
interface FindAndReplaceWithOptions<T> extends TerminatingFindAndReplace<T>, ReplaceWithOptions {

/**
* Explicitly define {@link FindAndReplaceOptions} for the {@link Update}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,17 @@ public FindAndReplaceWithProjection<T> withOptions(FindAndReplaceOptions options
options, replacement, targetType);
}

@Override
public TerminatingReplace withOptions(ReplaceOptions options) {

FindAndReplaceOptions target = new FindAndReplaceOptions();
if(options.isUpsert()) {
target.upsert();
}
return new ExecutableUpdateSupport<>(template, domainType, query, update, collection, findAndModifyOptions,
target, replacement, targetType);
}

@Override
public UpdateWithUpdate<T> matching(Query query) {

Expand Down Expand Up @@ -175,6 +186,15 @@ public UpdateResult upsert() {
getCollectionName(), targetType);
}

@Override
public UpdateResult replaceFirst() {
if(replacement != null) {
return template.replace(query, domainType, replacement, findAndReplaceOptions != null ? findAndReplaceOptions : ReplaceOptions.none(), getCollectionName());
}

return template.replace(query, domainType, update, findAndReplaceOptions != null ? findAndReplaceOptions : ReplaceOptions.none(), getCollectionName());
}

private UpdateResult doUpdate(boolean multi, boolean upsert) {
return template.doUpdate(getCollectionName(), query, update, domainType, upsert, multi);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@
* @author Christoph Strobl
* @since 2.1
*/
public class FindAndReplaceOptions {
public class FindAndReplaceOptions extends ReplaceOptions {

private boolean returnNew;
private boolean upsert;

private static final FindAndReplaceOptions NONE = new FindAndReplaceOptions() {

Expand Down Expand Up @@ -109,7 +108,7 @@ public FindAndReplaceOptions returnNew() {
*/
public FindAndReplaceOptions upsert() {

this.upsert = true;
super.upsert();
return this;
}

Expand All @@ -122,13 +121,4 @@ public boolean isReturnNew() {
return returnNew;
}

/**
* Get the bit indicating if to create a new document if not exists.
*
* @return {@literal true} if set.
*/
public boolean isUpsert() {
return upsert;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
*
* @author Mark Pollack
* @author Oliver Gierke
* @author Christoph Strobl
* @see MongoAction
*/
public enum MongoActionOperation {

REMOVE, UPDATE, INSERT, INSERT_LIST, SAVE, BULK;
REMOVE, UPDATE, INSERT, INSERT_LIST, SAVE, BULK, REPLACE;
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions;
import org.springframework.data.mongodb.core.mapreduce.MapReduceResults;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
Expand Down Expand Up @@ -1754,119 +1755,115 @@ default long exactCount(Query query, String collectionName) {
<T> List<T> findAllAndRemove(Query query, Class<T> entityClass, String collectionName);

/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document.
* <br />
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
* document. <br />
* The collection name is derived from the {@literal replacement} type. <br />
* Options are defaulted to {@link ReplaceOptions#empty()}. <br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
* Options are defaulted to {@link ReplaceOptions#none()}.
*
* @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 query the {@link Query} class that specifies the {@link Criteria} used to find a record. The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
* {@link #getCollectionName(Class) derived} from the given replacement value.
* @since 4.2
*/
default <T> UpdateResult replace(Query query, T replacement) {
return replace(query, replacement, ReplaceOptions.empty());
return replace(query, replacement, ReplaceOptions.none());
}

/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement}
* document.<br />
* Options are defaulted to {@link ReplaceOptions#empty()}. <br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
* document. Options are defaulted to {@link ReplaceOptions#none()}.
*
* @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 query the {@link Query} class that specifies the {@link Criteria} used to find a record. The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param collectionName the collection to query. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
* {@link #getCollectionName(Class) derived} from the given replacement value.
* @since 4.2
*/
default <T> UpdateResult replace(Query query, T replacement, String collectionName) {
return replace(query, replacement, ReplaceOptions.empty(), collectionName);
return replace(query, replacement, ReplaceOptions.none(), collectionName);
}

/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
* taking {@link ReplaceOptions} into account.<br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
* document taking {@link ReplaceOptions} 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 query the {@link Query} class that specifies the {@link Criteria} used to find a record.The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
* {@link #getCollectionName(Class) derived} from the given replacement value.
* @since 4.2
*/
default <T> UpdateResult replace(Query query, T replacement, ReplaceOptions options) {
return replace(query, replacement, options, getCollectionName(ClassUtils.getUserClass(replacement)));
}

/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
* taking {@link ReplaceOptions} into account.<br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
* document taking {@link ReplaceOptions} 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 query the {@link Query} class that specifies the {@link Criteria} used to find a record. The query may *
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
* {@link #getCollectionName(Class) derived} from the given replacement value.
* @since 4.2
*/
default <T> UpdateResult replace(Query query, T replacement, ReplaceOptions options, String collectionName) {

Assert.notNull(replacement, "Replacement must not be null");
return replace(query, replacement, options, (Class<T>) ClassUtils.getUserClass(replacement), collectionName);
return replace(query, (Class<T>) ClassUtils.getUserClass(replacement), replacement, options, collectionName);
}

/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
* taking {@link ReplaceOptions} into account.<br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
* document taking {@link ReplaceOptions} 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 replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record. The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* @param entityType the type used for mapping the {@link Query} to domain type fields and deriving the collection
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
* from. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
* {@link #getCollectionName(Class) derived} from the given replacement value.
* @since 4.2
*/
default <S> UpdateResult replace(Query query, S replacement, ReplaceOptions options, Class<S> entityType) {

return replace(query, replacement, options, entityType, getCollectionName(ClassUtils.getUserClass(entityType)));
default <S,T> UpdateResult replace(Query query, Class<S> entityType, T replacement, ReplaceOptions options) {
return replace(query, entityType, replacement, options, getCollectionName(ClassUtils.getUserClass(entityType)));
}

/**
* Triggers <a href="https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/">replaceOne</a> to
* replace a single document matching {@link Criteria} of given {@link Query} with the {@code replacement} document
* taking {@link ReplaceOptions} into account.<br />
* <strong>NOTE:</strong> The replacement entity must not hold an {@literal id}.
* Replace a single document matching the {@link Criteria} of given {@link Query} with the {@code replacement}
* document taking {@link ReplaceOptions} 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 replacement the replacement document. Must not be {@literal null}.
* @param options the {@link FindAndModifyOptions} holding additional information. Must not be {@literal null}.
* @param query the {@link Query} class that specifies the {@link Criteria} used to find a record. The query may
* contain an index {@link Query#withHint(String) hint} or the {@link Query#collation(Collation) collation}
* to use. Must not be {@literal null}.
* @param entityType the type used for mapping the {@link Query} to domain type fields. Must not be {@literal null}.
* @param replacement the replacement document. Must not be {@literal null}.
* @param options the {@link ReplaceOptions} holding additional information. Must not be {@literal null}.
* @param collectionName the collection to query. Must not be {@literal null}.
* @return the {@link UpdateResult} which lets you access the results of the previous replacement.
* @throws org.springframework.data.mapping.MappingException if the collection name cannot be
* {@link #getCollectionName(Class) derived} from the given replacement value.
* @since 4.2
*/
<S> UpdateResult replace(Query query, S replacement, ReplaceOptions options, Class<S> entityType,
<S,T> UpdateResult replace(Query query, Class<S> entityType, T replacement, ReplaceOptions options,
String collectionName);

/**
Expand Down
Loading

0 comments on commit 49ff6f1

Please sign in to comment.