1
2
3
4
5
6
7 package mongo
8
9 import (
10 "bytes"
11 "context"
12 "errors"
13 "fmt"
14 "strconv"
15
16 "go.mongodb.org/mongo-driver/bson"
17 "go.mongodb.org/mongo-driver/bson/bsontype"
18 "go.mongodb.org/mongo-driver/mongo/description"
19 "go.mongodb.org/mongo-driver/mongo/options"
20 "go.mongodb.org/mongo-driver/mongo/readpref"
21 "go.mongodb.org/mongo-driver/mongo/writeconcern"
22 "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
23 "go.mongodb.org/mongo-driver/x/mongo/driver"
24 "go.mongodb.org/mongo-driver/x/mongo/driver/operation"
25 "go.mongodb.org/mongo-driver/x/mongo/driver/session"
26 )
27
28
29
30 var ErrInvalidIndexValue = errors.New("invalid index value")
31
32
33 var ErrNonStringIndexName = errors.New("index name must be a string")
34
35
36 var ErrMultipleIndexDrop = errors.New("multiple indexes would be dropped")
37
38
39
40 type IndexView struct {
41 coll *Collection
42 }
43
44
45 type IndexModel struct {
46
47
48
49 Keys interface{}
50
51
52 Options *options.IndexOptions
53 }
54
55 func isNamespaceNotFoundError(err error) bool {
56 if de, ok := err.(driver.Error); ok {
57 return de.Code == 26
58 }
59 return false
60 }
61
62
63
64
65
66
67
68 func (iv IndexView) List(ctx context.Context, opts ...*options.ListIndexesOptions) (*Cursor, error) {
69 if ctx == nil {
70 ctx = context.Background()
71 }
72
73 sess := sessionFromContext(ctx)
74 if sess == nil && iv.coll.client.sessionPool != nil {
75 sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
76 }
77
78 err := iv.coll.client.validSession(sess)
79 if err != nil {
80 closeImplicitSession(sess)
81 return nil, err
82 }
83
84 selector := description.CompositeSelector([]description.ServerSelector{
85 description.ReadPrefSelector(readpref.Primary()),
86 description.LatencySelector(iv.coll.client.localThreshold),
87 })
88 selector = makeReadPrefSelector(sess, selector, iv.coll.client.localThreshold)
89
90
91
92 op := operation.NewListIndexes().
93 Session(sess).CommandMonitor(iv.coll.client.monitor).
94 ServerSelector(selector).ClusterClock(iv.coll.client.clock).
95 Database(iv.coll.db.name).Collection(iv.coll.name).
96 Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
97 Timeout(iv.coll.client.timeout)
98
99 cursorOpts := iv.coll.client.createBaseCursorOptions()
100
101 cursorOpts.MarshalValueEncoderFn = newEncoderFn(iv.coll.bsonOpts, iv.coll.registry)
102
103 lio := options.MergeListIndexesOptions(opts...)
104 if lio.BatchSize != nil {
105 op = op.BatchSize(*lio.BatchSize)
106 cursorOpts.BatchSize = *lio.BatchSize
107 }
108 op = op.MaxTime(lio.MaxTime)
109 retry := driver.RetryNone
110 if iv.coll.client.retryReads {
111 retry = driver.RetryOncePerCommand
112 }
113 op.Retry(retry)
114
115 err = op.Execute(ctx)
116 if err != nil {
117
118 closeImplicitSession(sess)
119 if isNamespaceNotFoundError(err) {
120 return newEmptyCursor(), nil
121 }
122
123 return nil, replaceErrors(err)
124 }
125
126 bc, err := op.Result(cursorOpts)
127 if err != nil {
128 closeImplicitSession(sess)
129 return nil, replaceErrors(err)
130 }
131 cursor, err := newCursorWithSession(bc, iv.coll.bsonOpts, iv.coll.registry, sess)
132 return cursor, replaceErrors(err)
133 }
134
135
136 func (iv IndexView) ListSpecifications(ctx context.Context, opts ...*options.ListIndexesOptions) ([]*IndexSpecification, error) {
137 cursor, err := iv.List(ctx, opts...)
138 if err != nil {
139 return nil, err
140 }
141
142 var results []*IndexSpecification
143 err = cursor.All(ctx, &results)
144 if err != nil {
145 return nil, err
146 }
147
148 ns := iv.coll.db.Name() + "." + iv.coll.Name()
149 for _, res := range results {
150
151
152 res.Namespace = ns
153 }
154
155 return results, nil
156 }
157
158
159
160 func (iv IndexView) CreateOne(ctx context.Context, model IndexModel, opts ...*options.CreateIndexesOptions) (string, error) {
161 names, err := iv.CreateMany(ctx, []IndexModel{model}, opts...)
162 if err != nil {
163 return "", err
164 }
165
166 return names[0], nil
167 }
168
169
170
171
172
173
174
175
176
177
178
179 func (iv IndexView) CreateMany(ctx context.Context, models []IndexModel, opts ...*options.CreateIndexesOptions) ([]string, error) {
180 names := make([]string, 0, len(models))
181
182 var indexes bsoncore.Document
183 aidx, indexes := bsoncore.AppendArrayStart(indexes)
184
185 for i, model := range models {
186 if model.Keys == nil {
187 return nil, fmt.Errorf("index model keys cannot be nil")
188 }
189
190 if isUnorderedMap(model.Keys) {
191 return nil, ErrMapForOrderedArgument{"keys"}
192 }
193
194 keys, err := marshal(model.Keys, iv.coll.bsonOpts, iv.coll.registry)
195 if err != nil {
196 return nil, err
197 }
198
199 name, err := getOrGenerateIndexName(keys, model)
200 if err != nil {
201 return nil, err
202 }
203
204 names = append(names, name)
205
206 var iidx int32
207 iidx, indexes = bsoncore.AppendDocumentElementStart(indexes, strconv.Itoa(i))
208 indexes = bsoncore.AppendDocumentElement(indexes, "key", keys)
209
210 if model.Options == nil {
211 model.Options = options.Index()
212 }
213 model.Options.SetName(name)
214
215 optsDoc, err := iv.createOptionsDoc(model.Options)
216 if err != nil {
217 return nil, err
218 }
219
220 indexes = bsoncore.AppendDocument(indexes, optsDoc)
221
222 indexes, err = bsoncore.AppendDocumentEnd(indexes, iidx)
223 if err != nil {
224 return nil, err
225 }
226 }
227
228 indexes, err := bsoncore.AppendArrayEnd(indexes, aidx)
229 if err != nil {
230 return nil, err
231 }
232
233 sess := sessionFromContext(ctx)
234
235 if sess == nil && iv.coll.client.sessionPool != nil {
236 sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
237 defer sess.EndSession()
238 }
239
240 err = iv.coll.client.validSession(sess)
241 if err != nil {
242 return nil, err
243 }
244
245 wc := iv.coll.writeConcern
246 if sess.TransactionRunning() {
247 wc = nil
248 }
249 if !writeconcern.AckWrite(wc) {
250 sess = nil
251 }
252
253 selector := makePinnedSelector(sess, iv.coll.writeSelector)
254
255 option := options.MergeCreateIndexesOptions(opts...)
256
257
258
259
260
261 op := operation.NewCreateIndexes(indexes).
262 Session(sess).WriteConcern(wc).ClusterClock(iv.coll.client.clock).
263 Database(iv.coll.db.name).Collection(iv.coll.name).CommandMonitor(iv.coll.client.monitor).
264 Deployment(iv.coll.client.deployment).ServerSelector(selector).ServerAPI(iv.coll.client.serverAPI).
265 Timeout(iv.coll.client.timeout).MaxTime(option.MaxTime)
266 if option.CommitQuorum != nil {
267 commitQuorum, err := marshalValue(option.CommitQuorum, iv.coll.bsonOpts, iv.coll.registry)
268 if err != nil {
269 return nil, err
270 }
271
272 op.CommitQuorum(commitQuorum)
273 }
274
275 err = op.Execute(ctx)
276 if err != nil {
277 _, err = processWriteError(err)
278 return nil, err
279 }
280
281 return names, nil
282 }
283
284 func (iv IndexView) createOptionsDoc(opts *options.IndexOptions) (bsoncore.Document, error) {
285 optsDoc := bsoncore.Document{}
286 if opts.Background != nil {
287 optsDoc = bsoncore.AppendBooleanElement(optsDoc, "background", *opts.Background)
288 }
289 if opts.ExpireAfterSeconds != nil {
290 optsDoc = bsoncore.AppendInt32Element(optsDoc, "expireAfterSeconds", *opts.ExpireAfterSeconds)
291 }
292 if opts.Name != nil {
293 optsDoc = bsoncore.AppendStringElement(optsDoc, "name", *opts.Name)
294 }
295 if opts.Sparse != nil {
296 optsDoc = bsoncore.AppendBooleanElement(optsDoc, "sparse", *opts.Sparse)
297 }
298 if opts.StorageEngine != nil {
299 doc, err := marshal(opts.StorageEngine, iv.coll.bsonOpts, iv.coll.registry)
300 if err != nil {
301 return nil, err
302 }
303
304 optsDoc = bsoncore.AppendDocumentElement(optsDoc, "storageEngine", doc)
305 }
306 if opts.Unique != nil {
307 optsDoc = bsoncore.AppendBooleanElement(optsDoc, "unique", *opts.Unique)
308 }
309 if opts.Version != nil {
310 optsDoc = bsoncore.AppendInt32Element(optsDoc, "v", *opts.Version)
311 }
312 if opts.DefaultLanguage != nil {
313 optsDoc = bsoncore.AppendStringElement(optsDoc, "default_language", *opts.DefaultLanguage)
314 }
315 if opts.LanguageOverride != nil {
316 optsDoc = bsoncore.AppendStringElement(optsDoc, "language_override", *opts.LanguageOverride)
317 }
318 if opts.TextVersion != nil {
319 optsDoc = bsoncore.AppendInt32Element(optsDoc, "textIndexVersion", *opts.TextVersion)
320 }
321 if opts.Weights != nil {
322 doc, err := marshal(opts.Weights, iv.coll.bsonOpts, iv.coll.registry)
323 if err != nil {
324 return nil, err
325 }
326
327 optsDoc = bsoncore.AppendDocumentElement(optsDoc, "weights", doc)
328 }
329 if opts.SphereVersion != nil {
330 optsDoc = bsoncore.AppendInt32Element(optsDoc, "2dsphereIndexVersion", *opts.SphereVersion)
331 }
332 if opts.Bits != nil {
333 optsDoc = bsoncore.AppendInt32Element(optsDoc, "bits", *opts.Bits)
334 }
335 if opts.Max != nil {
336 optsDoc = bsoncore.AppendDoubleElement(optsDoc, "max", *opts.Max)
337 }
338 if opts.Min != nil {
339 optsDoc = bsoncore.AppendDoubleElement(optsDoc, "min", *opts.Min)
340 }
341 if opts.BucketSize != nil {
342 optsDoc = bsoncore.AppendInt32Element(optsDoc, "bucketSize", *opts.BucketSize)
343 }
344 if opts.PartialFilterExpression != nil {
345 doc, err := marshal(opts.PartialFilterExpression, iv.coll.bsonOpts, iv.coll.registry)
346 if err != nil {
347 return nil, err
348 }
349
350 optsDoc = bsoncore.AppendDocumentElement(optsDoc, "partialFilterExpression", doc)
351 }
352 if opts.Collation != nil {
353 optsDoc = bsoncore.AppendDocumentElement(optsDoc, "collation", bsoncore.Document(opts.Collation.ToDocument()))
354 }
355 if opts.WildcardProjection != nil {
356 doc, err := marshal(opts.WildcardProjection, iv.coll.bsonOpts, iv.coll.registry)
357 if err != nil {
358 return nil, err
359 }
360
361 optsDoc = bsoncore.AppendDocumentElement(optsDoc, "wildcardProjection", doc)
362 }
363 if opts.Hidden != nil {
364 optsDoc = bsoncore.AppendBooleanElement(optsDoc, "hidden", *opts.Hidden)
365 }
366
367 return optsDoc, nil
368 }
369
370 func (iv IndexView) drop(ctx context.Context, name string, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
371 if ctx == nil {
372 ctx = context.Background()
373 }
374
375 sess := sessionFromContext(ctx)
376 if sess == nil && iv.coll.client.sessionPool != nil {
377 sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
378 defer sess.EndSession()
379 }
380
381 err := iv.coll.client.validSession(sess)
382 if err != nil {
383 return nil, err
384 }
385
386 wc := iv.coll.writeConcern
387 if sess.TransactionRunning() {
388 wc = nil
389 }
390 if !writeconcern.AckWrite(wc) {
391 sess = nil
392 }
393
394 selector := makePinnedSelector(sess, iv.coll.writeSelector)
395
396 dio := options.MergeDropIndexesOptions(opts...)
397
398
399
400 op := operation.NewDropIndexes(name).
401 Session(sess).WriteConcern(wc).CommandMonitor(iv.coll.client.monitor).
402 ServerSelector(selector).ClusterClock(iv.coll.client.clock).
403 Database(iv.coll.db.name).Collection(iv.coll.name).
404 Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
405 Timeout(iv.coll.client.timeout).MaxTime(dio.MaxTime)
406
407 err = op.Execute(ctx)
408 if err != nil {
409 return nil, replaceErrors(err)
410 }
411
412
413 ridx, res := bsoncore.AppendDocumentStart(nil)
414 res = bsoncore.AppendInt32Element(res, "nIndexesWas", op.Result().NIndexesWas)
415 res, _ = bsoncore.AppendDocumentEnd(res, ridx)
416 return res, nil
417 }
418
419
420
421
422
423
424
425
426
427
428
429
430 func (iv IndexView) DropOne(ctx context.Context, name string, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
431 if name == "*" {
432 return nil, ErrMultipleIndexDrop
433 }
434
435 return iv.drop(ctx, name, opts...)
436 }
437
438
439
440
441
442
443
444
445
446 func (iv IndexView) DropAll(ctx context.Context, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
447 return iv.drop(ctx, "*", opts...)
448 }
449
450 func getOrGenerateIndexName(keySpecDocument bsoncore.Document, model IndexModel) (string, error) {
451 if model.Options != nil && model.Options.Name != nil {
452 return *model.Options.Name, nil
453 }
454
455 name := bytes.NewBufferString("")
456 first := true
457
458 elems, err := keySpecDocument.Elements()
459 if err != nil {
460 return "", err
461 }
462 for _, elem := range elems {
463 if !first {
464 _, err := name.WriteRune('_')
465 if err != nil {
466 return "", err
467 }
468 }
469
470 _, err := name.WriteString(elem.Key())
471 if err != nil {
472 return "", err
473 }
474
475 _, err = name.WriteRune('_')
476 if err != nil {
477 return "", err
478 }
479
480 var value string
481
482 bsonValue := elem.Value()
483 switch bsonValue.Type {
484 case bsontype.Int32:
485 value = fmt.Sprintf("%d", bsonValue.Int32())
486 case bsontype.Int64:
487 value = fmt.Sprintf("%d", bsonValue.Int64())
488 case bsontype.String:
489 value = bsonValue.StringValue()
490 default:
491 return "", ErrInvalidIndexValue
492 }
493
494 _, err = name.WriteString(value)
495 if err != nil {
496 return "", err
497 }
498
499 first = false
500 }
501
502 return name.String(), nil
503 }
504
View as plain text