diff --git a/driver-core/src/main/com/mongodb/client/model/ReplaceOptions.java b/driver-core/src/main/com/mongodb/client/model/ReplaceOptions.java index 249a828364a..7a26e0997ba 100644 --- a/driver-core/src/main/com/mongodb/client/model/ReplaceOptions.java +++ b/driver-core/src/main/com/mongodb/client/model/ReplaceOptions.java @@ -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. @@ -221,6 +222,43 @@ public ReplaceOptions let(final Bson variables) { return this; } + /** + * Gets the sort criteria to apply to the operation. + * + *

+ * 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. + * + *

+ * 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{" @@ -231,6 +269,7 @@ public String toString() { + ", hintString=" + hintString + ", comment=" + comment + ", let=" + variables + + ", sort=" + sort + '}'; } } diff --git a/driver-core/src/main/com/mongodb/client/model/UpdateOptions.java b/driver-core/src/main/com/mongodb/client/model/UpdateOptions.java index d290610c816..88eb3cb6acb 100644 --- a/driver-core/src/main/com/mongodb/client/model/UpdateOptions.java +++ b/driver-core/src/main/com/mongodb/client/model/UpdateOptions.java @@ -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. @@ -256,6 +257,43 @@ public UpdateOptions let(final Bson variables) { return this; } + /** + * Gets the sort criteria to apply to the operation. + * + *

+ * 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. + * + *

+ * 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. + * + * @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{" @@ -267,6 +305,7 @@ public String toString() { + ", hintString=" + hintString + ", comment=" + comment + ", let=" + variables + + ", sort=" + sort + '}'; } } diff --git a/driver-core/src/main/com/mongodb/internal/bulk/UpdateRequest.java b/driver-core/src/main/com/mongodb/internal/bulk/UpdateRequest.java index 5a7df089641..e9d0b13c3cd 100644 --- a/driver-core/src/main/com/mongodb/internal/bulk/UpdateRequest.java +++ b/driver-core/src/main/com/mongodb/internal/bulk/UpdateRequest.java @@ -40,6 +40,7 @@ public final class UpdateRequest extends WriteRequest { private List 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) { @@ -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; + } } diff --git a/driver-core/src/main/com/mongodb/internal/connection/SplittablePayload.java b/driver-core/src/main/com/mongodb/internal/connection/SplittablePayload.java index d628a39238d..55bbac03b8b 100644 --- a/driver-core/src/main/com/mongodb/internal/connection/SplittablePayload.java +++ b/driver-core/src/main/com/mongodb/internal/connection/SplittablePayload.java @@ -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(); diff --git a/driver-core/src/main/com/mongodb/internal/operation/Operations.java b/driver-core/src/main/com/mongodb/internal/operation/Operations.java index d00c5a446c9..ecdd215ba91 100644 --- a/driver-core/src/main/com/mongodb/internal/operation/Operations.java +++ b/driver-core/src/main/com/mongodb/internal/operation/Operations.java @@ -464,7 +464,8 @@ MixedBulkWriteOperation bulkWrite(final List updateOneModel = (UpdateOneModel) writeModel; BsonValue update = updateOneModel.getUpdate() != null ? toBsonDocument(updateOneModel.getUpdate()) @@ -475,7 +476,8 @@ MixedBulkWriteOperation bulkWrite(final List updateManyModel = (UpdateManyModel) writeModel; BsonValue update = updateManyModel.getUpdate() != null ? toBsonDocument(updateManyModel.getUpdate()) diff --git a/driver-core/src/test/unit/com/mongodb/client/model/UpdateOptionsSpecification.groovy b/driver-core/src/test/unit/com/mongodb/client/model/UpdateOptionsSpecification.groovy index 0481120b05a..cd588936c18 100644 --- a/driver-core/src/test/unit/com/mongodb/client/model/UpdateOptionsSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/client/model/UpdateOptionsSpecification.groovy @@ -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))] + } } diff --git a/driver-core/src/test/unit/com/mongodb/internal/operation/UpdateRequestSpecification.groovy b/driver-core/src/test/unit/com/mongodb/internal/operation/UpdateRequestSpecification.groovy index f56411578cf..7ab84bb670b 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/operation/UpdateRequestSpecification.groovy +++ b/driver-core/src/test/unit/com/mongodb/internal/operation/UpdateRequestSpecification.groovy @@ -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))] + } } diff --git a/driver-legacy/src/main/com/mongodb/ReplaceRequest.java b/driver-legacy/src/main/com/mongodb/ReplaceRequest.java index 8427471af18..09443f1ee4d 100644 --- a/driver-legacy/src/main/com/mongodb/ReplaceRequest.java +++ b/driver-legacy/src/main/com/mongodb/ReplaceRequest.java @@ -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()); } } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java index 64cf204e565..192bde29e5e 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedCrudHelper.java @@ -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()); } @@ -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()); } diff --git a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java index 92a64024e92..0b2f4a6a2d5 100644 --- a/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java +++ b/driver-sync/src/test/functional/com/mongodb/client/unified/UnifiedTestModifications.java @@ -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 diff --git a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoCollectionSpecification.groovy b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoCollectionSpecification.groovy index 2fba3b90a0a..cbe43c10517 100644 --- a/driver-sync/src/test/unit/com/mongodb/client/internal/MongoCollectionSpecification.groovy +++ b/driver-sync/src/test/unit/com/mongodb/client/internal/MongoCollectionSpecification.groovy @@ -813,7 +813,7 @@ 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 @@ -821,7 +821,7 @@ class MongoCollectionSpecification extends Specification { 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 @@ -830,7 +830,7 @@ 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)], @@ -838,7 +838,8 @@ class MongoCollectionSpecification extends Specification { [null, Stub(ClientSession)], [true, false], [null, new BsonDocument('_id', new BsonInt32(1))], - [null, '_id_'] + [null, '_id_'], + [null, new BsonDocument('_id', new BsonInt32(1))] ].combinations() } @@ -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 filters, BsonDocument hintDoc, String hintStr -> + List 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 @@ -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() }