blob: 1ff8d4969f753fe36acc1b7ed0462f600e7bdb0e [file] [log] [blame]
Don Newton379ae252019-04-01 12:17:06 -04001// Copyright (C) MongoDB, Inc. 2017-present.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6
7package mongo
8
9import (
10 "bytes"
11 "context"
12 "errors"
13 "fmt"
14
15 "github.com/mongodb/mongo-go-driver/bson"
16 "github.com/mongodb/mongo-go-driver/bson/bsoncodec"
17 "github.com/mongodb/mongo-go-driver/bson/bsontype"
18 "github.com/mongodb/mongo-go-driver/mongo/options"
19 "github.com/mongodb/mongo-go-driver/mongo/readpref"
20 "github.com/mongodb/mongo-go-driver/x/bsonx"
21 "github.com/mongodb/mongo-go-driver/x/mongo/driver"
22 "github.com/mongodb/mongo-go-driver/x/network/command"
23 "github.com/mongodb/mongo-go-driver/x/network/description"
24)
25
26// ErrInvalidIndexValue indicates that the index Keys document has a value that isn't either a number or a string.
27var ErrInvalidIndexValue = errors.New("invalid index value")
28
29// ErrNonStringIndexName indicates that the index name specified in the options is not a string.
30var ErrNonStringIndexName = errors.New("index name must be a string")
31
32// ErrMultipleIndexDrop indicates that multiple indexes would be dropped from a call to IndexView.DropOne.
33var ErrMultipleIndexDrop = errors.New("multiple indexes would be dropped")
34
35// IndexView is used to create, drop, and list indexes on a given collection.
36type IndexView struct {
37 coll *Collection
38}
39
40// IndexModel contains information about an index.
41type IndexModel struct {
42 Keys interface{}
43 Options *options.IndexOptions
44}
45
46// List returns a cursor iterating over all the indexes in the collection.
47func (iv IndexView) List(ctx context.Context, opts ...*options.ListIndexesOptions) (*Cursor, error) {
48 sess := sessionFromContext(ctx)
49
50 err := iv.coll.client.ValidSession(sess)
51 if err != nil {
52 return nil, err
53 }
54
55 listCmd := command.ListIndexes{
56 NS: iv.coll.namespace(),
57 Session: sess,
58 Clock: iv.coll.client.clock,
59 }
60
61 readSelector := description.CompositeSelector([]description.ServerSelector{
62 description.ReadPrefSelector(readpref.Primary()),
63 description.LatencySelector(iv.coll.client.localThreshold),
64 })
65 batchCursor, err := driver.ListIndexes(
66 ctx, listCmd,
67 iv.coll.client.topology,
68 readSelector,
69 iv.coll.client.id,
70 iv.coll.client.topology.SessionPool,
71 opts...,
72 )
73 if err != nil {
74 if err == command.ErrEmptyCursor {
75 return newEmptyCursor(), nil
76 }
77 return nil, replaceTopologyErr(err)
78 }
79
80 cursor, err := newCursor(batchCursor, iv.coll.registry)
81 return cursor, replaceTopologyErr(err)
82}
83
84// CreateOne creates a single index in the collection specified by the model.
85func (iv IndexView) CreateOne(ctx context.Context, model IndexModel, opts ...*options.CreateIndexesOptions) (string, error) {
86 names, err := iv.CreateMany(ctx, []IndexModel{model}, opts...)
87 if err != nil {
88 return "", err
89 }
90
91 return names[0], nil
92}
93
94// CreateMany creates multiple indexes in the collection specified by the models. The names of the
95// creates indexes are returned.
96func (iv IndexView) CreateMany(ctx context.Context, models []IndexModel, opts ...*options.CreateIndexesOptions) ([]string, error) {
97 names := make([]string, 0, len(models))
98 indexes := bsonx.Arr{}
99
100 for _, model := range models {
101 if model.Keys == nil {
102 return nil, fmt.Errorf("index model keys cannot be nil")
103 }
104
105 name, err := getOrGenerateIndexName(iv.coll.registry, model)
106 if err != nil {
107 return nil, err
108 }
109
110 names = append(names, name)
111
112 keys, err := transformDocument(iv.coll.registry, model.Keys)
113 if err != nil {
114 return nil, err
115 }
116 index := bsonx.Doc{{"key", bsonx.Document(keys)}}
117 if model.Options != nil {
118 optsDoc, err := iv.createOptionsDoc(model.Options)
119 if err != nil {
120 return nil, err
121 }
122
123 index = append(index, optsDoc...)
124 }
125 index = index.Set("name", bsonx.String(name))
126
127 indexes = append(indexes, bsonx.Document(index))
128 }
129
130 sess := sessionFromContext(ctx)
131
132 err := iv.coll.client.ValidSession(sess)
133 if err != nil {
134 return nil, err
135 }
136
137 cmd := command.CreateIndexes{
138 NS: iv.coll.namespace(),
139 Indexes: indexes,
140 Session: sess,
141 Clock: iv.coll.client.clock,
142 }
143
144 _, err = driver.CreateIndexes(
145 ctx, cmd,
146 iv.coll.client.topology,
147 iv.coll.writeSelector,
148 iv.coll.client.id,
149 iv.coll.client.topology.SessionPool,
150 opts...,
151 )
152 if err != nil {
153 return nil, err
154 }
155
156 return names, nil
157}
158
159func (iv IndexView) createOptionsDoc(opts *options.IndexOptions) (bsonx.Doc, error) {
160 optsDoc := bsonx.Doc{}
161 if opts.Background != nil {
162 optsDoc = append(optsDoc, bsonx.Elem{"background", bsonx.Boolean(*opts.Background)})
163 }
164 if opts.ExpireAfterSeconds != nil {
165 optsDoc = append(optsDoc, bsonx.Elem{"expireAfterSeconds", bsonx.Int32(*opts.ExpireAfterSeconds)})
166 }
167 if opts.Name != nil {
168 optsDoc = append(optsDoc, bsonx.Elem{"name", bsonx.String(*opts.Name)})
169 }
170 if opts.Sparse != nil {
171 optsDoc = append(optsDoc, bsonx.Elem{"sparse", bsonx.Boolean(*opts.Sparse)})
172 }
173 if opts.StorageEngine != nil {
174 doc, err := transformDocument(iv.coll.registry, opts.StorageEngine)
175 if err != nil {
176 return nil, err
177 }
178
179 optsDoc = append(optsDoc, bsonx.Elem{"storageEngine", bsonx.Document(doc)})
180 }
181 if opts.Unique != nil {
182 optsDoc = append(optsDoc, bsonx.Elem{"unique", bsonx.Boolean(*opts.Unique)})
183 }
184 if opts.Version != nil {
185 optsDoc = append(optsDoc, bsonx.Elem{"v", bsonx.Int32(*opts.Version)})
186 }
187 if opts.DefaultLanguage != nil {
188 optsDoc = append(optsDoc, bsonx.Elem{"default_language", bsonx.String(*opts.DefaultLanguage)})
189 }
190 if opts.LanguageOverride != nil {
191 optsDoc = append(optsDoc, bsonx.Elem{"language_override", bsonx.String(*opts.LanguageOverride)})
192 }
193 if opts.TextVersion != nil {
194 optsDoc = append(optsDoc, bsonx.Elem{"textIndexVersion", bsonx.Int32(*opts.TextVersion)})
195 }
196 if opts.Weights != nil {
197 weightsDoc, err := transformDocument(iv.coll.registry, opts.Weights)
198 if err != nil {
199 return nil, err
200 }
201
202 optsDoc = append(optsDoc, bsonx.Elem{"weights", bsonx.Document(weightsDoc)})
203 }
204 if opts.SphereVersion != nil {
205 optsDoc = append(optsDoc, bsonx.Elem{"2dsphereIndexVersion", bsonx.Int32(*opts.SphereVersion)})
206 }
207 if opts.Bits != nil {
208 optsDoc = append(optsDoc, bsonx.Elem{"bits", bsonx.Int32(*opts.Bits)})
209 }
210 if opts.Max != nil {
211 optsDoc = append(optsDoc, bsonx.Elem{"max", bsonx.Double(*opts.Max)})
212 }
213 if opts.Min != nil {
214 optsDoc = append(optsDoc, bsonx.Elem{"min", bsonx.Double(*opts.Min)})
215 }
216 if opts.BucketSize != nil {
217 optsDoc = append(optsDoc, bsonx.Elem{"bucketSize", bsonx.Int32(*opts.BucketSize)})
218 }
219 if opts.PartialFilterExpression != nil {
220 doc, err := transformDocument(iv.coll.registry, opts.PartialFilterExpression)
221 if err != nil {
222 return nil, err
223 }
224
225 optsDoc = append(optsDoc, bsonx.Elem{"partialFilterExpression", bsonx.Document(doc)})
226 }
227 if opts.Collation != nil {
228 doc := opts.Collation.ToDocument()
229 optsDoc = append(optsDoc, bsonx.Elem{"collation", bsonx.Document(doc)})
230 }
231
232 return optsDoc, nil
233}
234
235// DropOne drops the index with the given name from the collection.
236func (iv IndexView) DropOne(ctx context.Context, name string, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
237 if name == "*" {
238 return nil, ErrMultipleIndexDrop
239 }
240
241 sess := sessionFromContext(ctx)
242
243 err := iv.coll.client.ValidSession(sess)
244 if err != nil {
245 return nil, err
246 }
247
248 cmd := command.DropIndexes{
249 NS: iv.coll.namespace(),
250 Index: name,
251 Session: sess,
252 Clock: iv.coll.client.clock,
253 }
254
255 return driver.DropIndexes(
256 ctx, cmd,
257 iv.coll.client.topology,
258 iv.coll.writeSelector,
259 iv.coll.client.id,
260 iv.coll.client.topology.SessionPool,
261 opts...,
262 )
263}
264
265// DropAll drops all indexes in the collection.
266func (iv IndexView) DropAll(ctx context.Context, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
267 sess := sessionFromContext(ctx)
268
269 err := iv.coll.client.ValidSession(sess)
270 if err != nil {
271 return nil, err
272 }
273
274 cmd := command.DropIndexes{
275 NS: iv.coll.namespace(),
276 Index: "*",
277 Session: sess,
278 Clock: iv.coll.client.clock,
279 }
280
281 return driver.DropIndexes(
282 ctx, cmd,
283 iv.coll.client.topology,
284 iv.coll.writeSelector,
285 iv.coll.client.id,
286 iv.coll.client.topology.SessionPool,
287 opts...,
288 )
289}
290
291func getOrGenerateIndexName(registry *bsoncodec.Registry, model IndexModel) (string, error) {
292 if model.Options != nil && model.Options.Name != nil {
293 return *model.Options.Name, nil
294 }
295
296 name := bytes.NewBufferString("")
297 first := true
298
299 keys, err := transformDocument(registry, model.Keys)
300 if err != nil {
301 return "", err
302 }
303 for _, elem := range keys {
304 if !first {
305 _, err := name.WriteRune('_')
306 if err != nil {
307 return "", err
308 }
309 }
310
311 _, err := name.WriteString(elem.Key)
312 if err != nil {
313 return "", err
314 }
315
316 _, err = name.WriteRune('_')
317 if err != nil {
318 return "", err
319 }
320
321 var value string
322
323 switch elem.Value.Type() {
324 case bsontype.Int32:
325 value = fmt.Sprintf("%d", elem.Value.Int32())
326 case bsontype.Int64:
327 value = fmt.Sprintf("%d", elem.Value.Int64())
328 case bsontype.String:
329 value = elem.Value.StringValue()
330 default:
331 return "", ErrInvalidIndexValue
332 }
333
334 _, err = name.WriteString(value)
335 if err != nil {
336 return "", err
337 }
338
339 first = false
340 }
341
342 return name.String(), nil
343}