Skip to content

Commit d1c02c9

Browse files
Divjot Arorarfblue2
authored andcommitted
Add mongo/distinctopt
GODRIVER-272 Change-Id: Ic909c0e9930e822699540ca14d1bd5ba0b6b5bc7
1 parent c8eae34 commit d1c02c9

File tree

5 files changed

+394
-5
lines changed

5 files changed

+394
-5
lines changed

mongo/collection.go

Lines changed: 8 additions & 2 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/distinctopt"
2425
"github.com/mongodb/mongo-go-driver/mongo/findopt"
2526
"github.com/mongodb/mongo-go-driver/mongo/insertopt"
2627
"github.com/mongodb/mongo-go-driver/mongo/replaceopt"
@@ -512,7 +513,7 @@ func (coll *Collection) Count(ctx context.Context, filter interface{},
512513
// *bson.Document. See TransformDocument for the list of valid types for
513514
// filter.
514515
func (coll *Collection) Distinct(ctx context.Context, fieldName string, filter interface{},
515-
opts ...option.DistinctOptioner) ([]interface{}, error) {
516+
opts ...distinctopt.Distinct) ([]interface{}, error) {
516517

517518
if ctx == nil {
518519
ctx = context.Background()
@@ -527,12 +528,17 @@ func (coll *Collection) Distinct(ctx context.Context, fieldName string, filter i
527528
}
528529
}
529530

531+
distinctOpts, err := distinctopt.BundleDistinct(opts...).Unbundle(true)
532+
if err != nil {
533+
return nil, err
534+
}
535+
530536
oldns := coll.namespace()
531537
cmd := command.Distinct{
532538
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
533539
Field: fieldName,
534540
Query: f,
535-
Opts: opts,
541+
Opts: distinctOpts,
536542
ReadPref: coll.readPreference,
537543
}
538544
res, err := dispatch.Distinct(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
@@ -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/distinctopt"
2223
"github.com/mongodb/mongo-go-driver/mongo/findopt"
2324
"github.com/mongodb/mongo-go-driver/mongo/insertopt"
2425
"github.com/mongodb/mongo-go-driver/mongo/replaceopt"
@@ -1211,7 +1212,7 @@ func TestCollection_Distinct_withOption(t *testing.T) {
12111212
coll := createTestCollection(t, nil, nil)
12121213
initCollection(t, coll)
12131214

1214-
results, err := coll.Distinct(context.Background(), "x", nil, Opt.Collation(&option.Collation{Locale: "en_US"}))
1215+
results, err := coll.Distinct(context.Background(), "x", nil, distinctopt.Collation(&option.Collation{Locale: "en_US"}))
12151216
require.Nil(t, err)
12161217
require.Equal(t, results, []interface{}{int32(1), int32(2), int32(3), int32(4), int32(5)})
12171218
}

mongo/crud_spec_test.go

Lines changed: 4 additions & 2 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/distinctopt"
1920
"github.com/mongodb/mongo-go-driver/mongo/findopt"
2021
"github.com/mongodb/mongo-go-driver/mongo/replaceopt"
2122
"github.com/mongodb/mongo-go-driver/mongo/updateopt"
@@ -321,10 +322,11 @@ func distinctTest(t *testing.T, coll *Collection, test *testCase) {
321322
filter = filterArg.(map[string]interface{})
322323
}
323324

324-
var opts []option.DistinctOptioner
325+
var opts []distinctopt.Distinct
325326

326327
if collation, found := test.Operation.Arguments["collation"]; found {
327-
opts = append(opts, Opt.Collation(collationFromMap(collation.(map[string]interface{}))))
328+
mapCollation := collationFromMap(collation.(map[string]interface{}))
329+
opts = append(opts, distinctopt.Collation(mapCollation))
328330
}
329331

330332
actual, err := coll.Distinct(context.Background(), fieldName, filter, opts...)

mongo/distinctopt/distinctopt.go

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

0 commit comments

Comments
 (0)