diff --git a/internal/integration/unified/client_operation_execution.go b/internal/integration/unified/client_operation_execution.go index 08ae2d52e4..2bb2ec9326 100644 --- a/internal/integration/unified/client_operation_execution.go +++ b/internal/integration/unified/client_operation_execution.go @@ -328,6 +328,7 @@ func createClientUpdateOneModel(value bson.Raw) (*mongo.ClientBulkWrite, error) Collation *options.Collation Hint *bson.RawValue Upsert *bool + Sort *bson.RawValue } err := bson.Unmarshal(value, &v) if err != nil { @@ -340,12 +341,17 @@ func createClientUpdateOneModel(value bson.Raw) (*mongo.ClientBulkWrite, error) return nil, err } } + var sort interface{} + if v.Sort != nil { + sort = v.Sort.Document() + } model := &mongo.ClientUpdateOneModel{ Filter: v.Filter, Update: v.Update, Collation: v.Collation, Hint: hint, Upsert: v.Upsert, + Sort: sort, } if len(v.ArrayFilters) > 0 { model.ArrayFilters = v.ArrayFilters @@ -405,6 +411,7 @@ func createClientReplaceOneModel(value bson.Raw) (*mongo.ClientBulkWrite, error) Collation *options.Collation Hint *bson.RawValue Upsert *bool + Sort *bson.RawValue } err := bson.Unmarshal(value, &v) if err != nil { @@ -417,6 +424,10 @@ func createClientReplaceOneModel(value bson.Raw) (*mongo.ClientBulkWrite, error) return nil, err } } + var sort interface{} + if v.Sort != nil { + sort = v.Sort.Document() + } ns := strings.SplitN(v.Namespace, ".", 2) return &mongo.ClientBulkWrite{ Database: ns[0], @@ -427,6 +438,7 @@ func createClientReplaceOneModel(value bson.Raw) (*mongo.ClientBulkWrite, error) Collation: v.Collation, Hint: hint, Upsert: v.Upsert, + Sort: sort, }, }, nil } diff --git a/mongo/bulk_write_models.go b/mongo/bulk_write_models.go index d4aff51625..ddc3043a2a 100644 --- a/mongo/bulk_write_models.go +++ b/mongo/bulk_write_models.go @@ -183,8 +183,9 @@ func (rom *ReplaceOneModel) SetUpsert(upsert bool) *ReplaceOneModel { } // 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. +// matched by the sort order will be replaced. This option is only valid for MongoDB versions >= 8.0. The sort parameter +// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The +// default value is nil. func (rom *ReplaceOneModel) SetSort(sort interface{}) *ReplaceOneModel { rom.Sort = sort return rom @@ -259,8 +260,9 @@ func (uom *UpdateOneModel) SetUpsert(upsert bool) *UpdateOneModel { } // SetSort specifies which document the operation updates if the query matches multiple documents. The first document -// matched by the sort order will be updated. 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. +// matched by the sort order will be updated. This option is only valid for MongoDB versions >= 8.0. The sort parameter +// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The +// default value is nil. func (uom *UpdateOneModel) SetSort(sort interface{}) *UpdateOneModel { uom.Sort = sort return uom diff --git a/mongo/client_bulk_write.go b/mongo/client_bulk_write.go index a4ac18d466..ab2d8b6acc 100644 --- a/mongo/client_bulk_write.go +++ b/mongo/client_bulk_write.go @@ -315,6 +315,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, tota arrayFilters: model.ArrayFilters, collation: model.Collation, upsert: model.Upsert, + sort: model.Sort, multi: false, checkDollarKey: true, }).marshal(mb.client.bsonOpts, mb.client.registry) @@ -342,6 +343,7 @@ func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, tota arrayFilters: nil, collation: model.Collation, upsert: model.Upsert, + sort: model.Sort, multi: false, checkDollarKey: false, }).marshal(mb.client.bsonOpts, mb.client.registry) @@ -603,6 +605,7 @@ type clientUpdateDoc struct { hint interface{} arrayFilters []interface{} collation *options.Collation + sort interface{} upsert *bool multi bool checkDollarKey bool @@ -657,6 +660,17 @@ func (d *clientUpdateDoc) marshal(bsonOpts *options.BSONOptions, registry *bson. doc = bsoncore.AppendValueElement(doc, "hint", hintVal) } + if d.sort != nil { + if isUnorderedMap(d.sort) { + return nil, ErrMapForOrderedArgument{"sort"} + } + sortVal, err := marshalValue(d.sort, bsonOpts, registry) + if err != nil { + return nil, err + } + doc = bsoncore.AppendValueElement(doc, "sort", sortVal) + } + return bsoncore.AppendDocumentEnd(doc, uidx) } diff --git a/mongo/client_bulk_write_models.go b/mongo/client_bulk_write_models.go index fdcac3d9ef..87550eee1f 100644 --- a/mongo/client_bulk_write_models.go +++ b/mongo/client_bulk_write_models.go @@ -52,6 +52,7 @@ type ClientUpdateOneModel struct { Update interface{} ArrayFilters []interface{} Hint interface{} + Sort interface{} } // NewClientUpdateOneModel creates a new ClientUpdateOneModel. @@ -105,6 +106,15 @@ func (uom *ClientUpdateOneModel) SetUpsert(upsert bool) *ClientUpdateOneModel { return uom } +// SetSort specifies which document the operation updates if the query matches multiple documents. The first document +// matched by the sort order will be updated. This option is only valid for MongoDB versions >= 8.0. The sort parameter +// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The +// default value is nil. +func (uom *ClientUpdateOneModel) SetSort(sort interface{}) *ClientUpdateOneModel { + uom.Sort = sort + return uom +} + // ClientUpdateManyModel is used to update multiple documents in a client-level BulkWrite operation. // // See corresponding setter methods for documentation. @@ -176,6 +186,7 @@ type ClientReplaceOneModel struct { Filter interface{} Replacement interface{} Hint interface{} + Sort interface{} } // NewClientReplaceOneModel creates a new ClientReplaceOneModel. @@ -222,6 +233,15 @@ func (rom *ClientReplaceOneModel) SetUpsert(upsert bool) *ClientReplaceOneModel 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 sort parameter +// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The +// default value is nil. +func (rom *ClientReplaceOneModel) SetSort(sort interface{}) *ClientReplaceOneModel { + rom.Sort = sort + return rom +} + // ClientDeleteOneModel is used to delete at most one document in a client-level BulkWriteOperation. // // See corresponding setter methods for documentation. diff --git a/mongo/options/findoptions.go b/mongo/options/findoptions.go index e036b9ed64..92701c9dd5 100644 --- a/mongo/options/findoptions.go +++ b/mongo/options/findoptions.go @@ -258,8 +258,8 @@ func (f *FindOptionsBuilder) SetSkip(i int64) *FindOptionsBuilder { } // SetSort sets the value for the Sort field. Sort is a document specifying the order in which -// documents should be returned. The driver will return an error if the sort parameter is a -// multi-key map. +// documents should be returned. The sort parameter is evaluated sequentially, so the driver will +// return an error if it is a multi-key map (which is unordeded). The default value is nil. func (f *FindOptionsBuilder) SetSort(sort interface{}) *FindOptionsBuilder { f.Opts = append(f.Opts, func(opts *FindOptions) error { opts.Sort = sort @@ -426,8 +426,9 @@ func (f *FindOneOptionsBuilder) SetSkip(i int64) *FindOneOptionsBuilder { } // SetSort sets the value for the Sort field. Sets a document specifying the sort order to -// apply to the query. The first document in the sorted order will be returned. The driver -// will return an error if the sort parameter is a multi-key map. +// apply to the query. The first document in the sorted order will be returned. The sort +// parameter is evaluated sequentially, so the driver will return an error if it is a multi- +// key map (which is unordeded). The default value is nil. func (f *FindOneOptionsBuilder) SetSort(sort interface{}) *FindOneOptionsBuilder { f.Opts = append(f.Opts, func(opts *FindOneOptions) error { opts.Sort = sort @@ -539,8 +540,9 @@ func (f *FindOneAndReplaceOptionsBuilder) SetReturnDocument(rd ReturnDocument) * // SetSort sets the value for the Sort field. Sets 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. The driver will return an -// error if the sort parameter is a multi-key map. The default value is nil. +// If set, the first document in the sorted order will be replaced. The sort parameter is evaluated +// sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). +// The default value is nil. func (f *FindOneAndReplaceOptionsBuilder) SetSort(sort interface{}) *FindOneAndReplaceOptionsBuilder { f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error { opts.Sort = sort @@ -716,8 +718,9 @@ func (f *FindOneAndUpdateOptionsBuilder) SetReturnDocument(rd ReturnDocument) *F // SetSort sets the value for the Sort field. Sets a document specifying which document should // be updated if the filter used by the operation matches multiple documents in the collection. -// If set, the first document in the sorted order will be updated. The driver will return an -// error if the sort parameter is a multi-key map. The default value is nil. +// If set, the first document in the sorted order will be updated. The sort parameter is evaluated +// sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). +// The default value is nil. func (f *FindOneAndUpdateOptionsBuilder) SetSort(sort interface{}) *FindOneAndUpdateOptionsBuilder { f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error { opts.Sort = sort @@ -846,8 +849,9 @@ func (f *FindOneAndDeleteOptionsBuilder) SetProjection(projection interface{}) * // SetSort sets the value for the Sort field. Sets 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 selected for replacement. The driver -// will return an error if the sort parameter is a multi-key map. The default value is nil. +// If set, the first document in the sorted order will be deleted. The sort parameter is evaluated +// sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). +// The default value is nil. func (f *FindOneAndDeleteOptionsBuilder) SetSort(sort interface{}) *FindOneAndDeleteOptionsBuilder { f.Opts = append(f.Opts, func(opts *FindOneAndDeleteOptions) error { opts.Sort = sort diff --git a/mongo/options/gridfsoptions.go b/mongo/options/gridfsoptions.go index e63978ca70..43af42b456 100644 --- a/mongo/options/gridfsoptions.go +++ b/mongo/options/gridfsoptions.go @@ -328,8 +328,9 @@ func (f *GridFSFindOptionsBuilder) SetSkip(i int32) *GridFSFindOptionsBuilder { } // SetSort sets the value for the Sort field. Sets a document specifying the order -// in which documents should be returned. The driver will return an error if the -// sort parameter is a multi-key map. +// in which documents should be returned. The sort parameter is evaluated sequentially, +// so the driver will return an error if it is a multi-key map (which is unordeded). +// The default value is nil. func (f *GridFSFindOptionsBuilder) SetSort(sort interface{}) *GridFSFindOptionsBuilder { f.Opts = append(f.Opts, func(opts *GridFSFindOptions) error { opts.Sort = sort diff --git a/mongo/options/replaceoptions.go b/mongo/options/replaceoptions.go index d50cf04a15..0416d071b2 100644 --- a/mongo/options/replaceoptions.go +++ b/mongo/options/replaceoptions.go @@ -127,8 +127,8 @@ func (ro *ReplaceOptionsBuilder) SetLet(l interface{}) *ReplaceOptionsBuilder { // SetSort sets the value for the Sort field. Specifies 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. +// versions >= 8.0. The sort parameter is evaluated sequentially, so the driver will return an error +// if it is a multi-key map (which is unordeded). The default value is nil. func (ro *ReplaceOptionsBuilder) SetSort(s interface{}) *ReplaceOptionsBuilder { ro.Opts = append(ro.Opts, func(opts *ReplaceOptions) error { opts.Sort = s diff --git a/mongo/options/updateoptions.go b/mongo/options/updateoptions.go index 5f16c10869..bbf721b221 100644 --- a/mongo/options/updateoptions.go +++ b/mongo/options/updateoptions.go @@ -141,8 +141,8 @@ func (uo *UpdateOneOptionsBuilder) SetLet(l interface{}) *UpdateOneOptionsBuilde // SetSort sets the value for the Sort field. Specifies a document specifying which document should // be updated if the filter used by the operation matches multiple documents in the collection. If // set, the first document in the sorted order will be updated. 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. +// versions >= 8.0. The sort parameter is evaluated sequentially, so the driver will return an error +// if it is a multi-key map (which is unordeded). The default value is nil. func (uo *UpdateOneOptionsBuilder) SetSort(s interface{}) *UpdateOneOptionsBuilder { uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error { opts.Sort = s diff --git a/testdata/crud/unified/client-bulkWrite-replaceOne-sort.json b/testdata/crud/unified/client-bulkWrite-replaceOne-sort.json new file mode 100644 index 0000000000..b86bc5f942 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-replaceOne-sort.json @@ -0,0 +1,163 @@ +{ + "description": "client bulkWrite updateOne-sort", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "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 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite replaceOne with sort option", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "ops": [ + { + "update": 0, + "filter": { + "_id": { + "$gt": 1 + } + }, + "updateMods": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "nErrors": 0, + "nMatched": 1, + "nModified": 1 + }, + "commandName": "bulkWrite" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 1 + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-replaceOne-sort.yml b/testdata/crud/unified/client-bulkWrite-replaceOne-sort.yml new file mode 100644 index 0000000000..a159ba5955 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-replaceOne-sort.yml @@ -0,0 +1,77 @@ +description: client bulkWrite updateOne-sort + +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid # Serverless does not support bulkWrite: CLOUDP-256344. + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: client bulkWrite replaceOne with sort option + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - replaceOne: + namespace: *namespace + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + replacement: { x: 1 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + ops: + - update: 0 + filter: { _id: { $gt: 1 } } + updateMods: { x: 1 } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + nsInfo: + - ns: *namespace + - commandSucceededEvent: + reply: + ok: 1 + nErrors: 0 + nMatched: 1 + nModified: 1 + commandName: bulkWrite + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 1 } diff --git a/testdata/crud/unified/client-bulkWrite-updateOne-sort.json b/testdata/crud/unified/client-bulkWrite-updateOne-sort.json new file mode 100644 index 0000000000..ef75dcb374 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-updateOne-sort.json @@ -0,0 +1,167 @@ +{ + "description": "client bulkWrite updateOne-sort", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "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 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite updateOne with sort option", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "ops": [ + { + "update": 0, + "filter": { + "_id": { + "$gt": 1 + } + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "nErrors": 0, + "nMatched": 1, + "nModified": 1 + }, + "commandName": "bulkWrite" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 34 + } + ] + } + ] + } + ] +} diff --git a/testdata/crud/unified/client-bulkWrite-updateOne-sort.yml b/testdata/crud/unified/client-bulkWrite-updateOne-sort.yml new file mode 100644 index 0000000000..73a265d6b8 --- /dev/null +++ b/testdata/crud/unified/client-bulkWrite-updateOne-sort.yml @@ -0,0 +1,77 @@ +description: client bulkWrite updateOne-sort + +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid # Serverless does not support bulkWrite: CLOUDP-256344. + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: client bulkWrite updateOne with sort option + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: { $inc: { x: 1 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + ops: + - update: 0 + filter: { _id: { $gt: 1 } } + updateMods: { $inc: { x: 1 } } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + nsInfo: + - ns: *namespace + - commandSucceededEvent: + reply: + ok: 1 + nErrors: 0 + nMatched: 1 + nModified: 1 + commandName: bulkWrite + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 34 }