| // Copyright (C) MongoDB, Inc. 2017-present. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may |
| // not use this file except in compliance with the License. You may obtain |
| // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 |
| |
| package mongo |
| |
| import ( |
| "bytes" |
| "context" |
| "errors" |
| "fmt" |
| |
| "github.com/mongodb/mongo-go-driver/bson" |
| "github.com/mongodb/mongo-go-driver/bson/bsoncodec" |
| "github.com/mongodb/mongo-go-driver/bson/bsontype" |
| "github.com/mongodb/mongo-go-driver/mongo/options" |
| "github.com/mongodb/mongo-go-driver/mongo/readpref" |
| "github.com/mongodb/mongo-go-driver/x/bsonx" |
| "github.com/mongodb/mongo-go-driver/x/mongo/driver" |
| "github.com/mongodb/mongo-go-driver/x/network/command" |
| "github.com/mongodb/mongo-go-driver/x/network/description" |
| ) |
| |
| // ErrInvalidIndexValue indicates that the index Keys document has a value that isn't either a number or a string. |
| var ErrInvalidIndexValue = errors.New("invalid index value") |
| |
| // ErrNonStringIndexName indicates that the index name specified in the options is not a string. |
| var ErrNonStringIndexName = errors.New("index name must be a string") |
| |
| // ErrMultipleIndexDrop indicates that multiple indexes would be dropped from a call to IndexView.DropOne. |
| var ErrMultipleIndexDrop = errors.New("multiple indexes would be dropped") |
| |
| // IndexView is used to create, drop, and list indexes on a given collection. |
| type IndexView struct { |
| coll *Collection |
| } |
| |
| // IndexModel contains information about an index. |
| type IndexModel struct { |
| Keys interface{} |
| Options *options.IndexOptions |
| } |
| |
| // List returns a cursor iterating over all the indexes in the collection. |
| func (iv IndexView) List(ctx context.Context, opts ...*options.ListIndexesOptions) (*Cursor, error) { |
| sess := sessionFromContext(ctx) |
| |
| err := iv.coll.client.ValidSession(sess) |
| if err != nil { |
| return nil, err |
| } |
| |
| listCmd := command.ListIndexes{ |
| NS: iv.coll.namespace(), |
| Session: sess, |
| Clock: iv.coll.client.clock, |
| } |
| |
| readSelector := description.CompositeSelector([]description.ServerSelector{ |
| description.ReadPrefSelector(readpref.Primary()), |
| description.LatencySelector(iv.coll.client.localThreshold), |
| }) |
| batchCursor, err := driver.ListIndexes( |
| ctx, listCmd, |
| iv.coll.client.topology, |
| readSelector, |
| iv.coll.client.id, |
| iv.coll.client.topology.SessionPool, |
| opts..., |
| ) |
| if err != nil { |
| if err == command.ErrEmptyCursor { |
| return newEmptyCursor(), nil |
| } |
| return nil, replaceTopologyErr(err) |
| } |
| |
| cursor, err := newCursor(batchCursor, iv.coll.registry) |
| return cursor, replaceTopologyErr(err) |
| } |
| |
| // CreateOne creates a single index in the collection specified by the model. |
| func (iv IndexView) CreateOne(ctx context.Context, model IndexModel, opts ...*options.CreateIndexesOptions) (string, error) { |
| names, err := iv.CreateMany(ctx, []IndexModel{model}, opts...) |
| if err != nil { |
| return "", err |
| } |
| |
| return names[0], nil |
| } |
| |
| // CreateMany creates multiple indexes in the collection specified by the models. The names of the |
| // creates indexes are returned. |
| func (iv IndexView) CreateMany(ctx context.Context, models []IndexModel, opts ...*options.CreateIndexesOptions) ([]string, error) { |
| names := make([]string, 0, len(models)) |
| indexes := bsonx.Arr{} |
| |
| for _, model := range models { |
| if model.Keys == nil { |
| return nil, fmt.Errorf("index model keys cannot be nil") |
| } |
| |
| name, err := getOrGenerateIndexName(iv.coll.registry, model) |
| if err != nil { |
| return nil, err |
| } |
| |
| names = append(names, name) |
| |
| keys, err := transformDocument(iv.coll.registry, model.Keys) |
| if err != nil { |
| return nil, err |
| } |
| index := bsonx.Doc{{"key", bsonx.Document(keys)}} |
| if model.Options != nil { |
| optsDoc, err := iv.createOptionsDoc(model.Options) |
| if err != nil { |
| return nil, err |
| } |
| |
| index = append(index, optsDoc...) |
| } |
| index = index.Set("name", bsonx.String(name)) |
| |
| indexes = append(indexes, bsonx.Document(index)) |
| } |
| |
| sess := sessionFromContext(ctx) |
| |
| err := iv.coll.client.ValidSession(sess) |
| if err != nil { |
| return nil, err |
| } |
| |
| cmd := command.CreateIndexes{ |
| NS: iv.coll.namespace(), |
| Indexes: indexes, |
| Session: sess, |
| Clock: iv.coll.client.clock, |
| } |
| |
| _, err = driver.CreateIndexes( |
| ctx, cmd, |
| iv.coll.client.topology, |
| iv.coll.writeSelector, |
| iv.coll.client.id, |
| iv.coll.client.topology.SessionPool, |
| opts..., |
| ) |
| if err != nil { |
| return nil, err |
| } |
| |
| return names, nil |
| } |
| |
| func (iv IndexView) createOptionsDoc(opts *options.IndexOptions) (bsonx.Doc, error) { |
| optsDoc := bsonx.Doc{} |
| if opts.Background != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"background", bsonx.Boolean(*opts.Background)}) |
| } |
| if opts.ExpireAfterSeconds != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"expireAfterSeconds", bsonx.Int32(*opts.ExpireAfterSeconds)}) |
| } |
| if opts.Name != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"name", bsonx.String(*opts.Name)}) |
| } |
| if opts.Sparse != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"sparse", bsonx.Boolean(*opts.Sparse)}) |
| } |
| if opts.StorageEngine != nil { |
| doc, err := transformDocument(iv.coll.registry, opts.StorageEngine) |
| if err != nil { |
| return nil, err |
| } |
| |
| optsDoc = append(optsDoc, bsonx.Elem{"storageEngine", bsonx.Document(doc)}) |
| } |
| if opts.Unique != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"unique", bsonx.Boolean(*opts.Unique)}) |
| } |
| if opts.Version != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"v", bsonx.Int32(*opts.Version)}) |
| } |
| if opts.DefaultLanguage != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"default_language", bsonx.String(*opts.DefaultLanguage)}) |
| } |
| if opts.LanguageOverride != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"language_override", bsonx.String(*opts.LanguageOverride)}) |
| } |
| if opts.TextVersion != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"textIndexVersion", bsonx.Int32(*opts.TextVersion)}) |
| } |
| if opts.Weights != nil { |
| weightsDoc, err := transformDocument(iv.coll.registry, opts.Weights) |
| if err != nil { |
| return nil, err |
| } |
| |
| optsDoc = append(optsDoc, bsonx.Elem{"weights", bsonx.Document(weightsDoc)}) |
| } |
| if opts.SphereVersion != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"2dsphereIndexVersion", bsonx.Int32(*opts.SphereVersion)}) |
| } |
| if opts.Bits != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"bits", bsonx.Int32(*opts.Bits)}) |
| } |
| if opts.Max != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"max", bsonx.Double(*opts.Max)}) |
| } |
| if opts.Min != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"min", bsonx.Double(*opts.Min)}) |
| } |
| if opts.BucketSize != nil { |
| optsDoc = append(optsDoc, bsonx.Elem{"bucketSize", bsonx.Int32(*opts.BucketSize)}) |
| } |
| if opts.PartialFilterExpression != nil { |
| doc, err := transformDocument(iv.coll.registry, opts.PartialFilterExpression) |
| if err != nil { |
| return nil, err |
| } |
| |
| optsDoc = append(optsDoc, bsonx.Elem{"partialFilterExpression", bsonx.Document(doc)}) |
| } |
| if opts.Collation != nil { |
| doc := opts.Collation.ToDocument() |
| optsDoc = append(optsDoc, bsonx.Elem{"collation", bsonx.Document(doc)}) |
| } |
| |
| return optsDoc, nil |
| } |
| |
| // DropOne drops the index with the given name from the collection. |
| func (iv IndexView) DropOne(ctx context.Context, name string, opts ...*options.DropIndexesOptions) (bson.Raw, error) { |
| if name == "*" { |
| return nil, ErrMultipleIndexDrop |
| } |
| |
| sess := sessionFromContext(ctx) |
| |
| err := iv.coll.client.ValidSession(sess) |
| if err != nil { |
| return nil, err |
| } |
| |
| cmd := command.DropIndexes{ |
| NS: iv.coll.namespace(), |
| Index: name, |
| Session: sess, |
| Clock: iv.coll.client.clock, |
| } |
| |
| return driver.DropIndexes( |
| ctx, cmd, |
| iv.coll.client.topology, |
| iv.coll.writeSelector, |
| iv.coll.client.id, |
| iv.coll.client.topology.SessionPool, |
| opts..., |
| ) |
| } |
| |
| // DropAll drops all indexes in the collection. |
| func (iv IndexView) DropAll(ctx context.Context, opts ...*options.DropIndexesOptions) (bson.Raw, error) { |
| sess := sessionFromContext(ctx) |
| |
| err := iv.coll.client.ValidSession(sess) |
| if err != nil { |
| return nil, err |
| } |
| |
| cmd := command.DropIndexes{ |
| NS: iv.coll.namespace(), |
| Index: "*", |
| Session: sess, |
| Clock: iv.coll.client.clock, |
| } |
| |
| return driver.DropIndexes( |
| ctx, cmd, |
| iv.coll.client.topology, |
| iv.coll.writeSelector, |
| iv.coll.client.id, |
| iv.coll.client.topology.SessionPool, |
| opts..., |
| ) |
| } |
| |
| func getOrGenerateIndexName(registry *bsoncodec.Registry, model IndexModel) (string, error) { |
| if model.Options != nil && model.Options.Name != nil { |
| return *model.Options.Name, nil |
| } |
| |
| name := bytes.NewBufferString("") |
| first := true |
| |
| keys, err := transformDocument(registry, model.Keys) |
| if err != nil { |
| return "", err |
| } |
| for _, elem := range keys { |
| if !first { |
| _, err := name.WriteRune('_') |
| if err != nil { |
| return "", err |
| } |
| } |
| |
| _, err := name.WriteString(elem.Key) |
| if err != nil { |
| return "", err |
| } |
| |
| _, err = name.WriteRune('_') |
| if err != nil { |
| return "", err |
| } |
| |
| var value string |
| |
| switch elem.Value.Type() { |
| case bsontype.Int32: |
| value = fmt.Sprintf("%d", elem.Value.Int32()) |
| case bsontype.Int64: |
| value = fmt.Sprintf("%d", elem.Value.Int64()) |
| case bsontype.String: |
| value = elem.Value.StringValue() |
| default: |
| return "", ErrInvalidIndexValue |
| } |
| |
| _, err = name.WriteString(value) |
| if err != nil { |
| return "", err |
| } |
| |
| first = false |
| } |
| |
| return name.String(), nil |
| } |