Skip to content

Commit c8eae34

Browse files
Divjot Arorarfblue2
authored andcommitted
Add mongo/countopt
GODRIVER-272 Change-Id: Iee3633f16e540fd0a97260ada6575dc47c0049b4
1 parent 2365c36 commit c8eae34

File tree

5 files changed

+517
-7
lines changed

5 files changed

+517
-7
lines changed

mongo/collection.go

Lines changed: 8 additions & 2 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/countopt"
2324
"github.com/mongodb/mongo-go-driver/mongo/findopt"
2425
"github.com/mongodb/mongo-go-driver/mongo/insertopt"
2526
"github.com/mongodb/mongo-go-driver/mongo/replaceopt"
@@ -477,7 +478,7 @@ func (coll *Collection) Aggregate(ctx context.Context, pipeline interface{},
477478
// *bson.Document. See TransformDocument for the list of valid types for
478479
// filter.
479480
func (coll *Collection) Count(ctx context.Context, filter interface{},
480-
opts ...option.CountOptioner) (int64, error) {
481+
opts ...countopt.Count) (int64, error) {
481482

482483
if ctx == nil {
483484
ctx = context.Background()
@@ -488,11 +489,16 @@ func (coll *Collection) Count(ctx context.Context, filter interface{},
488489
return 0, err
489490
}
490491

492+
countOpts, err := countopt.BundleCount(opts...).Unbundle(true)
493+
if err != nil {
494+
return 0, err
495+
}
496+
491497
oldns := coll.namespace()
492498
cmd := command.Count{
493499
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
494500
Query: f,
495-
Opts: opts,
501+
Opts: countOpts,
496502
ReadPref: coll.readPreference,
497503
}
498504
return dispatch.Count(ctx, cmd, coll.client.topology, coll.readSelector, coll.readConcern)

mongo/collection_internal_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/mongodb/mongo-go-driver/core/writeconcern"
1919
"github.com/mongodb/mongo-go-driver/internal/testutil"
2020
"github.com/mongodb/mongo-go-driver/mongo/aggregateopt"
21+
"github.com/mongodb/mongo-go-driver/mongo/countopt"
2122
"github.com/mongodb/mongo-go-driver/mongo/findopt"
2223
"github.com/mongodb/mongo-go-driver/mongo/insertopt"
2324
"github.com/mongodb/mongo-go-driver/mongo/replaceopt"
@@ -1162,7 +1163,7 @@ func TestCollection_Count_withOption(t *testing.T) {
11621163
coll := createTestCollection(t, nil, nil)
11631164
initCollection(t, coll)
11641165

1165-
count, err := coll.Count(context.Background(), nil, Opt.Limit(3))
1166+
count, err := coll.Count(context.Background(), nil, countopt.Limit(3))
11661167
require.Nil(t, err)
11671168
require.Equal(t, count, int64(3))
11681169
}

mongo/countopt/countopt.go

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
package countopt
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/mongodb/mongo-go-driver/core/option"
7+
"github.com/mongodb/mongo-go-driver/core/readconcern"
8+
)
9+
10+
var countBundle = new(CountBundle)
11+
12+
// Count is options for the count() function
13+
type Count interface {
14+
count()
15+
ConvertOption() option.CountOptioner
16+
}
17+
18+
// CountBundle is a bundle of Count options
19+
type CountBundle struct {
20+
option Count
21+
next *CountBundle
22+
}
23+
24+
// Implement the Count interface
25+
func (cb *CountBundle) count() {}
26+
27+
// ConvertOption implements the Count interface
28+
func (cb *CountBundle) ConvertOption() option.CountOptioner {
29+
return nil
30+
}
31+
32+
// BundleCount bundles Count options
33+
func BundleCount(opts ...Count) *CountBundle {
34+
head := countBundle
35+
36+
for _, opt := range opts {
37+
newBundle := CountBundle{
38+
option: opt,
39+
next: head,
40+
}
41+
42+
head = &newBundle
43+
}
44+
45+
return head
46+
}
47+
48+
// Collation specifies a collation.
49+
func (cb *CountBundle) Collation(c *option.Collation) *CountBundle {
50+
bundle := &CountBundle{
51+
option: OptCollation{
52+
Collation: c,
53+
},
54+
next: cb,
55+
}
56+
57+
return bundle
58+
}
59+
60+
// Limit adds an option to limit the maximum number of documents to count.
61+
func (cb *CountBundle) Limit(i int32) *CountBundle {
62+
bundle := &CountBundle{
63+
option: OptLimit(i),
64+
next: cb,
65+
}
66+
67+
return bundle
68+
}
69+
70+
// Skip adds an option to specify the number of documents to skip before counting.
71+
func (cb *CountBundle) Skip(i int32) *CountBundle {
72+
bundle := &CountBundle{
73+
option: OptSkip(i),
74+
next: cb,
75+
}
76+
77+
return bundle
78+
}
79+
80+
// Hint adds an option to specify the index to use.
81+
func (cb *CountBundle) Hint(hint interface{}) *CountBundle {
82+
bundle := &CountBundle{
83+
option: OptHint{hint},
84+
next: cb,
85+
}
86+
87+
return bundle
88+
}
89+
90+
// MaxTimeMs adds an option to specify the maximum amount of time to allow the operation to run.
91+
func (cb *CountBundle) MaxTimeMs(i int32) *CountBundle {
92+
bundle := &CountBundle{
93+
option: OptMaxTimeMs(i),
94+
next: cb,
95+
}
96+
97+
return bundle
98+
}
99+
100+
// ReadConcern adds an option to specify a read concern.
101+
func (cb *CountBundle) ReadConcern(rc *readconcern.ReadConcern) *CountBundle {
102+
bundle := &CountBundle{
103+
option: OptReadConcern{rc},
104+
next: cb,
105+
}
106+
107+
return bundle
108+
}
109+
110+
// Unbundle transforms a bundle into a slice of options, optionally deduplicating.
111+
func (cb *CountBundle) Unbundle(deduplicate bool) ([]option.CountOptioner, error) {
112+
options, err := cb.unbundle()
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
if !deduplicate {
118+
return options, nil
119+
}
120+
121+
// iterate backwards and make dedup slice
122+
optionsSet := make(map[reflect.Type]struct{})
123+
124+
for i := len(options) - 1; i >= 0; i-- {
125+
currOption := options[i]
126+
optionType := reflect.TypeOf(currOption)
127+
128+
if _, ok := optionsSet[optionType]; ok {
129+
// option already found
130+
options = append(options[:i], options[i+1:]...)
131+
continue
132+
}
133+
134+
optionsSet[optionType] = struct{}{}
135+
}
136+
137+
return options, nil
138+
}
139+
140+
// Calculates the total length of a bundle, accounting for nested bundles.
141+
func (cb *CountBundle) bundleLength() int {
142+
if cb == nil {
143+
return 0
144+
}
145+
146+
bundleLen := 0
147+
for ; cb != nil && cb.option != nil; cb = cb.next {
148+
if converted, ok := cb.option.(*CountBundle); ok {
149+
// nested bundle
150+
bundleLen += converted.bundleLength()
151+
continue
152+
}
153+
154+
bundleLen++
155+
}
156+
157+
return bundleLen
158+
}
159+
160+
// Helper that recursively unwraps bundle into slice of options
161+
func (cb *CountBundle) unbundle() ([]option.CountOptioner, error) {
162+
if cb == nil {
163+
return nil, nil
164+
}
165+
166+
listLen := cb.bundleLength()
167+
168+
options := make([]option.CountOptioner, listLen)
169+
index := listLen - 1
170+
171+
for listHead := cb; listHead != nil && listHead.option != nil; listHead = listHead.next {
172+
// if the current option is a nested bundle, Unbundle it and add its options to the current array
173+
if converted, ok := listHead.option.(*CountBundle); ok {
174+
nestedOptions, err := converted.unbundle()
175+
if err != nil {
176+
return nil, err
177+
}
178+
179+
// where to start inserting nested options
180+
startIndex := index - len(nestedOptions) + 1
181+
182+
// add nested options in order
183+
for _, nestedOp := range nestedOptions {
184+
options[startIndex] = nestedOp
185+
startIndex++
186+
}
187+
index -= len(nestedOptions)
188+
continue
189+
}
190+
191+
options[index] = listHead.option.ConvertOption()
192+
index--
193+
}
194+
195+
return options, nil
196+
}
197+
198+
// String implements the Stringer interface
199+
func (cb *CountBundle) String() string {
200+
if cb == nil {
201+
return ""
202+
}
203+
204+
str := ""
205+
for head := cb; head != nil && head.option != nil; head = head.next {
206+
if converted, ok := head.option.(*CountBundle); ok {
207+
str += converted.String()
208+
continue
209+
}
210+
211+
str += head.option.ConvertOption().String()
212+
}
213+
214+
return str
215+
}
216+
217+
// Collation specifies a Collation.
218+
func Collation(collation *option.Collation) OptCollation {
219+
return OptCollation{collation}
220+
}
221+
222+
// Limit limits the maximum number of documents to count.
223+
func Limit(i int64) OptLimit {
224+
return OptLimit(i)
225+
}
226+
227+
// Skip specifies the number of documents to skip before counting.
228+
func Skip(i int64) OptSkip {
229+
return OptSkip(i)
230+
}
231+
232+
// Hint specifies the index to use.
233+
func Hint(hint interface{}) OptHint {
234+
return OptHint{hint}
235+
}
236+
237+
// MaxTimeMs specifies the maximum amount of time to allow the operation to run.
238+
func MaxTimeMs(i int32) OptMaxTimeMs {
239+
return OptMaxTimeMs(i)
240+
}
241+
242+
// ReadConcern specifies a read concern.
243+
func ReadConcern(rc *readconcern.ReadConcern) OptReadConcern {
244+
return OptReadConcern{
245+
ReadConcern: rc,
246+
}
247+
}
248+
249+
// OptCollation specifies a collation.
250+
type OptCollation option.OptCollation
251+
252+
func (OptCollation) count() {}
253+
254+
// ConvertOption implements the Count interface.
255+
func (opt OptCollation) ConvertOption() option.CountOptioner {
256+
return option.OptCollation(opt)
257+
}
258+
259+
// OptLimit limits the maximum number of documents to count.
260+
type OptLimit option.OptLimit
261+
262+
// ConvertOption implements the Count interface.
263+
func (opt OptLimit) ConvertOption() option.CountOptioner {
264+
return option.OptLimit(opt)
265+
}
266+
267+
func (OptLimit) count() {}
268+
269+
// OptSkip specifies the number of documents to skip before counting.
270+
type OptSkip option.OptSkip
271+
272+
// ConvertOption implements the Count interface.
273+
func (opt OptSkip) ConvertOption() option.CountOptioner {
274+
return option.OptSkip(opt)
275+
}
276+
277+
func (OptSkip) count() {}
278+
279+
// OptHint specifies the index to use.
280+
type OptHint option.OptHint
281+
282+
// ConvertOption implements the Count interface.
283+
func (opt OptHint) ConvertOption() option.CountOptioner {
284+
return option.OptHint(opt)
285+
}
286+
287+
func (OptHint) count() {}
288+
289+
// OptMaxTimeMs specifies the maximum amount of time to allow the operation to run.
290+
type OptMaxTimeMs option.OptMaxTime
291+
292+
// ConvertOption implements the Count interface.
293+
func (opt OptMaxTimeMs) ConvertOption() option.CountOptioner {
294+
return option.OptMaxTime(opt)
295+
}
296+
297+
func (OptMaxTimeMs) count() {}
298+
299+
// OptReadConcern specifies a read concern.
300+
type OptReadConcern option.OptReadConcern
301+
302+
// ConvertOption implements the Count interface.
303+
func (opt OptReadConcern) ConvertOption() option.CountOptioner {
304+
return option.OptReadConcern(opt)
305+
}
306+
307+
func (OptReadConcern) count() {}

0 commit comments

Comments
 (0)