Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow updateOne and replaceOne to supply sort option #1585

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions driver-core/src/main/com/mongodb/client/model/ReplaceOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class ReplaceOptions {
private String hintString;
private BsonValue comment;
private Bson variables;
private Bson sort;

/**
* Returns true if a new document should be inserted if there are no matches to the query filter. The default is false.
Expand Down Expand Up @@ -221,6 +222,43 @@ public ReplaceOptions let(final Bson variables) {
return this;
}

/**
* Gets the sort criteria to apply to the operation.
*
* <p>
* The sort criteria determines which document the operation replaces if the query matches multiple documents.
* The first document matched by the sort criteria will be replaced.
* The default is null, which means no specific sort criteria is applied.
*
* @return a document describing the sort criteria, or null if no sort is specified.
* @mongodb.driver.manual reference/method/db.collection.replaceOne/ Sort
* @mongodb.server.release 8.0
* @since 5.3
* @see #sort(Bson)
*/
@Nullable
public Bson getSort() {
return sort;
}

/**
* Sets the sort criteria to apply to the operation. A null value means no sort criteria is set.
*
* <p>
* The sort criteria determines which document the operation replaces if the query matches multiple documents.
* The first document matched by the specified sort criteria will be replaced.
*
* @param sort the sort criteria, which may be null.
* @return this
* @mongodb.driver.manual reference/method/db.collection.replaceOne/ Sort
* @mongodb.server.release 8.0
* @since 5.3
*/
public ReplaceOptions sort(@Nullable final Bson sort) {
this.sort = sort;
return this;
}

@Override
public String toString() {
return "ReplaceOptions{"
Expand All @@ -231,6 +269,7 @@ public String toString() {
+ ", hintString=" + hintString
+ ", comment=" + comment
+ ", let=" + variables
+ ", sort=" + sort
+ '}';
}
}
39 changes: 39 additions & 0 deletions driver-core/src/main/com/mongodb/client/model/UpdateOptions.java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s also add unit tests to UpdateOptionsSpecification and UpdateRequestSpecification.

Additionally, we should update the tests in MongoCollectionSpecification to cover the logic for the new option.

Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class UpdateOptions {
private String hintString;
private BsonValue comment;
private Bson variables;
private Bson sort;

/**
* Returns true if a new document should be inserted if there are no matches to the query filter. The default is false.
Expand Down Expand Up @@ -256,6 +257,43 @@ public UpdateOptions let(final Bson variables) {
return this;
}

/**
* Gets the sort criteria to apply to the operation.
*
* <p>
* The sort criteria determines which document the operation updates if the query matches multiple documents.
* The first document matched by the sort criteria will be updated.
* The default is null, which means no specific sort criteria is applied.
*
* @return a document describing the sort criteria, or null if no sort is specified.
* @mongodb.driver.manual reference/method/db.collection.updateOne/ Sort
* @mongodb.server.release 8.0
* @since 5.3
* @see #sort(Bson)
*/
@Nullable
public Bson getSort() {
return sort;
}

/**
* Sets the sort criteria to apply to the operation. A null value means no sort criteria is set.
*
* <p>
* The sort criteria determines which document the operation updates if the query matches multiple documents.
* The first document matched by the specified sort criteria will be updated.
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we clarify the purpose of this sort option in the Javadoc? It would be helpful to explain how it determines which document is updated when multiple matches are found.

Suggested change
*
*
* When multiple documents match the query, the sort order specifies the document
* to be updated first.
*

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do!

* @param sort the sort criteria, which may be null.
* @return this
* @mongodb.driver.manual reference/method/db.collection.updateOne/ Sort
* @mongodb.server.release 8.0
* @since 5.3
*/
public UpdateOptions sort(@Nullable final Bson sort) {
this.sort = sort;
return this;
}

@Override
public String toString() {
return "UpdateOptions{"
Expand All @@ -267,6 +305,7 @@ public String toString() {
+ ", hintString=" + hintString
+ ", comment=" + comment
+ ", let=" + variables
+ ", sort=" + sort
+ '}';
}
}
11 changes: 11 additions & 0 deletions driver-core/src/main/com/mongodb/internal/bulk/UpdateRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public final class UpdateRequest extends WriteRequest {
private List<BsonDocument> arrayFilters;
@Nullable private BsonDocument hint;
@Nullable private String hintString;
@Nullable private BsonDocument sort;

public UpdateRequest(final BsonDocument filter, @Nullable final BsonValue update, final Type updateType) {
if (updateType != Type.UPDATE && updateType != Type.REPLACE) {
Expand Down Expand Up @@ -128,5 +129,15 @@ public UpdateRequest hintString(@Nullable final String hint) {
this.hintString = hint;
return this;
}

@Nullable
public BsonDocument getSort() {
return sort;
}

public UpdateRequest sort(@Nullable final BsonDocument sort) {
this.sort = sort;
return this;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,11 @@ public void encode(final BsonWriter writer, final WriteRequestWithIndex writeReq
} else if (update.getHintString() != null) {
writer.writeString("hint", update.getHintString());
}
if (update.getSort() != null) {
writer.writeName("sort");
getCodec(assertNotNull(update.getSort())).encode(writer, assertNotNull(update.getSort()),
EncoderContext.builder().build());
}
writer.writeEndDocument();
} else {
DeleteRequest deleteRequest = (DeleteRequest) writeRequestWithIndex.getWriteRequest();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,8 @@ MixedBulkWriteOperation bulkWrite(final List<? extends WriteModel<? extends TDoc
.upsert(replaceOneModel.getReplaceOptions().isUpsert())
.collation(replaceOneModel.getReplaceOptions().getCollation())
.hint(toBsonDocument(replaceOneModel.getReplaceOptions().getHint()))
.hintString(replaceOneModel.getReplaceOptions().getHintString());
.hintString(replaceOneModel.getReplaceOptions().getHintString())
.sort(toBsonDocument(replaceOneModel.getReplaceOptions().getSort()));
} else if (writeModel instanceof UpdateOneModel) {
UpdateOneModel<TDocument> updateOneModel = (UpdateOneModel<TDocument>) writeModel;
BsonValue update = updateOneModel.getUpdate() != null ? toBsonDocument(updateOneModel.getUpdate())
Expand All @@ -475,7 +476,8 @@ MixedBulkWriteOperation bulkWrite(final List<? extends WriteModel<? extends TDoc
.collation(updateOneModel.getOptions().getCollation())
.arrayFilters(toBsonDocumentList(updateOneModel.getOptions().getArrayFilters()))
.hint(toBsonDocument(updateOneModel.getOptions().getHint()))
.hintString(updateOneModel.getOptions().getHintString());
.hintString(updateOneModel.getOptions().getHintString())
.sort(toBsonDocument(updateOneModel.getOptions().getSort()));
} else if (writeModel instanceof UpdateManyModel) {
UpdateManyModel<TDocument> updateManyModel = (UpdateManyModel<TDocument>) writeModel;
BsonValue update = updateManyModel.getUpdate() != null ? toBsonDocument(updateManyModel.getUpdate())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,12 @@ class UpdateOptionsSpecification extends Specification {
where:
hint << [null, '_id_']
}

def 'should set sort'() {
expect:
new UpdateOptions().sort(sort).getSort() == sort

where:
sort << [null, new BsonDocument('_id', new BsonInt32(1))]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,13 @@ class UpdateRequestSpecification extends Specification {
where:
arrayFilters << [null, [], [new BsonDocument('a.b', new BsonInt32(42))]]
}

def 'should set sort property'() {
expect:
new UpdateRequest(new BsonDocument(), new BsonDocument(), type).sort(sort).getSort() == sort

where:
type << [WriteRequest.Type.UPDATE, WriteRequest.Type.REPLACE]
sort << [null, new BsonDocument('_id', new BsonInt32(1))]
}
}
4 changes: 2 additions & 2 deletions driver-legacy/src/main/com/mongodb/ReplaceRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ com.mongodb.internal.bulk.WriteRequest toNew(final DBCollection dbCollection) {
return new UpdateRequest(new BsonDocumentWrapper<>(query, codec),
new BsonDocumentWrapper<>(document, replacementCodec),
com.mongodb.internal.bulk.WriteRequest.Type.REPLACE)
.upsert(isUpsert())
.collation(getCollation());
.upsert(isUpsert())
.collation(getCollation());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,9 @@ private UpdateOptions getUpdateOptions(final BsonDocument arguments) {
case "collation":
options.collation(asCollation(cur.getValue().asDocument()));
break;
case "sort":
options.sort(cur.getValue().asDocument());
break;
default:
throw new UnsupportedOperationException("Unsupported argument: " + cur.getKey());
}
Expand Down Expand Up @@ -1193,6 +1196,9 @@ private ReplaceOptions getReplaceOptions(final BsonDocument arguments) {
case "collation":
options.collation(asCollation(cur.getValue().asDocument()));
break;
case "sort":
options.sort(cur.getValue().asDocument());
break;
default:
throw new UnsupportedOperationException("Unsupported argument: " + cur.getKey());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,6 @@ public static void doSkips(final TestDef def) {
.test("crud", "findOneAndUpdate-hint-unacknowledged", "Unacknowledged findOneAndUpdate with hint document on 4.4+ server")
.test("crud", "findOneAndDelete-hint-unacknowledged", "Unacknowledged findOneAndDelete with hint string on 4.4+ server")
.test("crud", "findOneAndDelete-hint-unacknowledged", "Unacknowledged findOneAndDelete with hint document on 4.4+ server");
def.skipJira("https://jira.mongodb.org/browse/JAVA-5622")
.test("crud", "updateOne-sort", "UpdateOne with sort option")
.test("crud", "updateOne-sort", "updateOne with sort option unsupported (server-side error)")
.test("crud", "replaceOne-sort", "ReplaceOne with sort option")
.test("crud", "replaceOne-sort", "replaceOne with sort option unsupported (server-side error)")
.test("crud", "BulkWrite updateOne-sort", "BulkWrite updateOne with sort option")
.test("crud", "BulkWrite updateOne-sort", "BulkWrite updateOne with sort option unsupported (server-side error)")
.test("crud", "BulkWrite replaceOne-sort", "BulkWrite replaceOne with sort option")
.test("crud", "BulkWrite replaceOne-sort", "BulkWrite replaceOne with sort option unsupported (server-side error)");

// gridfs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,15 +813,15 @@ class MongoCollectionSpecification extends Specification {
def expectedOperation = { boolean upsert, WriteConcern wc, Boolean bypassValidation, Collation collation ->
new MixedBulkWriteOperation(namespace,
[new UpdateRequest(new BsonDocument('a', new BsonInt32(1)), new BsonDocument('a', new BsonInt32(10)), REPLACE)
.collation(collation).upsert(upsert).hint(hint).hintString(hintString)], true, wc, retryWrites)
.collation(collation).upsert(upsert).hint(hint).hintString(hintString).sort(sort)], true, wc, retryWrites)
.bypassDocumentValidation(bypassValidation)
}
def replaceOneMethod = collection.&replaceOne

when:
def result = execute(replaceOneMethod, session, new Document('a', 1), new Document('a', 10),
new ReplaceOptions().upsert(true).bypassDocumentValidation(bypassDocumentValidation).collation(collation)
.hint(hint).hintString(hintString))
.hint(hint).hintString(hintString).sort(sort))
executor.getClientSession() == session
def operation = executor.getWriteOperation() as MixedBulkWriteOperation

Expand All @@ -830,15 +830,16 @@ class MongoCollectionSpecification extends Specification {
result == expectedResult

where:
[bypassDocumentValidation, modifiedCount, upsertedId, writeConcern, session, retryWrites, hint, hintString] << [
[bypassDocumentValidation, modifiedCount, upsertedId, writeConcern, session, retryWrites, hint, hintString, sort] << [
[null, true, false],
[1],
[null, new BsonInt32(42)],
[ACKNOWLEDGED, UNACKNOWLEDGED],
[null, Stub(ClientSession)],
[true, false],
[null, new BsonDocument('_id', new BsonInt32(1))],
[null, '_id_']
[null, '_id_'],
[null, new BsonDocument('_id', new BsonInt32(1))]
].combinations()
}

Expand Down Expand Up @@ -880,11 +881,11 @@ class MongoCollectionSpecification extends Specification {
def collection = new MongoCollectionImpl(namespace, Document, codecRegistry, readPreference, writeConcern,
retryWrites, true, readConcern, JAVA_LEGACY, null, TIMEOUT_SETTINGS, executor)
def expectedOperation = { boolean upsert, WriteConcern wc, Boolean bypassDocumentValidation, Collation collation,
List<Bson> filters, BsonDocument hintDoc, String hintStr ->
List<Bson> filters, BsonDocument hintDoc, String hintStr, BsonDocument sortDoc ->
new MixedBulkWriteOperation(namespace,
[new UpdateRequest(new BsonDocument('a', new BsonInt32(1)), new BsonDocument('a', new BsonInt32(10)), UPDATE)
.multi(false).upsert(upsert).collation(collation).arrayFilters(filters)
.hint(hintDoc).hintString(hintStr)], true, wc, retryWrites)
.hint(hintDoc).hintString(hintStr).sort(sortDoc)], true, wc, retryWrites)
.bypassDocumentValidation(bypassDocumentValidation)
}
def updateOneMethod = collection.&updateOne
Expand All @@ -894,29 +895,30 @@ class MongoCollectionSpecification extends Specification {
def operation = executor.getWriteOperation() as MixedBulkWriteOperation

then:
expect operation, isTheSameAs(expectedOperation(false, writeConcern, null, null, null, null, null))
expect operation, isTheSameAs(expectedOperation(false, writeConcern, null, null, null, null, null, null))
executor.getClientSession() == session
result == expectedResult

when:
result = execute(updateOneMethod, session, new Document('a', 1), new Document('a', 10),
new UpdateOptions().upsert(true).bypassDocumentValidation(true).collation(collation)
.arrayFilters(arrayFilters).hint(hint).hintString(hintString))
.arrayFilters(arrayFilters).hint(hint).hintString(hintString).sort(sort))
operation = executor.getWriteOperation() as MixedBulkWriteOperation

then:
expect operation, isTheSameAs(expectedOperation(true, writeConcern, true, collation, arrayFilters, hint, hintString))
expect operation, isTheSameAs(expectedOperation(true, writeConcern, true, collation, arrayFilters, hint, hintString, sort))
executor.getClientSession() == session
result == expectedResult

where:
[writeConcern, arrayFilters, session, retryWrites, hint, hintString] << [
[writeConcern, arrayFilters, session, retryWrites, hint, hintString, sort] << [
[ACKNOWLEDGED, UNACKNOWLEDGED],
[null, [], [new BsonDocument('a.b', new BsonInt32(42))]],
[null, Stub(ClientSession)],
[true, false],
[null, new BsonDocument('_id', new BsonInt32(1))],
[null, '_id_']
[null, '_id_'],
[null, new BsonDocument('_id', new BsonInt32(1))]
].combinations()
}

Expand Down