diff --git a/docs/migration-2.0.md b/docs/migration-2.0.md index a25e081bb9..3b0ad3b7fa 100644 --- a/docs/migration-2.0.md +++ b/docs/migration-2.0.md @@ -627,6 +627,10 @@ for _, set := range options.Client().ApplyURI(uri).Opts { return clientOptionAdder{option: &opts} ``` +### DeleteManyOptions / DeleteOneOptions + +The `DeleteOptions` has been separated into `DeleteManyOptions` and `DeleteOneOptions` to configure the corresponding `DeleteMany` and `DeleteOne` operations. + ### FindOneOptions The following types are not valid for a `findOne` operation and have been removed: @@ -636,6 +640,10 @@ The following types are not valid for a `findOne` operation and have been remove - `MaxAwaitTime` - `NoCursorTimeout` +### UpdateManyOptions / UpdateOneOptions + +The `UpdateOptions` has been separated into `UpdateManyOptions` and `UpdateOneOptions` to configure the corresponding `UpdateMany` and `UpdateOne` operations. + ### Merge\*Options All functions that merge options have been removed in favor of a generic solution. See [GODRIVER-2696](https://jira.mongodb.org/browse/GODRIVER-2696) for more information. diff --git a/internal/integration/collection_test.go b/internal/integration/collection_test.go index 13d95913d0..e65ff45235 100644 --- a/internal/integration/collection_test.go +++ b/internal/integration/collection_test.go @@ -296,7 +296,7 @@ func TestCollection(t *testing.T) { }) mt.RunOpts("not found with options", mtest.NewOptions().MinServerVersion("3.4"), func(mt *mtest.T) { initCollection(mt, mt.Coll) - opts := options.Delete().SetCollation(&options.Collation{Locale: "en_US"}) + opts := options.DeleteOne().SetCollation(&options.Collation{Locale: "en_US"}) res, err := mt.Coll.DeleteOne(context.Background(), bson.D{{"x", 0}}, opts) assert.Nil(mt, err, "DeleteOne error: %v", err) assert.Equal(mt, int64(0), res.DeletedCount, "expected DeletedCount 0, got %v", res.DeletedCount) @@ -339,13 +339,13 @@ func TestCollection(t *testing.T) { }) assert.Nil(mt, err, "CreateOne error: %v", err) - opts := options.Delete().SetHint(bson.M{"x": 1}) + opts := options.DeleteOne().SetHint(bson.M{"x": 1}) res, err := mt.Coll.DeleteOne(context.Background(), bson.D{{"x", 1}}, opts) assert.Nil(mt, err, "DeleteOne error: %v", err) assert.Equal(mt, int64(1), res.DeletedCount, "expected DeletedCount 1, got %v", res.DeletedCount) }) mt.RunOpts("multikey map index", mtest.NewOptions().MinServerVersion("4.4"), func(mt *mtest.T) { - opts := options.Delete().SetHint(bson.M{"x": 1, "y": 1}) + opts := options.DeleteOne().SetHint(bson.M{"x": 1, "y": 1}) _, err := mt.Coll.DeleteOne(context.Background(), bson.D{{"x", 0}}, opts) assert.Equal(mt, mongo.ErrMapForOrderedArgument{"hint"}, err, "expected error %v, got %v", mongo.ErrMapForOrderedArgument{"hint"}, err) }) @@ -365,7 +365,7 @@ func TestCollection(t *testing.T) { }) mt.RunOpts("not found with options", mtest.NewOptions().MinServerVersion("3.4"), func(mt *mtest.T) { initCollection(mt, mt.Coll) - opts := options.Delete().SetCollation(&options.Collation{Locale: "en_US"}) + opts := options.DeleteMany().SetCollation(&options.Collation{Locale: "en_US"}) res, err := mt.Coll.DeleteMany(context.Background(), bson.D{{"x", bson.D{{"$lt", 1}}}}, opts) assert.Nil(mt, err, "DeleteMany error: %v", err) assert.Equal(mt, int64(0), res.DeletedCount, "expected DeletedCount 0, got %v", res.DeletedCount) @@ -408,13 +408,13 @@ func TestCollection(t *testing.T) { }) assert.Nil(mt, err, "index CreateOne error: %v", err) - opts := options.Delete().SetHint(bson.M{"x": 1}) + opts := options.DeleteOne().SetHint(bson.M{"x": 1}) res, err := mt.Coll.DeleteOne(context.Background(), bson.D{{"x", 1}}, opts) assert.Nil(mt, err, "DeleteOne error: %v", err) assert.Equal(mt, int64(1), res.DeletedCount, "expected DeletedCount 1, got %v", res.DeletedCount) }) mt.RunOpts("multikey map index", mtest.NewOptions().MinServerVersion("4.4"), func(mt *mtest.T) { - opts := options.Delete().SetHint(bson.M{"x": 1, "y": 1}) + opts := options.DeleteMany().SetHint(bson.M{"x": 1, "y": 1}) _, err := mt.Coll.DeleteMany(context.Background(), bson.D{{"x", 0}}, opts) assert.Equal(mt, mongo.ErrMapForOrderedArgument{"hint"}, err, "expected error %v, got %v", mongo.ErrMapForOrderedArgument{"hint"}, err) }) @@ -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..0a122fbd73 100644 --- a/internal/integration/crud_helpers_test.go +++ b/internal/integration/crud_helpers_test.go @@ -801,7 +801,7 @@ func executeDeleteOne(mt *mtest.T, sess *mongo.Session, args bson.Raw) (*mongo.D mt.Helper() filter := emptyDoc - opts := options.Delete() + opts := options.DeleteOne() elems, _ := args.Elements() for _, elem := range elems { @@ -837,7 +837,7 @@ func executeDeleteMany(mt *mtest.T, sess *mongo.Session, args bson.Raw) (*mongo. mt.Helper() filter := emptyDoc - opts := options.Delete() + opts := options.DeleteMany() elems, _ := args.Elements() for _, elem := range elems { @@ -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/collection_operation_execution.go b/internal/integration/unified/collection_operation_execution.go index 80b73f2ebc..2060db01e3 100644 --- a/internal/integration/unified/collection_operation_execution.go +++ b/internal/integration/unified/collection_operation_execution.go @@ -403,7 +403,7 @@ func executeDeleteOne(ctx context.Context, operation *operation) (*operationResu } var filter bson.Raw - opts := options.Delete() + opts := options.DeleteOne() elems, err := operation.Arguments.Elements() if err != nil { @@ -457,7 +457,7 @@ func executeDeleteMany(ctx context.Context, operation *operation) (*operationRes } var filter bson.Raw - opts := options.Delete() + opts := options.DeleteMany() elems, err := operation.Arguments.Elements() if err != nil { @@ -1316,12 +1316,12 @@ func executeUpdateOne(ctx context.Context, operation *operation) (*operationResu return nil, err } - updateArgs, err := createUpdateArguments(operation.Arguments) + updateArgs, updateOpts, err := createUpdateOneArguments(operation.Arguments) if err != nil { return nil, err } - res, err := coll.UpdateOne(ctx, updateArgs.filter, updateArgs.update, updateArgs.opts) + res, err := coll.UpdateOne(ctx, updateArgs.filter, updateArgs.update, updateOpts) raw, buildErr := buildUpdateResultDocument(res) if buildErr != nil { return nil, buildErr @@ -1335,12 +1335,12 @@ func executeUpdateMany(ctx context.Context, operation *operation) (*operationRes return nil, err } - updateArgs, err := createUpdateArguments(operation.Arguments) + updateArgs, updateOpts, err := createUpdateManyArguments(operation.Arguments) if err != nil { return nil, err } - res, err := coll.UpdateMany(ctx, updateArgs.filter, updateArgs.update, updateArgs.opts) + res, err := coll.UpdateMany(ctx, updateArgs.filter, updateArgs.update, updateOpts) raw, buildErr := buildUpdateResultDocument(res) if buildErr != nil { return nil, buildErr diff --git a/internal/integration/unified/crud_helpers.go b/internal/integration/unified/crud_helpers.go index c640cf28c8..56eb49ebcc 100644 --- a/internal/integration/unified/crud_helpers.go +++ b/internal/integration/unified/crud_helpers.go @@ -23,14 +23,67 @@ func newMissingArgumentError(arg string) error { type updateArguments struct { filter bson.Raw update interface{} - opts *options.UpdateOptionsBuilder } -func createUpdateArguments(args bson.Raw) (*updateArguments, error) { - ua := &updateArguments{ - opts: options.Update(), +func createUpdateManyArguments(args bson.Raw) (*updateArguments, *options.UpdateManyOptionsBuilder, error) { + ua := &updateArguments{} + opts := options.UpdateMany() + + elems, _ := args.Elements() + for _, elem := range elems { + key := elem.Key() + val := elem.Value() + + switch key { + case "arrayFilters": + opts.SetArrayFilters( + bsonutil.RawToInterfaces(bsonutil.RawArrayToDocuments(val.Array())...), + ) + case "bypassDocumentValidation": + opts.SetBypassDocumentValidation(val.Boolean()) + case "collation": + collation, err := createCollation(val.Document()) + if err != nil { + return nil, nil, fmt.Errorf("error creating collation: %w", err) + } + opts.SetCollation(collation) + case "comment": + opts.SetComment(val) + case "filter": + ua.filter = val.Document() + case "hint": + hint, err := createHint(val) + if err != nil { + return nil, nil, fmt.Errorf("error creating hint: %w", err) + } + opts.SetHint(hint) + case "let": + opts.SetLet(val.Document()) + case "update": + var err error + ua.update, err = createUpdateValue(val) + if err != nil { + return nil, nil, fmt.Errorf("error processing update value: %w", err) + } + case "upsert": + opts.SetUpsert(val.Boolean()) + default: + return nil, nil, fmt.Errorf("unrecognized update option %q", key) + } } - var err error + if ua.filter == nil { + return nil, nil, newMissingArgumentError("filter") + } + if ua.update == nil { + return nil, nil, newMissingArgumentError("update") + } + + return ua, opts, nil +} + +func createUpdateOneArguments(args bson.Raw) (*updateArguments, *options.UpdateOneOptionsBuilder, error) { + ua := &updateArguments{} + opts := options.UpdateOne() elems, _ := args.Elements() for _, elem := range elems { @@ -39,48 +92,49 @@ func createUpdateArguments(args bson.Raw) (*updateArguments, error) { switch key { case "arrayFilters": - ua.opts.SetArrayFilters( + opts.SetArrayFilters( bsonutil.RawToInterfaces(bsonutil.RawArrayToDocuments(val.Array())...), ) case "bypassDocumentValidation": - ua.opts.SetBypassDocumentValidation(val.Boolean()) + opts.SetBypassDocumentValidation(val.Boolean()) case "collation": collation, err := createCollation(val.Document()) if err != nil { - return nil, fmt.Errorf("error creating collation: %w", err) + return nil, nil, fmt.Errorf("error creating collation: %w", err) } - ua.opts.SetCollation(collation) + opts.SetCollation(collation) case "comment": - ua.opts.SetComment(val) + opts.SetComment(val) case "filter": ua.filter = val.Document() case "hint": hint, err := createHint(val) if err != nil { - return nil, fmt.Errorf("error creating hint: %w", err) + return nil, nil, fmt.Errorf("error creating hint: %w", err) } - ua.opts.SetHint(hint) + opts.SetHint(hint) case "let": - ua.opts.SetLet(val.Document()) + opts.SetLet(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) + return nil, nil, fmt.Errorf("error processing update value: %w", err) } case "upsert": - ua.opts.SetUpsert(val.Boolean()) + opts.SetUpsert(val.Boolean()) default: - return nil, fmt.Errorf("unrecognized update option %q", key) + return nil, nil, fmt.Errorf("unrecognized update option %q", key) } } if ua.filter == nil { - return nil, newMissingArgumentError("filter") + return nil, nil, newMissingArgumentError("filter") } if ua.update == nil { - return nil, newMissingArgumentError("update") + return nil, nil, newMissingArgumentError("update") } - return ua, nil + return ua, opts, nil } type listCollectionsArguments struct { diff --git a/mongo/bulk_write.go b/mongo/bulk_write.go index 26ad74f016..d042a89c37 100644 --- a/mongo/bulk_write.go +++ b/mongo/bulk_write.go @@ -336,44 +336,37 @@ 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, + 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, + 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 +416,19 @@ 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{} + 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 +436,39 @@ 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 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/collection.go b/mongo/collection.go index 5ba3113c0c..4c2cfef4f0 100644 --- a/mongo/collection.go +++ b/mongo/collection.go @@ -454,7 +454,7 @@ func (coll *Collection) delete( filter interface{}, deleteOne bool, expectedRr returnResult, - opts ...options.Lister[options.DeleteOptions], + args *options.DeleteManyOptions, ) (*DeleteResult, error) { if ctx == nil { @@ -492,11 +492,6 @@ func (coll *Collection) delete( limit = 1 } - args, err := mongoutil.NewOptions[options.DeleteOptions](opts...) - if err != nil { - return nil, fmt.Errorf("failed to construct options from builder: %w", err) - } - didx, doc := bsoncore.AppendDocumentStart(nil) doc = bsoncore.AppendDocumentElement(doc, "q", f) doc = bsoncore.AppendInt32Element(doc, "limit", limit) @@ -569,9 +564,20 @@ func (coll *Collection) delete( func (coll *Collection) DeleteOne( ctx context.Context, filter interface{}, - opts ...options.Lister[options.DeleteOptions], + opts ...options.Lister[options.DeleteOneOptions], ) (*DeleteResult, error) { - return coll.delete(ctx, filter, true, rrOne, opts...) + args, err := mongoutil.NewOptions[options.DeleteOneOptions](opts...) + if err != nil { + return nil, fmt.Errorf("failed to construct options from builder: %w", err) + } + deleteOptions := &options.DeleteManyOptions{ + Collation: args.Collation, + Comment: args.Comment, + Hint: args.Hint, + Let: args.Let, + } + + return coll.delete(ctx, filter, true, rrOne, deleteOptions) } // DeleteMany executes a delete command to delete documents from the collection. @@ -587,9 +593,14 @@ func (coll *Collection) DeleteOne( func (coll *Collection) DeleteMany( ctx context.Context, filter interface{}, - opts ...options.Lister[options.DeleteOptions], + opts ...options.Lister[options.DeleteManyOptions], ) (*DeleteResult, error) { - return coll.delete(ctx, filter, false, rrMany, opts...) + args, err := mongoutil.NewOptions[options.DeleteManyOptions](opts...) + if err != nil { + return nil, fmt.Errorf("failed to construct options from builder: %w", err) + } + + return coll.delete(ctx, filter, false, rrMany, args) } func (coll *Collection) updateOrReplace( @@ -599,31 +610,25 @@ func (coll *Collection) updateOrReplace( multi bool, expectedRr returnResult, checkDollarKey bool, - opts ...options.Lister[options.UpdateOptions], + 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, + 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 +724,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 +750,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 +761,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, updateOptions) } // UpdateMany executes an update command to update documents in the collection. @@ -776,7 +795,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 +806,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, args) } // ReplaceOne executes an update command to replace at most one document in the collection. @@ -832,7 +856,7 @@ func (coll *Collection) ReplaceOne( return nil, err } - updateArgs := &options.UpdateOptions{ + updateOptions := &options.UpdateManyOptions{ BypassDocumentValidation: args.BypassDocumentValidation, Collation: args.Collation, Upsert: args.Upsert, @@ -841,8 +865,6 @@ func (coll *Collection) ReplaceOne( Comment: args.Comment, } - updateOptions := mongoutil.NewOptionsLister(updateArgs, nil) - return coll.updateOrReplace(ctx, f, r, false, rrOne, false, updateOptions) } diff --git a/mongo/crud_examples_test.go b/mongo/crud_examples_test.go index 159b5cb761..ff61f19f3d 100644 --- a/mongo/crud_examples_test.go +++ b/mongo/crud_examples_test.go @@ -283,7 +283,7 @@ func ExampleCollection_DeleteMany() { // Delete all documents in which the "name" field is "Bob" or "bob". // Specify the Collation option to provide a collation that will ignore case // for string comparisons. - opts := options.Delete().SetCollation(&options.Collation{ + opts := options.DeleteMany().SetCollation(&options.Collation{ Locale: "en_US", Strength: 1, CaseLevel: false, @@ -301,7 +301,7 @@ func ExampleCollection_DeleteOne() { // Delete at most one document in which the "name" field is "Bob" or "bob". // Specify the SetCollation option to provide a collation that will ignore // case for string comparisons. - opts := options.Delete().SetCollation(&options.Collation{ + opts := options.DeleteOne().SetCollation(&options.Collation{ Locale: "en_US", Strength: 1, CaseLevel: false, @@ -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/deleteoptions.go b/mongo/options/deleteoptions.go index 232660a801..27e0be85a3 100644 --- a/mongo/options/deleteoptions.go +++ b/mongo/options/deleteoptions.go @@ -6,9 +6,9 @@ package options -// DeleteOptions represents arguments that can be used to configure DeleteOne -// and DeleteMany operations. -type DeleteOptions struct { +// DeleteOneOptions represents arguments that can be used to configure DeleteOne +// operations. +type DeleteOneOptions struct { // 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. @@ -33,26 +33,26 @@ type DeleteOptions struct { Let interface{} } -// DeleteOptionsBuilder contains options to configure delete operations. Each +// DeleteOneOptionsBuilder contains options to configure DeleteOne operations. Each // option can be set through setter functions. See documentation for each setter // function for an explanation of the option. -type DeleteOptionsBuilder struct { - Opts []func(*DeleteOptions) error +type DeleteOneOptionsBuilder struct { + Opts []func(*DeleteOneOptions) error } -// Delete creates a new DeleteOptions instance. -func Delete() *DeleteOptionsBuilder { - return &DeleteOptionsBuilder{} +// DeleteOne creates a new DeleteOneOptions instance. +func DeleteOne() *DeleteOneOptionsBuilder { + return &DeleteOneOptionsBuilder{} } -// List returns a list of DeleteOptions setter functions. -func (do *DeleteOptionsBuilder) List() []func(*DeleteOptions) error { +// List returns a list of DeleteOneOptions setter functions. +func (do *DeleteOneOptionsBuilder) List() []func(*DeleteOneOptions) error { return do.Opts } // SetCollation sets the value for the Collation field. -func (do *DeleteOptionsBuilder) SetCollation(c *Collation) *DeleteOptionsBuilder { - do.Opts = append(do.Opts, func(opts *DeleteOptions) error { +func (do *DeleteOneOptionsBuilder) SetCollation(c *Collation) *DeleteOneOptionsBuilder { + do.Opts = append(do.Opts, func(opts *DeleteOneOptions) error { opts.Collation = c return nil @@ -62,8 +62,8 @@ func (do *DeleteOptionsBuilder) SetCollation(c *Collation) *DeleteOptionsBuilder } // SetComment sets the value for the Comment field. -func (do *DeleteOptionsBuilder) SetComment(comment interface{}) *DeleteOptionsBuilder { - do.Opts = append(do.Opts, func(opts *DeleteOptions) error { +func (do *DeleteOneOptionsBuilder) SetComment(comment interface{}) *DeleteOneOptionsBuilder { + do.Opts = append(do.Opts, func(opts *DeleteOneOptions) error { opts.Comment = comment return nil @@ -73,8 +73,8 @@ func (do *DeleteOptionsBuilder) SetComment(comment interface{}) *DeleteOptionsBu } // SetHint sets the value for the Hint field. -func (do *DeleteOptionsBuilder) SetHint(hint interface{}) *DeleteOptionsBuilder { - do.Opts = append(do.Opts, func(opts *DeleteOptions) error { +func (do *DeleteOneOptionsBuilder) SetHint(hint interface{}) *DeleteOneOptionsBuilder { + do.Opts = append(do.Opts, func(opts *DeleteOneOptions) error { opts.Hint = hint return nil @@ -84,8 +84,96 @@ func (do *DeleteOptionsBuilder) SetHint(hint interface{}) *DeleteOptionsBuilder } // SetLet sets the value for the Let field. -func (do *DeleteOptionsBuilder) SetLet(let interface{}) *DeleteOptionsBuilder { - do.Opts = append(do.Opts, func(opts *DeleteOptions) error { +func (do *DeleteOneOptionsBuilder) SetLet(let interface{}) *DeleteOneOptionsBuilder { + do.Opts = append(do.Opts, func(opts *DeleteOneOptions) error { + opts.Let = let + + return nil + }) + + return do +} + +// DeleteManyOptions represents arguments that can be used to configure DeleteMany +// operations. +type DeleteManyOptions struct { + // 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.4. 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{} + + // Specifies parameters for the delete 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{} +} + +// DeleteManyOptionsBuilder contains options to configure DeleteMany operations. +// Each option can be set through setter functions. See documentation for each +// setter function for an explanation of the option. +type DeleteManyOptionsBuilder struct { + Opts []func(*DeleteManyOptions) error +} + +// DeleteMany creates a new DeleteManyOptions instance. +func DeleteMany() *DeleteManyOptionsBuilder { + return &DeleteManyOptionsBuilder{} +} + +// List returns a list of DeleteOneOptions setter functions. +func (do *DeleteManyOptionsBuilder) List() []func(*DeleteManyOptions) error { + return do.Opts +} + +// SetCollation sets the value for the Collation field. +func (do *DeleteManyOptionsBuilder) SetCollation(c *Collation) *DeleteManyOptionsBuilder { + do.Opts = append(do.Opts, func(opts *DeleteManyOptions) error { + opts.Collation = c + + return nil + }) + + return do +} + +// SetComment sets the value for the Comment field. +func (do *DeleteManyOptionsBuilder) SetComment(comment interface{}) *DeleteManyOptionsBuilder { + do.Opts = append(do.Opts, func(opts *DeleteManyOptions) error { + opts.Comment = comment + + return nil + }) + + return do +} + +// SetHint sets the value for the Hint field. +func (do *DeleteManyOptionsBuilder) SetHint(hint interface{}) *DeleteManyOptionsBuilder { + do.Opts = append(do.Opts, func(opts *DeleteManyOptions) error { + opts.Hint = hint + + return nil + }) + + return do +} + +// SetLet sets the value for the Let field. +func (do *DeleteManyOptionsBuilder) SetLet(let interface{}) *DeleteManyOptionsBuilder { + do.Opts = append(do.Opts, func(opts *DeleteManyOptions) error { opts.Let = let return nil diff --git a/mongo/options/updateoptions.go b/mongo/options/updateoptions.go index 34ff1ba3f8..960deacb64 100644 --- a/mongo/options/updateoptions.go +++ b/mongo/options/updateoptions.go @@ -6,9 +6,9 @@ package options -// UpdateOptions represents arguments that can be used to configure UpdateOne and -// UpdateMany operations. -type UpdateOptions struct { +// 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. @@ -48,26 +48,26 @@ type UpdateOptions struct { Let interface{} } -// UpdateOptionsBuilder contains options to configure update 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 +// 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 } -// Update creates a new UpdateOptions instance. -func Update() *UpdateOptionsBuilder { - return &UpdateOptionsBuilder{} +// UpdateOne creates a new UpdateOneOptions instance. +func UpdateOne() *UpdateOneOptionsBuilder { + return &UpdateOneOptionsBuilder{} } -// List returns a list of UpdateOptions setter functions. -func (uo *UpdateOptionsBuilder) List() []func(*UpdateOptions) error { +// List returns a list of UpdateOneOptions setter functions. +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 +77,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 +88,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 +99,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 +110,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 +121,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 +132,144 @@ 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 + }) + + return uo +} + +// 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. + 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{} +} + +// 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 UpdateManyOptionsBuilder struct { + Opts []func(*UpdateManyOptions) error +} + +// UpdateMany creates a new UpdateManyOptions 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