Skip to content

Commit 882b279

Browse files
Divjot AroraRoland Fong
authored andcommitted
Add mongo/runcmdopt
GODRIVER-272 Change-Id: Idd6ef1f97c55e0b4f270c42695c1cebb93a401a1
1 parent c9fe824 commit 882b279

File tree

3 files changed

+201
-2
lines changed

3 files changed

+201
-2
lines changed

mongo/database.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/mongodb/mongo-go-driver/core/writeconcern"
2020
"github.com/mongodb/mongo-go-driver/mongo/collectionopt"
2121
"github.com/mongodb/mongo-go-driver/mongo/dbopt"
22+
"github.com/mongodb/mongo-go-driver/mongo/runcmdopt"
2223
)
2324

2425
// Database performs operations on a given database.
@@ -88,16 +89,25 @@ func (db *Database) Collection(name string, opts ...collectionopt.Option) *Colle
8889

8990
// RunCommand runs a command on the database. A user can supply a custom
9091
// context to this method, or nil to default to context.Background().
91-
func (db *Database) RunCommand(ctx context.Context, runCommand interface{}) (bson.Reader, error) {
92+
func (db *Database) RunCommand(ctx context.Context, runCommand interface{}, opts ...runcmdopt.Option) (bson.Reader, error) {
9293

9394
if ctx == nil {
9495
ctx = context.Background()
9596
}
9697

98+
runCmd, err := runcmdopt.BundleRunCmd(opts...).Unbundle()
99+
if err != nil {
100+
return nil, err
101+
}
102+
rp := runCmd.ReadPreference
103+
if rp == nil {
104+
rp = db.readPreference // inherit from db if nothing specified in options
105+
}
106+
97107
cmd := command.Command{
98108
DB: db.Name(),
99109
Command: runCommand,
100-
ReadPref: db.readPreference,
110+
ReadPref: rp,
101111
}
102112
return dispatch.Command(ctx, cmd, db.client.topology, db.writeSelector)
103113
}

mongo/runcmdopt/runcmd_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package runcmdopt
2+
3+
import (
4+
"testing"
5+
6+
"github.com/mongodb/mongo-go-driver/core/readpref"
7+
"github.com/mongodb/mongo-go-driver/internal/testutil/helpers"
8+
)
9+
10+
var rpPrimary = readpref.Primary()
11+
var rpSeconadary = readpref.Secondary()
12+
13+
func createNestedBundle1(t *testing.T) *RunCmdBundle {
14+
nested := BundleRunCmd(ReadPreference(rpPrimary))
15+
testhelpers.RequireNotNil(t, nested, "nested bundle was nil")
16+
17+
outer := BundleRunCmd(ReadPreference(rpSeconadary), nested)
18+
testhelpers.RequireNotNil(t, nested, "nested bundle was nil")
19+
20+
return outer
21+
}
22+
23+
func TestRunCmdOpt(t *testing.T) {
24+
nilBundle := BundleRunCmd()
25+
var nilRc = &RunCmd{}
26+
27+
var bundle1 *RunCmdBundle
28+
bundle1 = bundle1.ReadPreference(rpSeconadary)
29+
testhelpers.RequireNotNil(t, bundle1, "created bundle was nil")
30+
bundle1Rc := &RunCmd{
31+
ReadPreference: rpSeconadary,
32+
}
33+
34+
bundle2 := BundleRunCmd(ReadPreference(rpPrimary))
35+
testhelpers.RequireNotNil(t, bundle2, "created bundle was nil")
36+
bundle2Rc := &RunCmd{
37+
ReadPreference: rpPrimary,
38+
}
39+
40+
nested1 := createNestedBundle1(t)
41+
nested1Rc := &RunCmd{
42+
ReadPreference: rpPrimary,
43+
}
44+
45+
t.Run("Unbundle", func(t *testing.T) {
46+
var cases = []struct {
47+
name string
48+
bundle *RunCmdBundle
49+
rc *RunCmd
50+
}{
51+
{"NilBundle", nilBundle, nilRc},
52+
{"Bundle1", bundle1, bundle1Rc},
53+
{"Bundle2", bundle2, bundle2Rc},
54+
{"Nested1", nested1, nested1Rc},
55+
}
56+
57+
for _, tc := range cases {
58+
t.Run(tc.name, func(t *testing.T) {
59+
rc, err := tc.bundle.Unbundle()
60+
testhelpers.RequireNil(t, err, "err unbundling rc: %s", err)
61+
62+
switch {
63+
case rc.ReadPreference != tc.rc.ReadPreference:
64+
t.Errorf("read preferences don't match")
65+
}
66+
})
67+
}
68+
})
69+
}

mongo/runcmdopt/runcmdopt.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package runcmdopt
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/mongodb/mongo-go-driver/core/readpref"
7+
)
8+
9+
var runCmdBundle = new(RunCmdBundle)
10+
11+
// Option represents a RunCommand option.
12+
type Option interface {
13+
runCmdOption()
14+
}
15+
16+
// optionFunc adds the option to the RunCmd instance.
17+
type optionFunc func(*RunCmd) error
18+
19+
// RunCmd represents a run command.
20+
type RunCmd struct {
21+
ReadPreference *readpref.ReadPref
22+
}
23+
24+
// RunCmdBundle is a bundle of RunCommand options.
25+
type RunCmdBundle struct {
26+
option Option
27+
next *RunCmdBundle
28+
}
29+
30+
func (*RunCmdBundle) runCmdOption() {}
31+
32+
func (optionFunc) runCmdOption() {}
33+
34+
// BundleRunCmd bundles RunCommand options
35+
func BundleRunCmd(opts ...Option) *RunCmdBundle {
36+
head := runCmdBundle
37+
38+
for _, opt := range opts {
39+
newBundle := RunCmdBundle{
40+
option: opt,
41+
next: head,
42+
}
43+
head = &newBundle
44+
}
45+
46+
return head
47+
}
48+
49+
// ReadPreference sets the read preference.
50+
func (rcb *RunCmdBundle) ReadPreference(rp *readpref.ReadPref) *RunCmdBundle {
51+
return &RunCmdBundle{
52+
option: ReadPreference(rp),
53+
next: rcb,
54+
}
55+
}
56+
57+
// Unbundle unbundles the options, returning a RunCmd instance.
58+
func (rcb *RunCmdBundle) Unbundle() (*RunCmd, error) {
59+
database := &RunCmd{}
60+
err := rcb.unbundle(database)
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
return database, nil
66+
}
67+
68+
// Helper that recursively unwraps the bundle.
69+
func (rcb *RunCmdBundle) unbundle(database *RunCmd) error {
70+
if rcb == nil {
71+
return nil
72+
}
73+
74+
for head := rcb; head != nil && head.option != nil; head = head.next {
75+
var err error
76+
switch opt := head.option.(type) {
77+
case *RunCmdBundle:
78+
err = opt.unbundle(database) // add all bundle's options to database
79+
case optionFunc:
80+
err = opt(database) // add option to database
81+
default:
82+
return nil
83+
}
84+
if err != nil {
85+
return err
86+
}
87+
}
88+
89+
return nil
90+
}
91+
92+
// String implements the Stringer interface
93+
func (rcb *RunCmdBundle) String() string {
94+
if rcb == nil {
95+
return ""
96+
}
97+
98+
str := ""
99+
for head := rcb; head != nil && head.option != nil; head = head.next {
100+
switch opt := head.option.(type) {
101+
case *RunCmdBundle:
102+
str += opt.String()
103+
case optionFunc:
104+
str += reflect.TypeOf(opt).String() + "\n"
105+
}
106+
}
107+
108+
return str
109+
}
110+
111+
// ReadPreference sets the read preference.
112+
func ReadPreference(rp *readpref.ReadPref) Option {
113+
return optionFunc(
114+
func(rc *RunCmd) error {
115+
if rc.ReadPreference == nil {
116+
rc.ReadPreference = rp
117+
}
118+
return nil
119+
})
120+
}

0 commit comments

Comments
 (0)