diff --git a/mongo/bulk_write.go b/mongo/bulk_write.go index 9c386786be..3af38acd4d 100644 --- a/mongo/bulk_write.go +++ b/mongo/bulk_write.go @@ -337,6 +337,7 @@ func (bw *bulkWrite) runUpdate(ctx context.Context, batch bulkWriteBatch) (opera filter: converted.Filter, update: converted.Replacement, hint: converted.Hint, + sort: converted.Sort, collation: converted.Collation, upsert: converted.Upsert, }.marshal(bw.collection.bsonOpts, bw.collection.registry) diff --git a/mongo/bulk_write_models.go b/mongo/bulk_write_models.go index c81f8127d6..f22c2b8c4e 100644 --- a/mongo/bulk_write_models.go +++ b/mongo/bulk_write_models.go @@ -125,6 +125,7 @@ type ReplaceOneModel struct { Filter interface{} Replacement interface{} Hint interface{} + Sort interface{} } // NewReplaceOneModel creates a new ReplaceOneModel. @@ -173,6 +174,14 @@ func (rom *ReplaceOneModel) SetUpsert(upsert bool) *ReplaceOneModel { return rom } +// SetSort specifies which document the operation replaces if the query matches multiple documents. The first document +// matched by the sort order will be replaced. This option is only valid for MongoDB versions >= 8.0. The driver will +// return an error if the sort parameter is a multi-key map. The default value is nil. +func (rom *ReplaceOneModel) SetSort(sort interface{}) *ReplaceOneModel { + rom.Sort = &sort + return rom +} + func (*ReplaceOneModel) writeModel() {} // UpdateOneModel is used to update at most one document in a BulkWrite operation. diff --git a/mongo/collection.go b/mongo/collection.go index 1f3ac8423f..698be220e1 100644 --- a/mongo/collection.go +++ b/mongo/collection.go @@ -759,6 +759,7 @@ func (coll *Collection) ReplaceOne(ctx context.Context, filter interface{}, uOpts.Hint = opt.Hint uOpts.Let = opt.Let uOpts.Comment = opt.Comment + uOpts.Sort = opt.Sort updateOptions = append(updateOptions, uOpts) } diff --git a/mongo/integration/crud_helpers_test.go b/mongo/integration/crud_helpers_test.go index 0ade1f3478..d7eb7c833a 100644 --- a/mongo/integration/crud_helpers_test.go +++ b/mongo/integration/crud_helpers_test.go @@ -1000,6 +1000,8 @@ func executeReplaceOne(mt *mtest.T, sess mongo.Session, args bson.Raw) (*mongo.U opts = opts.SetCollation(createCollation(mt, val.Document())) case "hint": opts = opts.SetHint(createHint(mt, val)) + case "sort": + opts = opts.SetSort(createSort(mt, val)) case "session": default: mt.Fatalf("unrecognized replaceOne option: %v", key) diff --git a/mongo/integration/unified/bulkwrite_helpers.go b/mongo/integration/unified/bulkwrite_helpers.go index 5a12367991..632abe9698 100644 --- a/mongo/integration/unified/bulkwrite_helpers.go +++ b/mongo/integration/unified/bulkwrite_helpers.go @@ -248,6 +248,12 @@ func createBulkWriteModel(rawModel bson.Raw) (mongo.WriteModel, error) { return nil, fmt.Errorf("error creating hint: %w", err) } rom.SetHint(hint) + case "sort": + sort, err := createSort(val) + if err != nil { + return nil, fmt.Errorf("error creating sort: %w", err) + } + rom.SetSort(sort) case "replacement": replacement = val.Document() case "upsert": diff --git a/mongo/integration/unified/collection_operation_execution.go b/mongo/integration/unified/collection_operation_execution.go index 1235e4d62d..fb22506149 100644 --- a/mongo/integration/unified/collection_operation_execution.go +++ b/mongo/integration/unified/collection_operation_execution.go @@ -1248,6 +1248,12 @@ func executeReplaceOne(ctx context.Context, operation *operation) (*operationRes return nil, fmt.Errorf("error creating hint: %w", err) } opts.SetHint(hint) + case "sort": + sort, err := createSort(val) + if err != nil { + return nil, fmt.Errorf("error creating sort: %w", err) + } + opts.SetSort(sort) case "replacement": replacement = val.Document() case "upsert": diff --git a/mongo/options/replaceoptions.go b/mongo/options/replaceoptions.go index f7d3960194..32878d29ea 100644 --- a/mongo/options/replaceoptions.go +++ b/mongo/options/replaceoptions.go @@ -40,6 +40,12 @@ type ReplaceOptions struct { // Values must be constant or closed expressions that do not reference document fields. Parameters can then be // accessed as variables in an aggregate expression context (e.g. "$$var"). Let interface{} + + // A document specifying which document should be replaced if the filter used by the operation matches multiple + // documents in the collection. If set, the first document in the sorted order will be replaced. This option is + // only valid for MongoDB versions >= 8.0. The driver will return an error if the sort parameter is a multi-key + // map. The default value is nil. + Sort interface{} } // Replace creates a new ReplaceOptions instance. @@ -83,6 +89,12 @@ func (ro *ReplaceOptions) SetLet(l interface{}) *ReplaceOptions { return ro } +// SetSort sets the value for the Sort field. +func (ro *ReplaceOptions) SetSort(s interface{}) *ReplaceOptions { + ro.Sort = s + return ro +} + // MergeReplaceOptions combines the given ReplaceOptions instances into a single ReplaceOptions in a last-one-wins // fashion. // @@ -112,6 +124,9 @@ func MergeReplaceOptions(opts ...*ReplaceOptions) *ReplaceOptions { if ro.Let != nil { rOpts.Let = ro.Let } + if ro.Sort != nil { + rOpts.Sort = ro.Sort + } } return rOpts diff --git a/testdata/crud/unified/bulkWrite-replaceOne-sort.json b/testdata/crud/unified/bulkWrite-replaceOne-sort.json new file mode 100644 index 0000000000..c0bd383514 --- /dev/null +++ b/testdata/crud/unified/bulkWrite-replaceOne-sort.json @@ -0,0 +1,239 @@ +{ + "description": "BulkWrite replaceOne-sort", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "BulkWrite replaceOne with sort option", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "replaceOne": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "update" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 1 + } + ] + } + ] + }, + { + "description": "BulkWrite replaceOne with sort option unsupported (server-side error)", + "runOnRequirements": [ + { + "maxServerVersion": "7.99" + } + ], + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "replaceOne": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + } + } + ] + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/bulkWrite-updateOne-sort.json b/testdata/crud/unified/bulkWrite-updateOne-sort.json index 4ea4f26665..f78bd3bf3e 100644 --- a/testdata/crud/unified/bulkWrite-updateOne-sort.json +++ b/testdata/crud/unified/bulkWrite-updateOne-sort.json @@ -187,7 +187,6 @@ ] }, "expectError": { - "errorContains": "'update.updates.sort' is an unknown field", "isClientError": false } } diff --git a/testdata/crud/unified/replaceOne-sort.json b/testdata/crud/unified/replaceOne-sort.json new file mode 100644 index 0000000000..4960175f84 --- /dev/null +++ b/testdata/crud/unified/replaceOne-sort.json @@ -0,0 +1,233 @@ +{ + "description": "replaceOne-sort", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "ReplaceOne with sort option", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "update" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 1 + } + ] + } + ] + }, + { + "description": "replaceOne with sort option unsupported (server-side error)", + "runOnRequirements": [ + { + "minServerVersion": "4.2.0", + "maxServerVersion": "7.99" + } + ], + "operations": [ + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/updateMany-sort.json b/testdata/crud/unified/updateMany-sort.json index 4cf1f05c63..d54fba4ba6 100644 --- a/testdata/crud/unified/updateMany-sort.json +++ b/testdata/crud/unified/updateMany-sort.json @@ -48,98 +48,6 @@ "tests": [ { "description": "UpdateMany with sort option unsupported", - "runOnRequirements": [ - { - "minServerVersion": "8.0" - } - ], - "operations": [ - { - "name": "updateMany", - "object": "collection0", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "sort": { - "_id": -1 - }, - "update": { - "$inc": { - "x": 1 - } - } - }, - "expectError": { - "isError": true - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "update": "coll0", - "updates": [ - { - "q": { - "_id": { - "$gt": 1 - } - }, - "u": { - "$inc": { - "x": 1 - } - }, - "sort": { - "_id": -1 - }, - "multi": true, - "upsert": { - "$$unsetOrMatches": false - } - } - ] - } - } - } - ] - } - ], - "outcome": [ - { - "collectionName": "coll0", - "databaseName": "crud-tests", - "documents": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ] - }, - { - "description": "updateMany with sort option unsupported (server-side error)", - "runOnRequirements": [ - { - "maxServerVersion": "7.99" - } - ], "operations": [ { "name": "updateMany", @@ -160,7 +68,6 @@ } }, "expectError": { - "errorContains": "'update.updates.sort' is an unknown field", "isClientError": false } } diff --git a/testdata/crud/unified/updateOne-sort.json b/testdata/crud/unified/updateOne-sort.json index 9fe28f69f1..8fe4f50b94 100644 --- a/testdata/crud/unified/updateOne-sort.json +++ b/testdata/crud/unified/updateOne-sort.json @@ -174,7 +174,6 @@ } }, "expectError": { - "errorContains": "'update.updates.sort' is an unknown field", "isClientError": false } }