Skip to content

Commit 6c086d6

Browse files
Divjot AroraRoland Fong
authored andcommitted
Add mongo/deleteopt
GODRIVER-272 Change-Id: I288c7632ae66e39998ebcc68eb958ba8a0ec23c8
1 parent 7b2e076 commit 6c086d6

File tree

5 files changed

+419
-16
lines changed

5 files changed

+419
-16
lines changed

mongo/collection.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/mongodb/mongo-go-driver/core/writeconcern"
2222
"github.com/mongodb/mongo-go-driver/mongo/aggregateopt"
2323
"github.com/mongodb/mongo-go-driver/mongo/countopt"
24+
"github.com/mongodb/mongo-go-driver/mongo/deleteopt"
2425
"github.com/mongodb/mongo-go-driver/mongo/distinctopt"
2526
"github.com/mongodb/mongo-go-driver/mongo/findopt"
2627
"github.com/mongodb/mongo-go-driver/mongo/insertopt"
@@ -185,7 +186,7 @@ func (coll *Collection) InsertMany(ctx context.Context, documents []interface{},
185186
// *bson.Document. See TransformDocument for the list of valid types for
186187
// filter.
187188
func (coll *Collection) DeleteOne(ctx context.Context, filter interface{},
188-
opts ...option.DeleteOptioner) (*DeleteResult, error) {
189+
opts ...deleteopt.Delete) (*DeleteResult, error) {
189190

190191
if ctx == nil {
191192
ctx = context.Background()
@@ -201,11 +202,16 @@ func (coll *Collection) DeleteOne(ctx context.Context, filter interface{},
201202
bson.EC.Int32("limit", 1)),
202203
}
203204

205+
deleteOpts, err := deleteopt.BundleDelete(opts...).Unbundle(true)
206+
if err != nil {
207+
return nil, err
208+
}
209+
204210
oldns := coll.namespace()
205211
cmd := command.Delete{
206212
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
207213
Deletes: deleteDocs,
208-
Opts: opts,
214+
Opts: deleteOpts,
209215
}
210216

211217
res, err := dispatch.Delete(ctx, cmd, coll.client.topology, coll.writeSelector, coll.writeConcern)
@@ -224,7 +230,7 @@ func (coll *Collection) DeleteOne(ctx context.Context, filter interface{},
224230
// *bson.Document. See TransformDocument for the list of valid types for
225231
// filter.
226232
func (coll *Collection) DeleteMany(ctx context.Context, filter interface{},
227-
opts ...option.DeleteOptioner) (*DeleteResult, error) {
233+
opts ...deleteopt.Delete) (*DeleteResult, error) {
228234

229235
if ctx == nil {
230236
ctx = context.Background()
@@ -236,11 +242,16 @@ func (coll *Collection) DeleteMany(ctx context.Context, filter interface{},
236242
}
237243
deleteDocs := []*bson.Document{bson.NewDocument(bson.EC.SubDocument("q", f), bson.EC.Int32("limit", 0))}
238244

245+
deleteOpts, err := deleteopt.BundleDelete(opts...).Unbundle(true)
246+
if err != nil {
247+
return nil, err
248+
}
249+
239250
oldns := coll.namespace()
240251
cmd := command.Delete{
241252
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
242253
Deletes: deleteDocs,
243-
Opts: opts,
254+
Opts: deleteOpts,
244255
}
245256

246257
res, err := dispatch.Delete(ctx, cmd, coll.client.topology, coll.writeSelector, coll.writeConcern)

mongo/collection_internal_test.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/mongodb/mongo-go-driver/internal/testutil"
2020
"github.com/mongodb/mongo-go-driver/mongo/aggregateopt"
2121
"github.com/mongodb/mongo-go-driver/mongo/countopt"
22+
"github.com/mongodb/mongo-go-driver/mongo/deleteopt"
2223
"github.com/mongodb/mongo-go-driver/mongo/distinctopt"
2324
"github.com/mongodb/mongo-go-driver/mongo/findopt"
2425
"github.com/mongodb/mongo-go-driver/mongo/insertopt"
@@ -394,7 +395,11 @@ func TestCollection_DeleteOne_notFound_withOption(t *testing.T) {
394395
initCollection(t, coll)
395396

396397
filter := bson.NewDocument(bson.EC.Int32("x", 0))
397-
result, err := coll.DeleteOne(context.Background(), filter, Opt.Collation(&option.Collation{Locale: "en_US"}))
398+
399+
collationOpt := &option.Collation{
400+
Locale: "en_US",
401+
}
402+
result, err := coll.DeleteOne(context.Background(), filter, deleteopt.Collation(collationOpt))
398403
require.Nil(t, err)
399404
require.Equal(t, result.DeletedCount, int64(0))
400405
}
@@ -447,9 +452,8 @@ func TestCollection_DeleteMany_WriteConcernError(t *testing.T) {
447452
filter := bson.NewDocument(bson.EC.Int32("x", 1))
448453
coll := createTestCollection(t, nil, nil)
449454

450-
optwc, err := Opt.WriteConcern(writeconcern.New(writeconcern.W(25)))
451-
require.NoError(t, err)
452-
_, err = coll.DeleteOne(context.Background(), filter, optwc)
455+
optwc := deleteopt.WriteConcern(writeconcern.New(writeconcern.W(25)))
456+
_, err := coll.DeleteOne(context.Background(), filter, optwc)
453457
got, ok := err.(WriteConcernError)
454458
if !ok {
455459
t.Errorf("Did not receive correct type of error. got %T; want %T", err, WriteConcernError{})
@@ -511,7 +515,7 @@ func TestCollection_DeleteMany_notFound_withOption(t *testing.T) {
511515
filter := bson.NewDocument(
512516
bson.EC.SubDocumentFromElements("x", bson.EC.Int32("$lt", 1)))
513517

514-
result, err := coll.DeleteMany(context.Background(), filter, Opt.Collation(&option.Collation{Locale: "en_US"}))
518+
result, err := coll.DeleteMany(context.Background(), filter, deleteopt.Collation(&option.Collation{Locale: "en_US"}))
515519
require.Nil(t, err)
516520
require.Equal(t, result.DeletedCount, int64(0))
517521
}
@@ -564,9 +568,8 @@ func TestCollection_DeleteOne_WriteConcernError(t *testing.T) {
564568
filter := bson.NewDocument(bson.EC.Int32("x", 1))
565569
coll := createTestCollection(t, nil, nil)
566570

567-
optwc, err := Opt.WriteConcern(writeconcern.New(writeconcern.W(25)))
568-
require.NoError(t, err)
569-
_, err = coll.DeleteMany(context.Background(), filter, optwc)
571+
optwc := deleteopt.WriteConcern(writeconcern.New(writeconcern.W(25)))
572+
_, err := coll.DeleteMany(context.Background(), filter, optwc)
570573
got, ok := err.(WriteConcernError)
571574
if !ok {
572575
t.Errorf("Did not receive correct type of error. got %T; want %T", err, WriteConcernError{})

mongo/crud_spec_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/mongodb/mongo-go-driver/internal/testutil/helpers"
1717
"github.com/mongodb/mongo-go-driver/mongo/aggregateopt"
1818
"github.com/mongodb/mongo-go-driver/mongo/countopt"
19+
"github.com/mongodb/mongo-go-driver/mongo/deleteopt"
1920
"github.com/mongodb/mongo-go-driver/mongo/distinctopt"
2021
"github.com/mongodb/mongo-go-driver/mongo/findopt"
2122
"github.com/mongodb/mongo-go-driver/mongo/replaceopt"
@@ -262,10 +263,11 @@ func deleteManyTest(t *testing.T, coll *Collection, test *testCase) {
262263
t.Run(test.Description, func(t *testing.T) {
263264
filter := test.Operation.Arguments["filter"].(map[string]interface{})
264265

265-
var opts []option.DeleteOptioner
266+
var opts []deleteopt.Delete
266267

267268
if collation, found := test.Operation.Arguments["collation"]; found {
268-
opts = append(opts, Opt.Collation(collationFromMap(collation.(map[string]interface{}))))
269+
mapCollation := collation.(map[string]interface{})
270+
opts = append(opts, deleteopt.Collation(collationFromMap(mapCollation)))
269271
}
270272

271273
actual, err := coll.DeleteMany(context.Background(), filter, opts...)
@@ -288,10 +290,11 @@ func deleteOneTest(t *testing.T, coll *Collection, test *testCase) {
288290
t.Run(test.Description, func(t *testing.T) {
289291
filter := test.Operation.Arguments["filter"].(map[string]interface{})
290292

291-
var opts []option.DeleteOptioner
293+
var opts []deleteopt.Delete
292294

293295
if collation, found := test.Operation.Arguments["collation"]; found {
294-
opts = append(opts, Opt.Collation(collationFromMap(collation.(map[string]interface{}))))
296+
mapCollation := collationFromMap(collation.(map[string]interface{}))
297+
opts = append(opts, deleteopt.Collation(mapCollation))
295298
}
296299

297300
actual, err := coll.DeleteOne(context.Background(), filter, opts...)

mongo/deleteopt/deleteopt.go

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package deleteopt
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/mongodb/mongo-go-driver/core/option"
7+
"github.com/mongodb/mongo-go-driver/core/writeconcern"
8+
)
9+
10+
var deleteBundle = new(DeleteBundle)
11+
12+
// Delete is options for the delete() function.
13+
type Delete interface {
14+
delete()
15+
ConvertOption() option.DeleteOptioner
16+
}
17+
18+
// DeleteBundle is a bundle of Delete options
19+
type DeleteBundle struct {
20+
option Delete
21+
next *DeleteBundle
22+
}
23+
24+
func (db *DeleteBundle) delete() {}
25+
26+
// ConvertOption implements the Delete interface.
27+
func (db *DeleteBundle) ConvertOption() option.DeleteOptioner {
28+
return nil
29+
}
30+
31+
// BundleDelete bundles Delete options.
32+
func BundleDelete(opts ...Delete) *DeleteBundle {
33+
head := deleteBundle
34+
35+
for _, opt := range opts {
36+
newBundle := DeleteBundle{
37+
option: opt,
38+
next: head,
39+
}
40+
41+
head = &newBundle
42+
}
43+
44+
return head
45+
}
46+
47+
// Collation adds an option to specify a collation.
48+
func (db *DeleteBundle) Collation(c *option.Collation) *DeleteBundle {
49+
bundle := &DeleteBundle{
50+
option: Collation(c),
51+
next: db,
52+
}
53+
54+
return bundle
55+
}
56+
57+
// WriteConcern adds an option to specify a write concern.
58+
func (db *DeleteBundle) WriteConcern(wc *writeconcern.WriteConcern) *DeleteBundle {
59+
bundle := &DeleteBundle{
60+
option: WriteConcern(wc),
61+
next: db,
62+
}
63+
64+
return bundle
65+
}
66+
67+
// Unbundle transforms a bundle into a slice of options, optionally deduplicating
68+
func (db *DeleteBundle) Unbundle(deduplicate bool) ([]option.DeleteOptioner, error) {
69+
70+
options, err := db.unbundle()
71+
if err != nil {
72+
return nil, err
73+
}
74+
75+
if !deduplicate {
76+
return options, nil
77+
}
78+
79+
// iterate backwards and make dedup slice
80+
optionsSet := make(map[reflect.Type]struct{})
81+
82+
for i := len(options) - 1; i >= 0; i-- {
83+
currOption := options[i]
84+
optionType := reflect.TypeOf(currOption)
85+
86+
if _, ok := optionsSet[optionType]; ok {
87+
// option already found
88+
options = append(options[:i], options[i+1:]...)
89+
continue
90+
}
91+
92+
optionsSet[optionType] = struct{}{}
93+
}
94+
95+
return options, nil
96+
}
97+
98+
// Calculates the total length of a bundle, accounting for nested bundles.
99+
func (db *DeleteBundle) bundleLength() int {
100+
if db == nil {
101+
return 0
102+
}
103+
104+
bundleLen := 0
105+
for ; db != nil && db.option != nil; db = db.next {
106+
if converted, ok := db.option.(*DeleteBundle); ok {
107+
// nested bundle
108+
bundleLen += converted.bundleLength()
109+
continue
110+
}
111+
112+
bundleLen++
113+
}
114+
115+
return bundleLen
116+
}
117+
118+
// Helper that recursively unwraps bundle into slice of options
119+
func (db *DeleteBundle) unbundle() ([]option.DeleteOptioner, error) {
120+
if db == nil {
121+
return nil, nil
122+
}
123+
124+
listLen := db.bundleLength()
125+
126+
options := make([]option.DeleteOptioner, listLen)
127+
index := listLen - 1
128+
129+
for listHead := db; listHead != nil && listHead.option != nil; listHead = listHead.next {
130+
// if the current option is a nested bundle, Unbundle it and add its options to the current array
131+
if converted, ok := listHead.option.(*DeleteBundle); ok {
132+
nestedOptions, err := converted.unbundle()
133+
if err != nil {
134+
return nil, err
135+
}
136+
137+
// where to start inserting nested options
138+
startIndex := index - len(nestedOptions) + 1
139+
140+
// add nested options in order
141+
for _, nestedOp := range nestedOptions {
142+
options[startIndex] = nestedOp
143+
startIndex++
144+
}
145+
index -= len(nestedOptions)
146+
continue
147+
}
148+
149+
options[index] = listHead.option.ConvertOption()
150+
index--
151+
}
152+
153+
return options, nil
154+
}
155+
156+
// String implements the Stringer interface
157+
func (db *DeleteBundle) String() string {
158+
if db == nil {
159+
return ""
160+
}
161+
162+
str := ""
163+
for head := db; head != nil && head.option != nil; head = head.next {
164+
if converted, ok := head.option.(*DeleteBundle); ok {
165+
str += converted.String()
166+
continue
167+
}
168+
169+
str += head.option.ConvertOption().String() + "\n"
170+
}
171+
172+
return str
173+
}
174+
175+
// Collation specifies a collation.
176+
func Collation(c *option.Collation) OptCollation {
177+
return OptCollation{Collation: c}
178+
}
179+
180+
// WriteConcern specifies a write concern.
181+
func WriteConcern(wc *writeconcern.WriteConcern) OptWriteConcern {
182+
return OptWriteConcern{wc}
183+
}
184+
185+
// OptCollation specifies a collation.
186+
type OptCollation option.OptCollation
187+
188+
func (OptCollation) delete() {}
189+
190+
// ConvertOption implements the Delete interface.
191+
func (opt OptCollation) ConvertOption() option.DeleteOptioner {
192+
return option.OptCollation(opt)
193+
}
194+
195+
// OptWriteConcern specifies a write concern.
196+
type OptWriteConcern option.OptWriteConcern
197+
198+
func (OptWriteConcern) delete() {}
199+
200+
// ConvertOption implements the Delete interface.
201+
func (opt OptWriteConcern) ConvertOption() option.DeleteOptioner {
202+
return option.OptWriteConcern(opt)
203+
}

0 commit comments

Comments
 (0)