Skip to content

Commit b5643d5

Browse files
committed
Add mongo/collectionopt
GODRIVER-272 Change-Id: I801d503bdb6452de4b4537cbca32b7fb79f88a80
1 parent c6a8120 commit b5643d5

File tree

5 files changed

+396
-9
lines changed

5 files changed

+396
-9
lines changed

mongo/collection.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/mongodb/mongo-go-driver/core/readpref"
2121
"github.com/mongodb/mongo-go-driver/core/writeconcern"
2222
"github.com/mongodb/mongo-go-driver/mongo/aggregateopt"
23+
"github.com/mongodb/mongo-go-driver/mongo/collectionopt"
2324
"github.com/mongodb/mongo-go-driver/mongo/countopt"
2425
"github.com/mongodb/mongo-go-driver/mongo/deleteopt"
2526
"github.com/mongodb/mongo-go-driver/mongo/distinctopt"
@@ -41,14 +42,34 @@ type Collection struct {
4142
writeSelector description.ServerSelector
4243
}
4344

44-
func newCollection(db *Database, name string) *Collection {
45+
func newCollection(db *Database, name string, opts ...collectionopt.Option) *Collection {
46+
collOpt, err := collectionopt.BundleCollection(opts...).Unbundle()
47+
if err != nil {
48+
return nil
49+
}
50+
51+
rc := db.readConcern
52+
if collOpt.ReadConcern != nil {
53+
rc = collOpt.ReadConcern
54+
}
55+
56+
wc := db.writeConcern
57+
if collOpt.WriteConcern != nil {
58+
wc = collOpt.WriteConcern
59+
}
60+
61+
rp := db.readPreference
62+
if collOpt.ReadPreference != nil {
63+
rp = collOpt.ReadPreference
64+
}
65+
4566
coll := &Collection{
4667
client: db.client,
4768
db: db,
4869
name: name,
49-
readPreference: db.readPreference,
50-
readConcern: db.readConcern,
51-
writeConcern: db.writeConcern,
70+
readPreference: rp,
71+
readConcern: rc,
72+
writeConcern: wc,
5273
readSelector: db.readSelector,
5374
writeSelector: db.writeSelector,
5475
}

mongo/collection_internal_test.go

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ import (
1515
"github.com/mongodb/mongo-go-driver/bson"
1616
"github.com/mongodb/mongo-go-driver/bson/objectid"
1717
"github.com/mongodb/mongo-go-driver/core/option"
18+
"github.com/mongodb/mongo-go-driver/core/readconcern"
19+
"github.com/mongodb/mongo-go-driver/core/readpref"
1820
"github.com/mongodb/mongo-go-driver/core/writeconcern"
1921
"github.com/mongodb/mongo-go-driver/internal/testutil"
2022
"github.com/mongodb/mongo-go-driver/mongo/aggregateopt"
23+
"github.com/mongodb/mongo-go-driver/mongo/collectionopt"
2124
"github.com/mongodb/mongo-go-driver/mongo/countopt"
2225
"github.com/mongodb/mongo-go-driver/mongo/deleteopt"
2326
"github.com/mongodb/mongo-go-driver/mongo/distinctopt"
@@ -29,15 +32,15 @@ import (
2932
"github.com/stretchr/testify/require"
3033
)
3134

32-
func createTestCollection(t *testing.T, dbName *string, collName *string) *Collection {
35+
func createTestCollection(t *testing.T, dbName *string, collName *string, opts ...collectionopt.Option) *Collection {
3336
if collName == nil {
3437
coll := testutil.ColName(t)
3538
collName = &coll
3639
}
3740

3841
db := createTestDatabase(t, dbName)
3942

40-
return db.Collection(*collName)
43+
return db.Collection(*collName, opts...)
4144
}
4245

4346
func initCollection(t *testing.T, coll *Collection) {
@@ -76,6 +79,73 @@ func TestCollection_initialize(t *testing.T) {
7679
require.NotNil(t, coll.db)
7780
}
7881

82+
func compareColls(t *testing.T, expected *Collection, got *Collection) {
83+
switch {
84+
case expected.readPreference != got.readPreference:
85+
t.Errorf("expected read preference %#v. got %#v", expected.readPreference, got.readPreference)
86+
case expected.readConcern != got.readConcern:
87+
t.Errorf("expected read concern %#v. got %#v", expected.readConcern, got.readConcern)
88+
case expected.writeConcern != got.writeConcern:
89+
t.Errorf("expected write concern %#v. got %#v", expected.writeConcern, got.writeConcern)
90+
}
91+
}
92+
93+
func TestCollection_Options(t *testing.T) {
94+
name := "testDb_options"
95+
rpPrimary := readpref.Primary()
96+
rpSecondary := readpref.Secondary()
97+
wc1 := writeconcern.New(writeconcern.W(5))
98+
wc2 := writeconcern.New(writeconcern.W(10))
99+
rcLocal := readconcern.Local()
100+
rcMajority := readconcern.Majority()
101+
102+
opts := []collectionopt.Option{collectionopt.ReadPreference(rpPrimary), collectionopt.ReadConcern(rcLocal), collectionopt.WriteConcern(wc1),
103+
collectionopt.ReadPreference(rpSecondary), collectionopt.ReadConcern(rcMajority), collectionopt.WriteConcern(wc2)}
104+
105+
dbName := "collection_internal_test_db1"
106+
107+
expectedColl := &Collection{
108+
readConcern: rcMajority,
109+
readPreference: rpSecondary,
110+
writeConcern: wc2,
111+
}
112+
113+
t.Run("IndividualOptions", func(t *testing.T) {
114+
// if options specified multiple times, last instance should take precedence
115+
coll := createTestCollection(t, &dbName, &name, opts...)
116+
compareColls(t, expectedColl, coll)
117+
})
118+
119+
t.Run("Bundle", func(t *testing.T) {
120+
coll := createTestCollection(t, &dbName, &name, collectionopt.BundleCollection(opts...))
121+
compareColls(t, expectedColl, coll)
122+
})
123+
}
124+
125+
func TestCollection_InheritOptions(t *testing.T) {
126+
name := "testDb_options_inherit"
127+
client := createTestClient(t)
128+
129+
rpPrimary := readpref.Primary()
130+
rcLocal := readconcern.Local()
131+
wc1 := writeconcern.New(writeconcern.W(10))
132+
133+
db := client.Database("collection_internal_test_db2")
134+
db.readPreference = rpPrimary
135+
db.readConcern = rcLocal
136+
coll := db.Collection(name, collectionopt.WriteConcern(wc1))
137+
138+
// coll should inherit read preference and read concern from client
139+
switch {
140+
case coll.readPreference != rpPrimary:
141+
t.Errorf("expected read preference primary. got %#v", coll.readPreference)
142+
case coll.readConcern != rcLocal:
143+
t.Errorf("expected read concern local. got %#v", coll.readConcern)
144+
case coll.writeConcern != wc1:
145+
t.Errorf("expected write concern %#v. got %#v", wc1, coll.writeConcern)
146+
}
147+
}
148+
79149
func TestCollection_namespace(t *testing.T) {
80150
t.Parallel()
81151

mongo/collectionopt/collectionopt.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package collectionopt
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/mongodb/mongo-go-driver/core/readconcern"
7+
"github.com/mongodb/mongo-go-driver/core/readpref"
8+
"github.com/mongodb/mongo-go-driver/core/writeconcern"
9+
)
10+
11+
var collectionBundle = new(CollectionBundle)
12+
13+
// Option represents a collection option.
14+
type Option interface {
15+
collectionOption()
16+
}
17+
18+
// optionFunc adds the option to the client.
19+
type optionFunc func(*Collection) error
20+
21+
// Collection represents a collection.
22+
type Collection struct {
23+
ReadConcern *readconcern.ReadConcern
24+
WriteConcern *writeconcern.WriteConcern
25+
ReadPreference *readpref.ReadPref
26+
}
27+
28+
// CollectionBundle is a bundle of collection options.
29+
type CollectionBundle struct {
30+
option Option
31+
next *CollectionBundle
32+
}
33+
34+
// CollectionBundle implements Option.
35+
func (*CollectionBundle) collectionOption() {}
36+
37+
// OptionFunc implements Option.
38+
func (optionFunc) collectionOption() {}
39+
40+
// BundleCollection bundles collection options.
41+
func BundleCollection(opts ...Option) *CollectionBundle {
42+
head := collectionBundle
43+
44+
for _, opt := range opts {
45+
newBundle := CollectionBundle{
46+
option: opt,
47+
next: head,
48+
}
49+
50+
head = &newBundle
51+
}
52+
53+
return head
54+
}
55+
56+
// ReadConcern sets the read concern.
57+
func (cb *CollectionBundle) ReadConcern(rc *readconcern.ReadConcern) *CollectionBundle {
58+
return &CollectionBundle{
59+
option: ReadConcern(rc),
60+
next: cb,
61+
}
62+
}
63+
64+
// WriteConcern sets the write concern.
65+
func (cb *CollectionBundle) WriteConcern(wc *writeconcern.WriteConcern) *CollectionBundle {
66+
return &CollectionBundle{
67+
option: WriteConcern(wc),
68+
next: cb,
69+
}
70+
}
71+
72+
// ReadPreference sets the read preference.
73+
func (cb *CollectionBundle) ReadPreference(rp *readpref.ReadPref) *CollectionBundle {
74+
return &CollectionBundle{
75+
option: ReadPreference(rp),
76+
next: cb,
77+
}
78+
}
79+
80+
// String prints a string representation of the bundle for debug purposes
81+
func (cb *CollectionBundle) String() string {
82+
if cb == nil {
83+
return ""
84+
}
85+
86+
debugStr := ""
87+
for head := cb; head != nil && head.option != nil; head = head.next {
88+
switch opt := head.option.(type) {
89+
case *CollectionBundle:
90+
debugStr += opt.String()
91+
case optionFunc:
92+
debugStr += reflect.TypeOf(opt).String() + "\n"
93+
default:
94+
return debugStr + "(error: CollectionOption can only be *CollectionBundle or optionFunc)"
95+
}
96+
}
97+
98+
return debugStr
99+
}
100+
101+
// Unbundle unbundles the options, returning a collection.
102+
func (cb *CollectionBundle) Unbundle() (*Collection, error) {
103+
client := &Collection{}
104+
err := cb.unbundle(client)
105+
if err != nil {
106+
return nil, err
107+
}
108+
109+
return client, nil
110+
}
111+
112+
// Helper that recursively unwraps the bundle.
113+
func (cb *CollectionBundle) unbundle(client *Collection) error {
114+
if cb == nil {
115+
return nil
116+
}
117+
118+
for head := cb; head != nil && head.option != nil; head = head.next {
119+
var err error
120+
switch opt := head.option.(type) {
121+
case *CollectionBundle:
122+
err = opt.unbundle(client) // add all bundle's options to client
123+
case optionFunc:
124+
err = opt(client) // add option to client
125+
default:
126+
return nil
127+
}
128+
if err != nil {
129+
return err
130+
}
131+
}
132+
133+
return nil
134+
135+
}
136+
137+
// ReadConcern sets the read concern.
138+
func ReadConcern(rc *readconcern.ReadConcern) Option {
139+
return optionFunc(
140+
func(c *Collection) error {
141+
if c.ReadConcern == nil {
142+
c.ReadConcern = rc
143+
}
144+
return nil
145+
})
146+
}
147+
148+
// WriteConcern sets the write concern.
149+
func WriteConcern(wc *writeconcern.WriteConcern) Option {
150+
return optionFunc(
151+
func(c *Collection) error {
152+
if c.WriteConcern == nil {
153+
c.WriteConcern = wc
154+
}
155+
return nil
156+
})
157+
}
158+
159+
// ReadPreference sets the read preference.
160+
func ReadPreference(rp *readpref.ReadPref) Option {
161+
return optionFunc(
162+
func(c *Collection) error {
163+
if c.ReadPreference == nil {
164+
c.ReadPreference = rp
165+
}
166+
return nil
167+
})
168+
}

0 commit comments

Comments
 (0)