From 5a87ba97a17da926ae4a3c45552a4edbf1e48b0c Mon Sep 17 00:00:00 2001 From: Qingyang Hu Date: Mon, 9 Sep 2024 18:39:06 -0400 Subject: [PATCH] GODRIVER-3285 [v2]Allow update to supply sort option. --- internal/integration/collection_test.go | 6 +- internal/integration/crud_helpers_test.go | 8 +- internal/integration/crud_prose_test.go | 2 +- .../integration/unified/bulkwrite_helpers.go | 12 + .../unified/collection_operation_execution.go | 10 +- internal/integration/unified/crud_helpers.go | 58 +++- mongo/bulk_write.go | 122 +++++---- mongo/bulk_write_models.go | 18 ++ mongo/collection.go | 65 +++-- mongo/crud_examples_test.go | 2 +- mongo/options/replaceoptions.go | 17 ++ mongo/options/updateoptions.go | 199 ++++++++++++-- .../unified/bulkWrite-replaceOne-sort.json | 239 ++++++++++++++++ .../unified/bulkWrite-replaceOne-sort.yml | 94 +++++++ .../unified/bulkWrite-updateOne-sort.json | 255 ++++++++++++++++++ .../crud/unified/bulkWrite-updateOne-sort.yml | 94 +++++++ testdata/crud/unified/replaceOne-sort.json | 232 ++++++++++++++++ testdata/crud/unified/replaceOne-sort.yml | 94 +++++++ testdata/crud/unified/updateOne-sort.json | 240 +++++++++++++++++ testdata/crud/unified/updateOne-sort.yml | 96 +++++++ 20 files changed, 1730 insertions(+), 133 deletions(-) create mode 100644 testdata/crud/unified/bulkWrite-replaceOne-sort.json create mode 100644 testdata/crud/unified/bulkWrite-replaceOne-sort.yml create mode 100644 testdata/crud/unified/bulkWrite-updateOne-sort.json create mode 100644 testdata/crud/unified/bulkWrite-updateOne-sort.yml create mode 100644 testdata/crud/unified/replaceOne-sort.json create mode 100644 testdata/crud/unified/replaceOne-sort.yml create mode 100644 testdata/crud/unified/updateOne-sort.json create mode 100644 testdata/crud/unified/updateOne-sort.yml diff --git a/internal/integration/collection_test.go b/internal/integration/collection_test.go index 13d95913d0..be90ce99f6 100644 --- a/internal/integration/collection_test.go +++ b/internal/integration/collection_test.go @@ -451,7 +451,7 @@ func TestCollection(t *testing.T) { filter := bson.D{{"x", 0}} update := bson.D{{"$inc", bson.D{{"x", 1}}}} - res, err := mt.Coll.UpdateOne(context.Background(), filter, update, options.Update().SetUpsert(true)) + res, err := mt.Coll.UpdateOne(context.Background(), filter, update, options.UpdateOne().SetUpsert(true)) assert.Nil(mt, err, "UpdateOne error: %v", err) assert.Equal(mt, int64(0), res.MatchedCount, "expected matched count 0, got %v", res.MatchedCount) assert.Equal(mt, int64(0), res.ModifiedCount, "expected matched count 0, got %v", res.ModifiedCount) @@ -570,7 +570,7 @@ func TestCollection(t *testing.T) { update := bson.D{{"$inc", bson.D{{"x", 1}}}} id := "blah" - res, err := mt.Coll.UpdateByID(context.Background(), id, update, options.Update().SetUpsert(true)) + res, err := mt.Coll.UpdateByID(context.Background(), id, update, options.UpdateOne().SetUpsert(true)) assert.Nil(mt, err, "UpdateByID error: %v", err) assert.Equal(mt, int64(0), res.MatchedCount, "expected matched count 0, got %v", res.MatchedCount) assert.Equal(mt, int64(0), res.ModifiedCount, "expected modified count 0, got %v", res.ModifiedCount) @@ -633,7 +633,7 @@ func TestCollection(t *testing.T) { filter := bson.D{{"x", bson.D{{"$lt", 1}}}} update := bson.D{{"$inc", bson.D{{"x", 1}}}} - res, err := mt.Coll.UpdateMany(context.Background(), filter, update, options.Update().SetUpsert(true)) + res, err := mt.Coll.UpdateMany(context.Background(), filter, update, options.UpdateMany().SetUpsert(true)) assert.Nil(mt, err, "UpdateMany error: %v", err) assert.Equal(mt, int64(0), res.MatchedCount, "expected matched count 0, got %v", res.MatchedCount) assert.Equal(mt, int64(0), res.ModifiedCount, "expected modified count 0, got %v", res.ModifiedCount) diff --git a/internal/integration/crud_helpers_test.go b/internal/integration/crud_helpers_test.go index 595fc134bf..10285c1e2b 100644 --- a/internal/integration/crud_helpers_test.go +++ b/internal/integration/crud_helpers_test.go @@ -874,7 +874,7 @@ func executeUpdateOne(mt *mtest.T, sess *mongo.Session, args bson.Raw) (*mongo.U filter := emptyDoc var update interface{} = emptyDoc - opts := options.Update() + opts := options.UpdateOne() elems, _ := args.Elements() for _, elem := range elems { @@ -902,7 +902,7 @@ func executeUpdateOne(mt *mtest.T, sess *mongo.Session, args bson.Raw) (*mongo.U } } - updateArgs, err := mongoutil.NewOptions[options.UpdateOptions](opts) + updateArgs, err := mongoutil.NewOptions[options.UpdateOneOptions](opts) require.NoError(mt, err, "failed to construct options from builder") if updateArgs.Upsert == nil { @@ -926,7 +926,7 @@ func executeUpdateMany(mt *mtest.T, sess *mongo.Session, args bson.Raw) (*mongo. filter := emptyDoc var update interface{} = emptyDoc - opts := options.Update() + opts := options.UpdateMany() elems, _ := args.Elements() for _, elem := range elems { @@ -954,7 +954,7 @@ func executeUpdateMany(mt *mtest.T, sess *mongo.Session, args bson.Raw) (*mongo. } } - updateArgs, err := mongoutil.NewOptions[options.UpdateOptions](opts) + updateArgs, err := mongoutil.NewOptions[options.UpdateManyOptions](opts) require.NoError(mt, err, "failed to construct options from builder") if updateArgs.Upsert == nil { diff --git a/internal/integration/crud_prose_test.go b/internal/integration/crud_prose_test.go index 8233763807..eda9a382ef 100644 --- a/internal/integration/crud_prose_test.go +++ b/internal/integration/crud_prose_test.go @@ -296,7 +296,7 @@ func TestHintErrors(t *testing.T) { mt.Run("UpdateMany", func(mt *mtest.T) { _, got := mt.Coll.UpdateMany(context.Background(), bson.D{{"a", 1}}, bson.D{{"$inc", bson.D{{"a", 1}}}}, - options.Update().SetHint("_id_")) + options.UpdateMany().SetHint("_id_")) assert.NotNil(mt, got, "expected non-nil error, got nil") assert.Equal(mt, got, expected, "expected: %v got: %v", expected, got) }) diff --git a/internal/integration/unified/bulkwrite_helpers.go b/internal/integration/unified/bulkwrite_helpers.go index 11db1eb0cf..69314e665a 100644 --- a/internal/integration/unified/bulkwrite_helpers.go +++ b/internal/integration/unified/bulkwrite_helpers.go @@ -102,6 +102,12 @@ func createBulkWriteModel(rawModel bson.Raw) (mongo.WriteModel, error) { if err != nil { return nil, fmt.Errorf("error creating update: %w", err) } + case "sort": + sort, err := createSort(val) + if err != nil { + return nil, fmt.Errorf("error creating sort: %w", err) + } + uom.SetSort(sort) case "upsert": uom.SetUpsert(val.Boolean()) default: @@ -249,6 +255,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/internal/integration/unified/collection_operation_execution.go b/internal/integration/unified/collection_operation_execution.go index 80b73f2ebc..cb99282b8e 100644 --- a/internal/integration/unified/collection_operation_execution.go +++ b/internal/integration/unified/collection_operation_execution.go @@ -1291,6 +1291,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": @@ -1316,7 +1322,7 @@ func executeUpdateOne(ctx context.Context, operation *operation) (*operationResu return nil, err } - updateArgs, err := createUpdateArguments(operation.Arguments) + updateArgs, err := createUpdateArguments[options.UpdateOneOptions](operation.Arguments) if err != nil { return nil, err } @@ -1335,7 +1341,7 @@ func executeUpdateMany(ctx context.Context, operation *operation) (*operationRes return nil, err } - updateArgs, err := createUpdateArguments(operation.Arguments) + updateArgs, err := createUpdateArguments[options.UpdateManyOptions](operation.Arguments) if err != nil { return nil, err } diff --git a/internal/integration/unified/crud_helpers.go b/internal/integration/unified/crud_helpers.go index c640cf28c8..6c562e90b5 100644 --- a/internal/integration/unified/crud_helpers.go +++ b/internal/integration/unified/crud_helpers.go @@ -8,6 +8,8 @@ package unified import ( "fmt" + "reflect" + "strings" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/internal/bsonutil" @@ -20,38 +22,43 @@ func newMissingArgumentError(arg string) error { return fmt.Errorf("operation arguments document is missing required field %q", arg) } -type updateArguments struct { +type updateArguments[Options options.UpdateManyOptions | options.UpdateOneOptions] struct { filter bson.Raw update interface{} - opts *options.UpdateOptionsBuilder + opts options.Lister[Options] } -func createUpdateArguments(args bson.Raw) (*updateArguments, error) { - ua := &updateArguments{ - opts: options.Update(), +func createUpdateArguments[Options options.UpdateManyOptions | options.UpdateOneOptions](args bson.Raw) (*updateArguments[Options], error) { + ua := &updateArguments[Options]{} + var builder reflect.Value + switch any((*Options)(nil)).(type) { + case *options.UpdateManyOptions: + builder = reflect.ValueOf(options.UpdateMany()) + case *options.UpdateOneOptions: + builder = reflect.ValueOf(options.UpdateOne()) } - var err error elems, _ := args.Elements() for _, elem := range elems { key := elem.Key() val := elem.Value() + var arg reflect.Value switch key { case "arrayFilters": - ua.opts.SetArrayFilters( + arg = reflect.ValueOf( bsonutil.RawToInterfaces(bsonutil.RawArrayToDocuments(val.Array())...), ) case "bypassDocumentValidation": - ua.opts.SetBypassDocumentValidation(val.Boolean()) + arg = reflect.ValueOf(val.Boolean()) case "collation": collation, err := createCollation(val.Document()) if err != nil { return nil, fmt.Errorf("error creating collation: %w", err) } - ua.opts.SetCollation(collation) + arg = reflect.ValueOf(collation) case "comment": - ua.opts.SetComment(val) + arg = reflect.ValueOf(val) case "filter": ua.filter = val.Document() case "hint": @@ -59,19 +66,29 @@ func createUpdateArguments(args bson.Raw) (*updateArguments, error) { if err != nil { return nil, fmt.Errorf("error creating hint: %w", err) } - ua.opts.SetHint(hint) - case "let": - ua.opts.SetLet(val.Document()) + arg = reflect.ValueOf(hint) + case "let", "sort": + arg = reflect.ValueOf(val.Document()) case "update": + var err error ua.update, err = createUpdateValue(val) if err != nil { return nil, fmt.Errorf("error processing update value: %w", err) } case "upsert": - ua.opts.SetUpsert(val.Boolean()) + arg = reflect.ValueOf(val.Boolean()) default: return nil, fmt.Errorf("unrecognized update option %q", key) } + if arg.IsValid() { + fn := builder.MethodByName( + fmt.Sprintf("Set%s%s", strings.ToUpper(string(key[0])), key[1:]), + ) + if !fn.IsValid() { + return nil, fmt.Errorf("unrecognized update option %q", key) + } + fn.Call([]reflect.Value{arg}) + } } if ua.filter == nil { return nil, newMissingArgumentError("filter") @@ -79,6 +96,7 @@ func createUpdateArguments(args bson.Raw) (*updateArguments, error) { if ua.update == nil { return nil, newMissingArgumentError("update") } + ua.opts = builder.Interface().(options.Lister[Options]) return ua, nil } @@ -158,3 +176,15 @@ func createHint(val bson.RawValue) (interface{}, error) { } return hint, nil } + +func createSort(val bson.RawValue) (interface{}, error) { + var sort interface{} + + switch val.Type { + case bson.TypeEmbeddedDocument: + sort = val.Document() + default: + return nil, fmt.Errorf("unrecognized sort value type %s", val.Type) + } + return sort, nil +} diff --git a/mongo/bulk_write.go b/mongo/bulk_write.go index 26ad74f016..f089205e13 100644 --- a/mongo/bulk_write.go +++ b/mongo/bulk_write.go @@ -336,44 +336,39 @@ func (bw *bulkWrite) runUpdate(ctx context.Context, batch bulkWriteBatch) (opera switch converted := model.(type) { case *ReplaceOneModel: - doc, err = createUpdateDoc( - converted.Filter, - converted.Replacement, - converted.Hint, - nil, - converted.Collation, - converted.Upsert, - false, - false, - bw.collection.bsonOpts, - bw.collection.registry) + doc, err = updateDoc{ + 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) hasHint = hasHint || (converted.Hint != nil) case *UpdateOneModel: - doc, err = createUpdateDoc( - converted.Filter, - converted.Update, - converted.Hint, - converted.ArrayFilters, - converted.Collation, - converted.Upsert, - false, - true, - bw.collection.bsonOpts, - bw.collection.registry) + doc, err = updateDoc{ + filter: converted.Filter, + update: converted.Update, + hint: converted.Hint, + sort: converted.Sort, + arrayFilters: converted.ArrayFilters, + collation: converted.Collation, + upsert: converted.Upsert, + checkDollarKey: true, + }.marshal(bw.collection.bsonOpts, bw.collection.registry) hasHint = hasHint || (converted.Hint != nil) hasArrayFilters = hasArrayFilters || (converted.ArrayFilters != nil) case *UpdateManyModel: - doc, err = createUpdateDoc( - converted.Filter, - converted.Update, - converted.Hint, - converted.ArrayFilters, - converted.Collation, - converted.Upsert, - true, - true, - bw.collection.bsonOpts, - bw.collection.registry) + doc, err = updateDoc{ + filter: converted.Filter, + update: converted.Update, + hint: converted.Hint, + arrayFilters: converted.ArrayFilters, + collation: converted.Collation, + upsert: converted.Upsert, + multi: true, + checkDollarKey: true, + }.marshal(bw.collection.bsonOpts, bw.collection.registry) hasHint = hasHint || (converted.Hint != nil) hasArrayFilters = hasArrayFilters || (converted.ArrayFilters != nil) } @@ -423,19 +418,20 @@ func (bw *bulkWrite) runUpdate(ctx context.Context, batch bulkWriteBatch) (opera return op.Result(), err } -func createUpdateDoc( - filter interface{}, - update interface{}, - hint interface{}, - arrayFilters []interface{}, - collation *options.Collation, - upsert *bool, - multi bool, - checkDollarKey bool, - bsonOpts *options.BSONOptions, - registry *bson.Registry, -) (bsoncore.Document, error) { - f, err := marshal(filter, bsonOpts, registry) +type updateDoc struct { + filter interface{} + update interface{} + hint interface{} + sort interface{} + arrayFilters []interface{} + collation *options.Collation + upsert *bool + multi bool + checkDollarKey bool +} + +func (doc updateDoc) marshal(bsonOpts *options.BSONOptions, registry *bson.Registry) (bsoncore.Document, error) { + f, err := marshal(doc.filter, bsonOpts, registry) if err != nil { return nil, err } @@ -443,39 +439,49 @@ func createUpdateDoc( uidx, updateDoc := bsoncore.AppendDocumentStart(nil) updateDoc = bsoncore.AppendDocumentElement(updateDoc, "q", f) - u, err := marshalUpdateValue(update, bsonOpts, registry, checkDollarKey) + u, err := marshalUpdateValue(doc.update, bsonOpts, registry, doc.checkDollarKey) if err != nil { return nil, err } updateDoc = bsoncore.AppendValueElement(updateDoc, "u", u) - if multi { - updateDoc = bsoncore.AppendBooleanElement(updateDoc, "multi", multi) + if doc.multi { + updateDoc = bsoncore.AppendBooleanElement(updateDoc, "multi", doc.multi) + } + if doc.sort != nil { + if isUnorderedMap(doc.sort) { + return nil, ErrMapForOrderedArgument{"sort"} + } + s, err := marshal(doc.sort, bsonOpts, registry) + if err != nil { + return nil, err + } + updateDoc = bsoncore.AppendDocumentElement(updateDoc, "sort", s) } - if arrayFilters != nil { + if doc.arrayFilters != nil { reg := registry - arr, err := marshalValue(arrayFilters, bsonOpts, reg) + arr, err := marshalValue(doc.arrayFilters, bsonOpts, reg) if err != nil { return nil, err } updateDoc = bsoncore.AppendArrayElement(updateDoc, "arrayFilters", arr.Data) } - if collation != nil { - updateDoc = bsoncore.AppendDocumentElement(updateDoc, "collation", bsoncore.Document(toDocument(collation))) + if doc.collation != nil { + updateDoc = bsoncore.AppendDocumentElement(updateDoc, "collation", bsoncore.Document(toDocument(doc.collation))) } - if upsert != nil { - updateDoc = bsoncore.AppendBooleanElement(updateDoc, "upsert", *upsert) + if doc.upsert != nil { + updateDoc = bsoncore.AppendBooleanElement(updateDoc, "upsert", *doc.upsert) } - if hint != nil { - if isUnorderedMap(hint) { + if doc.hint != nil { + if isUnorderedMap(doc.hint) { return nil, ErrMapForOrderedArgument{"hint"} } - hintVal, err := marshalValue(hint, bsonOpts, registry) + hintVal, err := marshalValue(doc.hint, bsonOpts, registry) if err != nil { return nil, err } diff --git a/mongo/bulk_write_models.go b/mongo/bulk_write_models.go index 84902a0299..50e4a3ced6 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. @@ -183,6 +192,7 @@ type UpdateOneModel struct { Update interface{} ArrayFilters []interface{} Hint interface{} + Sort interface{} } // NewUpdateOneModel creates a new UpdateOneModel. @@ -238,6 +248,14 @@ func (uom *UpdateOneModel) SetUpsert(upsert bool) *UpdateOneModel { 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 driver will +// return an error if the sort parameter is a multi-key map. The default value is nil. +func (uom *UpdateOneModel) SetSort(sort interface{}) *UpdateOneModel { + uom.Sort = sort + return uom +} + func (*UpdateOneModel) writeModel() {} // UpdateManyModel is used to update multiple documents in a BulkWrite operation. diff --git a/mongo/collection.go b/mongo/collection.go index 5ba3113c0c..1d952a7dd3 100644 --- a/mongo/collection.go +++ b/mongo/collection.go @@ -599,31 +599,27 @@ func (coll *Collection) updateOrReplace( multi bool, expectedRr returnResult, checkDollarKey bool, - opts ...options.Lister[options.UpdateOptions], + sort interface{}, + args *options.UpdateManyOptions, ) (*UpdateResult, error) { if ctx == nil { ctx = context.Background() } - args, err := mongoutil.NewOptions[options.UpdateOptions](opts...) - if err != nil { - return nil, fmt.Errorf("failed to construct options from builder: %w", err) - } - // collation, arrayFilters, upsert, and hint are included on the individual update documents rather than as part of the // command - updateDoc, err := createUpdateDoc( - filter, - update, - args.Hint, - args.ArrayFilters, - args.Collation, - args.Upsert, - multi, - checkDollarKey, - coll.bsonOpts, - coll.registry) + updateDoc, err := updateDoc{ + filter: filter, + update: update, + hint: args.Hint, + sort: sort, + arrayFilters: args.ArrayFilters, + collation: args.Collation, + upsert: args.Upsert, + multi: multi, + checkDollarKey: checkDollarKey, + }.marshal(coll.bsonOpts, coll.registry) if err != nil { return nil, err } @@ -719,7 +715,7 @@ func (coll *Collection) UpdateByID( ctx context.Context, id interface{}, update interface{}, - opts ...options.Lister[options.UpdateOptions], + opts ...options.Lister[options.UpdateOneOptions], ) (*UpdateResult, error) { if id == nil { return nil, ErrNilValue @@ -745,7 +741,7 @@ func (coll *Collection) UpdateOne( ctx context.Context, filter interface{}, update interface{}, - opts ...options.Lister[options.UpdateOptions], + opts ...options.Lister[options.UpdateOneOptions], ) (*UpdateResult, error) { if ctx == nil { ctx = context.Background() @@ -756,7 +752,21 @@ func (coll *Collection) UpdateOne( return nil, err } - return coll.updateOrReplace(ctx, f, update, false, rrOne, true, opts...) + args, err := mongoutil.NewOptions[options.UpdateOneOptions](opts...) + if err != nil { + return nil, fmt.Errorf("failed to construct options from builder: %w", err) + } + updateOptions := options.UpdateManyOptions{ + ArrayFilters: args.ArrayFilters, + BypassDocumentValidation: args.BypassDocumentValidation, + Collation: args.Collation, + Comment: args.Comment, + Hint: args.Hint, + Upsert: args.Upsert, + Let: args.Let, + } + + return coll.updateOrReplace(ctx, f, update, false, rrOne, true, args.Sort, &updateOptions) } // UpdateMany executes an update command to update documents in the collection. @@ -776,7 +786,7 @@ func (coll *Collection) UpdateMany( ctx context.Context, filter interface{}, update interface{}, - opts ...options.Lister[options.UpdateOptions], + opts ...options.Lister[options.UpdateManyOptions], ) (*UpdateResult, error) { if ctx == nil { ctx = context.Background() @@ -787,7 +797,12 @@ func (coll *Collection) UpdateMany( return nil, err } - return coll.updateOrReplace(ctx, f, update, true, rrMany, true, opts...) + args, err := mongoutil.NewOptions[options.UpdateManyOptions](opts...) + if err != nil { + return nil, fmt.Errorf("failed to construct options from builder: %w", err) + } + + return coll.updateOrReplace(ctx, f, update, true, rrMany, true, nil, args) } // ReplaceOne executes an update command to replace at most one document in the collection. @@ -832,7 +847,7 @@ func (coll *Collection) ReplaceOne( return nil, err } - updateArgs := &options.UpdateOptions{ + updateOptions := &options.UpdateManyOptions{ BypassDocumentValidation: args.BypassDocumentValidation, Collation: args.Collation, Upsert: args.Upsert, @@ -841,9 +856,7 @@ func (coll *Collection) ReplaceOne( Comment: args.Comment, } - updateOptions := mongoutil.NewOptionsLister(updateArgs, nil) - - return coll.updateOrReplace(ctx, f, r, false, rrOne, false, updateOptions) + return coll.updateOrReplace(ctx, f, r, false, rrOne, false, args.Sort, updateOptions) } // Aggregate executes an aggregate command against the collection and returns a cursor over the resulting documents. diff --git a/mongo/crud_examples_test.go b/mongo/crud_examples_test.go index 159b5cb761..49b9174bf2 100644 --- a/mongo/crud_examples_test.go +++ b/mongo/crud_examples_test.go @@ -568,7 +568,7 @@ func ExampleCollection_UpdateOne() { // "newemail@example.com". // Specify the Upsert option to insert a new document if a document matching // the filter isn't found. - opts := options.Update().SetUpsert(true) + opts := options.UpdateOne().SetUpsert(true) filter := bson.D{{"_id", id}} update := bson.D{{"$set", bson.D{{"email", "newemail@example.com"}}}} diff --git a/mongo/options/replaceoptions.go b/mongo/options/replaceoptions.go index 39322b92a8..db675004fb 100644 --- a/mongo/options/replaceoptions.go +++ b/mongo/options/replaceoptions.go @@ -41,6 +41,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{} } // ReplaceOptionsBuilder contains options to configure replace operations. Each @@ -125,3 +131,14 @@ func (ro *ReplaceOptionsBuilder) SetLet(l interface{}) *ReplaceOptionsBuilder { return ro } + +// SetSort sets the value for the Sort field. +func (ro *ReplaceOptionsBuilder) SetSort(s interface{}) *ReplaceOptionsBuilder { + ro.Opts = append(ro.Opts, func(opts *ReplaceOptions) error { + opts.Sort = s + + return nil + }) + + return ro +} diff --git a/mongo/options/updateoptions.go b/mongo/options/updateoptions.go index 34ff1ba3f8..fb8fa9122f 100644 --- a/mongo/options/updateoptions.go +++ b/mongo/options/updateoptions.go @@ -6,9 +6,8 @@ package options -// UpdateOptions represents arguments that can be used to configure UpdateOne and -// UpdateMany operations. -type UpdateOptions struct { +// UpdateManyOptions represents arguments that can be used to configure UpdateMany operations. +type UpdateManyOptions struct { // A set of filters specifying to which array elements an update should apply. This option is only valid for MongoDB // versions >= 3.6. For previous server versions, the driver will return an error if this option is used. The // default value is nil, which means the update will apply to all array elements. @@ -48,26 +47,167 @@ type UpdateOptions struct { Let interface{} } -// UpdateOptionsBuilder contains options to configure update operations. Each +// UpdateManyOptionsBuilder contains options to configure UpdateMany operations. Each // option can be set through setter functions. See documentation for each setter // function for an explanation of the option. -type UpdateOptionsBuilder struct { - Opts []func(*UpdateOptions) error +type UpdateManyOptionsBuilder struct { + Opts []func(*UpdateManyOptions) error } -// Update creates a new UpdateOptions instance. -func Update() *UpdateOptionsBuilder { - return &UpdateOptionsBuilder{} +// UpdateMany creates a new UpdateOptions instance. +func UpdateMany() *UpdateManyOptionsBuilder { + return &UpdateManyOptionsBuilder{} +} + +// List returns a list of UpdateManyOptions setter functions. +func (uo *UpdateManyOptionsBuilder) List() []func(*UpdateManyOptions) error { + return uo.Opts +} + +// SetArrayFilters sets the value for the ArrayFilters field. +func (uo *UpdateManyOptionsBuilder) SetArrayFilters(af []interface{}) *UpdateManyOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error { + opts.ArrayFilters = af + + return nil + }) + + return uo +} + +// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. +func (uo *UpdateManyOptionsBuilder) SetBypassDocumentValidation(b bool) *UpdateManyOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error { + opts.BypassDocumentValidation = &b + + return nil + }) + + return uo +} + +// SetCollation sets the value for the Collation field. +func (uo *UpdateManyOptionsBuilder) SetCollation(c *Collation) *UpdateManyOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error { + opts.Collation = c + + return nil + }) + + return uo +} + +// SetComment sets the value for the Comment field. +func (uo *UpdateManyOptionsBuilder) SetComment(comment interface{}) *UpdateManyOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error { + opts.Comment = comment + + return nil + }) + + return uo +} + +// SetHint sets the value for the Hint field. +func (uo *UpdateManyOptionsBuilder) SetHint(h interface{}) *UpdateManyOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error { + opts.Hint = h + + return nil + }) + + return uo +} + +// SetUpsert sets the value for the Upsert field. +func (uo *UpdateManyOptionsBuilder) SetUpsert(b bool) *UpdateManyOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error { + opts.Upsert = &b + + return nil + }) + + return uo +} + +// SetLet sets the value for the Let field. +func (uo *UpdateManyOptionsBuilder) SetLet(l interface{}) *UpdateManyOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error { + opts.Let = l + + return nil + }) + + return uo +} + +// UpdateOneOptions represents arguments that can be used to configure UpdateOne operations. +type UpdateOneOptions struct { + // A set of filters specifying to which array elements an update should apply. This option is only valid for MongoDB + // versions >= 3.6. For previous server versions, the driver will return an error if this option is used. The + // default value is nil, which means the update will apply to all array elements. + ArrayFilters []interface{} + + // If true, writes executed as part of the operation will opt out of document-level validation on the server. This + // option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is + // false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document + // validation. + BypassDocumentValidation *bool + + // Specifies a collation to use for string comparisons during the operation. This option is only valid for MongoDB + // versions >= 3.4. For previous server versions, the driver will return an error if this option is used. The + // default value is nil, which means the default collation of the collection will be used. + Collation *Collation + + // A string or document that will be included in server logs, profiling logs, and currentOp queries to help trace + // the operation. The default value is nil, which means that no comment will be included in the logs. + Comment interface{} + + // The index to use for the operation. This should either be the index name as a string or the index specification + // as a document. This option is only valid for MongoDB versions >= 4.2. Server versions >= 3.4 will return an error + // if this option is specified. For server versions < 3.4, the driver will return a client-side error if this option + // is specified. The driver will return an error if this option is specified during an unacknowledged write + // operation. The driver will return an error if the hint parameter is a multi-key map. The default value is nil, + // which means that no hint will be sent. + Hint interface{} + + // If true, a new document will be inserted if the filter does not match any documents in the collection. The + // default value is false. + Upsert *bool + + // Specifies parameters for the update expression. This option is only valid for MongoDB versions >= 5.0. Older + // servers will report an error for using this option. This must be a document mapping parameter names to values. + // 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 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. + Sort interface{} +} + +// UpdateOneOptionsBuilder contains options to configure UpdateOne operations. Each +// option can be set through setter functions. See documentation for each setter +// function for an explanation of the option. +type UpdateOneOptionsBuilder struct { + Opts []func(*UpdateOneOptions) error +} + +// UpdateOne creates a new UpdateOptions instance. +func UpdateOne() *UpdateOneOptionsBuilder { + return &UpdateOneOptionsBuilder{} } // List returns a list of UpdateOptions setter functions. -func (uo *UpdateOptionsBuilder) List() []func(*UpdateOptions) error { +func (uo *UpdateOneOptionsBuilder) List() []func(*UpdateOneOptions) error { return uo.Opts } // SetArrayFilters sets the value for the ArrayFilters field. -func (uo *UpdateOptionsBuilder) SetArrayFilters(af []interface{}) *UpdateOptionsBuilder { - uo.Opts = append(uo.Opts, func(opts *UpdateOptions) error { +func (uo *UpdateOneOptionsBuilder) SetArrayFilters(af []interface{}) *UpdateOneOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error { opts.ArrayFilters = af return nil @@ -77,8 +217,8 @@ func (uo *UpdateOptionsBuilder) SetArrayFilters(af []interface{}) *UpdateOptions } // SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. -func (uo *UpdateOptionsBuilder) SetBypassDocumentValidation(b bool) *UpdateOptionsBuilder { - uo.Opts = append(uo.Opts, func(opts *UpdateOptions) error { +func (uo *UpdateOneOptionsBuilder) SetBypassDocumentValidation(b bool) *UpdateOneOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error { opts.BypassDocumentValidation = &b return nil @@ -88,8 +228,8 @@ func (uo *UpdateOptionsBuilder) SetBypassDocumentValidation(b bool) *UpdateOptio } // SetCollation sets the value for the Collation field. -func (uo *UpdateOptionsBuilder) SetCollation(c *Collation) *UpdateOptionsBuilder { - uo.Opts = append(uo.Opts, func(opts *UpdateOptions) error { +func (uo *UpdateOneOptionsBuilder) SetCollation(c *Collation) *UpdateOneOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error { opts.Collation = c return nil @@ -99,8 +239,8 @@ func (uo *UpdateOptionsBuilder) SetCollation(c *Collation) *UpdateOptionsBuilder } // SetComment sets the value for the Comment field. -func (uo *UpdateOptionsBuilder) SetComment(comment interface{}) *UpdateOptionsBuilder { - uo.Opts = append(uo.Opts, func(opts *UpdateOptions) error { +func (uo *UpdateOneOptionsBuilder) SetComment(comment interface{}) *UpdateOneOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error { opts.Comment = comment return nil @@ -110,8 +250,8 @@ func (uo *UpdateOptionsBuilder) SetComment(comment interface{}) *UpdateOptionsBu } // SetHint sets the value for the Hint field. -func (uo *UpdateOptionsBuilder) SetHint(h interface{}) *UpdateOptionsBuilder { - uo.Opts = append(uo.Opts, func(opts *UpdateOptions) error { +func (uo *UpdateOneOptionsBuilder) SetHint(h interface{}) *UpdateOneOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error { opts.Hint = h return nil @@ -121,8 +261,8 @@ func (uo *UpdateOptionsBuilder) SetHint(h interface{}) *UpdateOptionsBuilder { } // SetUpsert sets the value for the Upsert field. -func (uo *UpdateOptionsBuilder) SetUpsert(b bool) *UpdateOptionsBuilder { - uo.Opts = append(uo.Opts, func(opts *UpdateOptions) error { +func (uo *UpdateOneOptionsBuilder) SetUpsert(b bool) *UpdateOneOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error { opts.Upsert = &b return nil @@ -132,8 +272,8 @@ func (uo *UpdateOptionsBuilder) SetUpsert(b bool) *UpdateOptionsBuilder { } // SetLet sets the value for the Let field. -func (uo *UpdateOptionsBuilder) SetLet(l interface{}) *UpdateOptionsBuilder { - uo.Opts = append(uo.Opts, func(opts *UpdateOptions) error { +func (uo *UpdateOneOptionsBuilder) SetLet(l interface{}) *UpdateOneOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error { opts.Let = l return nil @@ -141,3 +281,14 @@ func (uo *UpdateOptionsBuilder) SetLet(l interface{}) *UpdateOptionsBuilder { return uo } + +// SetSort sets the value for the Sort field. +func (uo *UpdateOneOptionsBuilder) SetSort(s interface{}) *UpdateOneOptionsBuilder { + uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error { + opts.Sort = s + + return nil + }) + + return uo +} 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-replaceOne-sort.yml b/testdata/crud/unified/bulkWrite-replaceOne-sort.yml new file mode 100644 index 0000000000..9594166f5b --- /dev/null +++ b/testdata/crud/unified/bulkWrite-replaceOne-sort.yml @@ -0,0 +1,94 @@ +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 new file mode 100644 index 0000000000..f78bd3bf3e --- /dev/null +++ b/testdata/crud/unified/bulkWrite-updateOne-sort.json @@ -0,0 +1,255 @@ +{ + "description": "BulkWrite updateOne-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 updateOne with sort option", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateOne": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": [ + { + "$set": { + "x": 1 + } + } + ] + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": [ + { + "$set": { + "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 updateOne with sort option unsupported (server-side error)", + "runOnRequirements": [ + { + "maxServerVersion": "7.99" + } + ], + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateOne": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": [ + { + "$set": { + "x": 1 + } + } + ] + } + } + ] + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": [ + { + "$set": { + "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.yml b/testdata/crud/unified/bulkWrite-updateOne-sort.yml new file mode 100644 index 0000000000..9446986fab --- /dev/null +++ b/testdata/crud/unified/bulkWrite-updateOne-sort.yml @@ -0,0 +1,94 @@ +description: BulkWrite updateOne-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 updateOne with sort option + runOnRequirements: + - minServerVersion: "8.0" + operations: + - object: collection0 + name: bulkWrite + arguments: + requests: + - updateOne: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: [ $set: { x: 1 } ] + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + update: coll0 + updates: + - q: { _id: { $gt: 1 } } + u: [ $set: { 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 updateOne with sort option unsupported (server-side error) + runOnRequirements: + - maxServerVersion: "7.99" + operations: + - object: collection0 + name: bulkWrite + arguments: + requests: + - updateOne: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: [ $set: { x: 1 } ] + expectError: + isClientError: false + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + update: coll0 + updates: + - q: { _id: { $gt: 1 } } + u: [ $set: { 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/replaceOne-sort.json b/testdata/crud/unified/replaceOne-sort.json new file mode 100644 index 0000000000..cf2271dda5 --- /dev/null +++ b/testdata/crud/unified/replaceOne-sort.json @@ -0,0 +1,232 @@ +{ + "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": [ + { + "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/replaceOne-sort.yml b/testdata/crud/unified/replaceOne-sort.yml new file mode 100644 index 0000000000..3395b795e8 --- /dev/null +++ b/testdata/crud/unified/replaceOne-sort.yml @@ -0,0 +1,94 @@ +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: + - 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/updateOne-sort.json b/testdata/crud/unified/updateOne-sort.json new file mode 100644 index 0000000000..8fe4f50b94 --- /dev/null +++ b/testdata/crud/unified/updateOne-sort.json @@ -0,0 +1,240 @@ +{ + "description": "updateOne-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": "UpdateOne with sort option", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "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": 34 + } + ] + } + ] + }, + { + "description": "updateOne with sort option unsupported (server-side error)", + "runOnRequirements": [ + { + "maxServerVersion": "7.99" + } + ], + "operations": [ + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "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/updateOne-sort.yml b/testdata/crud/unified/updateOne-sort.yml new file mode 100644 index 0000000000..a278575fa5 --- /dev/null +++ b/testdata/crud/unified/updateOne-sort.yml @@ -0,0 +1,96 @@ +description: updateOne-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: UpdateOne with sort option + runOnRequirements: + - minServerVersion: "8.0" + operations: + - name: updateOne + object: collection0 + arguments: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + update: coll0 + updates: + - q: { _id: { $gt: 1 } } + u: { $inc: { 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: 34 } + + - description: updateOne with sort option unsupported (server-side error) + runOnRequirements: + - maxServerVersion: "7.99" + operations: + - name: updateOne + object: collection0 + arguments: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: { $inc: { x: 1 } } + expectError: + isClientError: false + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + update: coll0 + updates: + - q: { _id: { $gt: 1 } } + u: { $inc: { 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 }