...

Source file src/go.mongodb.org/mongo-driver/mongo/index_view.go

Documentation: go.mongodb.org/mongo-driver/mongo

     1  // 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  
     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  // ErrInvalidIndexValue is returned if an index is created with a keys document that has a value that is not a number
    29  // or string.
    30  var ErrInvalidIndexValue = errors.New("invalid index value")
    31  
    32  // ErrNonStringIndexName is returned if an index is created with a name that is not a string.
    33  var ErrNonStringIndexName = errors.New("index name must be a string")
    34  
    35  // ErrMultipleIndexDrop is returned if multiple indexes would be dropped from a call to IndexView.DropOne.
    36  var ErrMultipleIndexDrop = errors.New("multiple indexes would be dropped")
    37  
    38  // IndexView is a type that can be used to create, drop, and list indexes on a collection. An IndexView for a collection
    39  // can be created by a call to Collection.Indexes().
    40  type IndexView struct {
    41  	coll *Collection
    42  }
    43  
    44  // IndexModel represents a new index to be created.
    45  type IndexModel struct {
    46  	// A document describing which keys should be used for the index. It cannot be nil. This must be an order-preserving
    47  	// type such as bson.D. Map types such as bson.M are not valid. See https://www.mongodb.com/docs/manual/indexes/#indexes
    48  	// for examples of valid documents.
    49  	Keys interface{}
    50  
    51  	// The options to use to create the index.
    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  // List executes a listIndexes command and returns a cursor over the indexes in the collection.
    63  //
    64  // The opts parameter can be used to specify options for this operation (see the options.ListIndexesOptions
    65  // documentation).
    66  //
    67  // For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listIndexes/.
    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  	// TODO(GODRIVER-3038): This operation should pass CSE to the ListIndexes
    91  	// Crypt setter to be applied to the operation.
    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  		// for namespaceNotFound errors, return an empty cursor and do not throw an error
   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  // ListSpecifications executes a List command and returns a slice of returned IndexSpecifications
   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  		// Pre-4.4 servers report a namespace in their responses, so we only set Namespace manually if it was not in
   151  		// the response.
   152  		res.Namespace = ns
   153  	}
   154  
   155  	return results, nil
   156  }
   157  
   158  // CreateOne executes a createIndexes command to create an index on the collection and returns the name of the new
   159  // index. See the IndexView.CreateMany documentation for more information and an example.
   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  // CreateMany executes a createIndexes command to create multiple indexes on the collection and returns the names of
   170  // the new indexes.
   171  //
   172  // For each IndexModel in the models parameter, the index name can be specified via the Options field. If a name is not
   173  // given, it will be generated from the Keys document.
   174  //
   175  // The opts parameter can be used to specify options for this operation (see the options.CreateIndexesOptions
   176  // documentation).
   177  //
   178  // For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/createIndexes/.
   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  	// TODO(GODRIVER-3038): This operation should pass CSE to the CreateIndexes
   258  	// Crypt setter to be applied to the operation.
   259  	//
   260  	// This was added in GODRIVER-2413 for the 2.0 major release.
   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  	// TODO(GODRIVER-3038): This operation should pass CSE to the DropIndexes
   399  	// Crypt setter to be applied to the operation.
   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  	// TODO: it's weird to return a bson.Raw here because we have to convert the result back to BSON
   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  // DropOne executes a dropIndexes operation to drop an index on the collection. If the operation succeeds, this returns
   420  // a BSON document in the form {nIndexesWas: <int32>}. The "nIndexesWas" field in the response contains the number of
   421  // indexes that existed prior to the drop.
   422  //
   423  // The name parameter should be the name of the index to drop. If the name is "*", ErrMultipleIndexDrop will be returned
   424  // without running the command because doing so would drop all indexes.
   425  //
   426  // The opts parameter can be used to specify options for this operation (see the options.DropIndexesOptions
   427  // documentation).
   428  //
   429  // For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
   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  // DropAll executes a dropIndexes operation to drop all indexes on the collection. If the operation succeeds, this
   439  // returns a BSON document in the form {nIndexesWas: <int32>}. The "nIndexesWas" field in the response contains the
   440  // number of indexes that existed prior to the drop.
   441  //
   442  // The opts parameter can be used to specify options for this operation (see the options.DropIndexesOptions
   443  // documentation).
   444  //
   445  // For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
   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