github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.mongodb.org/mongo-driver/mongo/index_view.go (about)

     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  	op := operation.NewListIndexes().
    90  		Session(sess).CommandMonitor(iv.coll.client.monitor).
    91  		ServerSelector(selector).ClusterClock(iv.coll.client.clock).
    92  		Database(iv.coll.db.name).Collection(iv.coll.name).
    93  		Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
    94  		Timeout(iv.coll.client.timeout)
    95  
    96  	cursorOpts := iv.coll.client.createBaseCursorOptions()
    97  	lio := options.MergeListIndexesOptions(opts...)
    98  	if lio.BatchSize != nil {
    99  		op = op.BatchSize(*lio.BatchSize)
   100  		cursorOpts.BatchSize = *lio.BatchSize
   101  	}
   102  	op = op.MaxTime(lio.MaxTime)
   103  	retry := driver.RetryNone
   104  	if iv.coll.client.retryReads {
   105  		retry = driver.RetryOncePerCommand
   106  	}
   107  	op.Retry(retry)
   108  
   109  	err = op.Execute(ctx)
   110  	if err != nil {
   111  		// for namespaceNotFound errors, return an empty cursor and do not throw an error
   112  		closeImplicitSession(sess)
   113  		if isNamespaceNotFoundError(err) {
   114  			return newEmptyCursor(), nil
   115  		}
   116  
   117  		return nil, replaceErrors(err)
   118  	}
   119  
   120  	bc, err := op.Result(cursorOpts)
   121  	if err != nil {
   122  		closeImplicitSession(sess)
   123  		return nil, replaceErrors(err)
   124  	}
   125  	cursor, err := newCursorWithSession(bc, iv.coll.bsonOpts, iv.coll.registry, sess)
   126  	return cursor, replaceErrors(err)
   127  }
   128  
   129  // ListSpecifications executes a List command and returns a slice of returned IndexSpecifications
   130  func (iv IndexView) ListSpecifications(ctx context.Context, opts ...*options.ListIndexesOptions) ([]*IndexSpecification, error) {
   131  	cursor, err := iv.List(ctx, opts...)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	var results []*IndexSpecification
   137  	err = cursor.All(ctx, &results)
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	ns := iv.coll.db.Name() + "." + iv.coll.Name()
   143  	for _, res := range results {
   144  		// Pre-4.4 servers report a namespace in their responses, so we only set Namespace manually if it was not in
   145  		// the response.
   146  		res.Namespace = ns
   147  	}
   148  
   149  	return results, nil
   150  }
   151  
   152  // CreateOne executes a createIndexes command to create an index on the collection and returns the name of the new
   153  // index. See the IndexView.CreateMany documentation for more information and an example.
   154  func (iv IndexView) CreateOne(ctx context.Context, model IndexModel, opts ...*options.CreateIndexesOptions) (string, error) {
   155  	names, err := iv.CreateMany(ctx, []IndexModel{model}, opts...)
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  
   160  	return names[0], nil
   161  }
   162  
   163  // CreateMany executes a createIndexes command to create multiple indexes on the collection and returns the names of
   164  // the new indexes.
   165  //
   166  // For each IndexModel in the models parameter, the index name can be specified via the Options field. If a name is not
   167  // given, it will be generated from the Keys document.
   168  //
   169  // The opts parameter can be used to specify options for this operation (see the options.CreateIndexesOptions
   170  // documentation).
   171  //
   172  // For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/createIndexes/.
   173  func (iv IndexView) CreateMany(ctx context.Context, models []IndexModel, opts ...*options.CreateIndexesOptions) ([]string, error) {
   174  	names := make([]string, 0, len(models))
   175  
   176  	var indexes bsoncore.Document
   177  	aidx, indexes := bsoncore.AppendArrayStart(indexes)
   178  
   179  	for i, model := range models {
   180  		if model.Keys == nil {
   181  			return nil, fmt.Errorf("index model keys cannot be nil")
   182  		}
   183  
   184  		if isUnorderedMap(model.Keys) {
   185  			return nil, ErrMapForOrderedArgument{"keys"}
   186  		}
   187  
   188  		keys, err := marshal(model.Keys, iv.coll.bsonOpts, iv.coll.registry)
   189  		if err != nil {
   190  			return nil, err
   191  		}
   192  
   193  		name, err := getOrGenerateIndexName(keys, model)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  
   198  		names = append(names, name)
   199  
   200  		var iidx int32
   201  		iidx, indexes = bsoncore.AppendDocumentElementStart(indexes, strconv.Itoa(i))
   202  		indexes = bsoncore.AppendDocumentElement(indexes, "key", keys)
   203  
   204  		if model.Options == nil {
   205  			model.Options = options.Index()
   206  		}
   207  		model.Options.SetName(name)
   208  
   209  		optsDoc, err := iv.createOptionsDoc(model.Options)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  
   214  		indexes = bsoncore.AppendDocument(indexes, optsDoc)
   215  
   216  		indexes, err = bsoncore.AppendDocumentEnd(indexes, iidx)
   217  		if err != nil {
   218  			return nil, err
   219  		}
   220  	}
   221  
   222  	indexes, err := bsoncore.AppendArrayEnd(indexes, aidx)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	sess := sessionFromContext(ctx)
   228  
   229  	if sess == nil && iv.coll.client.sessionPool != nil {
   230  		sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
   231  		defer sess.EndSession()
   232  	}
   233  
   234  	err = iv.coll.client.validSession(sess)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	wc := iv.coll.writeConcern
   240  	if sess.TransactionRunning() {
   241  		wc = nil
   242  	}
   243  	if !writeconcern.AckWrite(wc) {
   244  		sess = nil
   245  	}
   246  
   247  	selector := makePinnedSelector(sess, iv.coll.writeSelector)
   248  
   249  	option := options.MergeCreateIndexesOptions(opts...)
   250  
   251  	op := operation.NewCreateIndexes(indexes).
   252  		Session(sess).WriteConcern(wc).ClusterClock(iv.coll.client.clock).
   253  		Database(iv.coll.db.name).Collection(iv.coll.name).CommandMonitor(iv.coll.client.monitor).
   254  		Deployment(iv.coll.client.deployment).ServerSelector(selector).ServerAPI(iv.coll.client.serverAPI).
   255  		Timeout(iv.coll.client.timeout).MaxTime(option.MaxTime)
   256  	if option.CommitQuorum != nil {
   257  		commitQuorum, err := marshalValue(option.CommitQuorum, iv.coll.bsonOpts, iv.coll.registry)
   258  		if err != nil {
   259  			return nil, err
   260  		}
   261  
   262  		op.CommitQuorum(commitQuorum)
   263  	}
   264  
   265  	err = op.Execute(ctx)
   266  	if err != nil {
   267  		_, err = processWriteError(err)
   268  		return nil, err
   269  	}
   270  
   271  	return names, nil
   272  }
   273  
   274  func (iv IndexView) createOptionsDoc(opts *options.IndexOptions) (bsoncore.Document, error) {
   275  	optsDoc := bsoncore.Document{}
   276  	if opts.Background != nil {
   277  		optsDoc = bsoncore.AppendBooleanElement(optsDoc, "background", *opts.Background)
   278  	}
   279  	if opts.ExpireAfterSeconds != nil {
   280  		optsDoc = bsoncore.AppendInt32Element(optsDoc, "expireAfterSeconds", *opts.ExpireAfterSeconds)
   281  	}
   282  	if opts.Name != nil {
   283  		optsDoc = bsoncore.AppendStringElement(optsDoc, "name", *opts.Name)
   284  	}
   285  	if opts.Sparse != nil {
   286  		optsDoc = bsoncore.AppendBooleanElement(optsDoc, "sparse", *opts.Sparse)
   287  	}
   288  	if opts.StorageEngine != nil {
   289  		doc, err := marshal(opts.StorageEngine, iv.coll.bsonOpts, iv.coll.registry)
   290  		if err != nil {
   291  			return nil, err
   292  		}
   293  
   294  		optsDoc = bsoncore.AppendDocumentElement(optsDoc, "storageEngine", doc)
   295  	}
   296  	if opts.Unique != nil {
   297  		optsDoc = bsoncore.AppendBooleanElement(optsDoc, "unique", *opts.Unique)
   298  	}
   299  	if opts.Version != nil {
   300  		optsDoc = bsoncore.AppendInt32Element(optsDoc, "v", *opts.Version)
   301  	}
   302  	if opts.DefaultLanguage != nil {
   303  		optsDoc = bsoncore.AppendStringElement(optsDoc, "default_language", *opts.DefaultLanguage)
   304  	}
   305  	if opts.LanguageOverride != nil {
   306  		optsDoc = bsoncore.AppendStringElement(optsDoc, "language_override", *opts.LanguageOverride)
   307  	}
   308  	if opts.TextVersion != nil {
   309  		optsDoc = bsoncore.AppendInt32Element(optsDoc, "textIndexVersion", *opts.TextVersion)
   310  	}
   311  	if opts.Weights != nil {
   312  		doc, err := marshal(opts.Weights, iv.coll.bsonOpts, iv.coll.registry)
   313  		if err != nil {
   314  			return nil, err
   315  		}
   316  
   317  		optsDoc = bsoncore.AppendDocumentElement(optsDoc, "weights", doc)
   318  	}
   319  	if opts.SphereVersion != nil {
   320  		optsDoc = bsoncore.AppendInt32Element(optsDoc, "2dsphereIndexVersion", *opts.SphereVersion)
   321  	}
   322  	if opts.Bits != nil {
   323  		optsDoc = bsoncore.AppendInt32Element(optsDoc, "bits", *opts.Bits)
   324  	}
   325  	if opts.Max != nil {
   326  		optsDoc = bsoncore.AppendDoubleElement(optsDoc, "max", *opts.Max)
   327  	}
   328  	if opts.Min != nil {
   329  		optsDoc = bsoncore.AppendDoubleElement(optsDoc, "min", *opts.Min)
   330  	}
   331  	if opts.BucketSize != nil {
   332  		optsDoc = bsoncore.AppendInt32Element(optsDoc, "bucketSize", *opts.BucketSize)
   333  	}
   334  	if opts.PartialFilterExpression != nil {
   335  		doc, err := marshal(opts.PartialFilterExpression, iv.coll.bsonOpts, iv.coll.registry)
   336  		if err != nil {
   337  			return nil, err
   338  		}
   339  
   340  		optsDoc = bsoncore.AppendDocumentElement(optsDoc, "partialFilterExpression", doc)
   341  	}
   342  	if opts.Collation != nil {
   343  		optsDoc = bsoncore.AppendDocumentElement(optsDoc, "collation", bsoncore.Document(opts.Collation.ToDocument()))
   344  	}
   345  	if opts.WildcardProjection != nil {
   346  		doc, err := marshal(opts.WildcardProjection, iv.coll.bsonOpts, iv.coll.registry)
   347  		if err != nil {
   348  			return nil, err
   349  		}
   350  
   351  		optsDoc = bsoncore.AppendDocumentElement(optsDoc, "wildcardProjection", doc)
   352  	}
   353  	if opts.Hidden != nil {
   354  		optsDoc = bsoncore.AppendBooleanElement(optsDoc, "hidden", *opts.Hidden)
   355  	}
   356  
   357  	return optsDoc, nil
   358  }
   359  
   360  func (iv IndexView) drop(ctx context.Context, name string, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
   361  	if ctx == nil {
   362  		ctx = context.Background()
   363  	}
   364  
   365  	sess := sessionFromContext(ctx)
   366  	if sess == nil && iv.coll.client.sessionPool != nil {
   367  		sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
   368  		defer sess.EndSession()
   369  	}
   370  
   371  	err := iv.coll.client.validSession(sess)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	wc := iv.coll.writeConcern
   377  	if sess.TransactionRunning() {
   378  		wc = nil
   379  	}
   380  	if !writeconcern.AckWrite(wc) {
   381  		sess = nil
   382  	}
   383  
   384  	selector := makePinnedSelector(sess, iv.coll.writeSelector)
   385  
   386  	dio := options.MergeDropIndexesOptions(opts...)
   387  	op := operation.NewDropIndexes(name).
   388  		Session(sess).WriteConcern(wc).CommandMonitor(iv.coll.client.monitor).
   389  		ServerSelector(selector).ClusterClock(iv.coll.client.clock).
   390  		Database(iv.coll.db.name).Collection(iv.coll.name).
   391  		Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
   392  		Timeout(iv.coll.client.timeout).MaxTime(dio.MaxTime)
   393  
   394  	err = op.Execute(ctx)
   395  	if err != nil {
   396  		return nil, replaceErrors(err)
   397  	}
   398  
   399  	// TODO: it's weird to return a bson.Raw here because we have to convert the result back to BSON
   400  	ridx, res := bsoncore.AppendDocumentStart(nil)
   401  	res = bsoncore.AppendInt32Element(res, "nIndexesWas", op.Result().NIndexesWas)
   402  	res, _ = bsoncore.AppendDocumentEnd(res, ridx)
   403  	return res, nil
   404  }
   405  
   406  // DropOne executes a dropIndexes operation to drop an index on the collection. If the operation succeeds, this returns
   407  // a BSON document in the form {nIndexesWas: <int32>}. The "nIndexesWas" field in the response contains the number of
   408  // indexes that existed prior to the drop.
   409  //
   410  // The name parameter should be the name of the index to drop. If the name is "*", ErrMultipleIndexDrop will be returned
   411  // without running the command because doing so would drop all indexes.
   412  //
   413  // The opts parameter can be used to specify options for this operation (see the options.DropIndexesOptions
   414  // documentation).
   415  //
   416  // For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
   417  func (iv IndexView) DropOne(ctx context.Context, name string, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
   418  	if name == "*" {
   419  		return nil, ErrMultipleIndexDrop
   420  	}
   421  
   422  	return iv.drop(ctx, name, opts...)
   423  }
   424  
   425  // DropAll executes a dropIndexes operation to drop all indexes on the collection. If the operation succeeds, this
   426  // returns a BSON document in the form {nIndexesWas: <int32>}. The "nIndexesWas" field in the response contains the
   427  // number of indexes that existed prior to the drop.
   428  //
   429  // The opts parameter can be used to specify options for this operation (see the options.DropIndexesOptions
   430  // documentation).
   431  //
   432  // For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
   433  func (iv IndexView) DropAll(ctx context.Context, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
   434  	return iv.drop(ctx, "*", opts...)
   435  }
   436  
   437  func getOrGenerateIndexName(keySpecDocument bsoncore.Document, model IndexModel) (string, error) {
   438  	if model.Options != nil && model.Options.Name != nil {
   439  		return *model.Options.Name, nil
   440  	}
   441  
   442  	name := bytes.NewBufferString("")
   443  	first := true
   444  
   445  	elems, err := keySpecDocument.Elements()
   446  	if err != nil {
   447  		return "", err
   448  	}
   449  	for _, elem := range elems {
   450  		if !first {
   451  			_, err := name.WriteRune('_')
   452  			if err != nil {
   453  				return "", err
   454  			}
   455  		}
   456  
   457  		_, err := name.WriteString(elem.Key())
   458  		if err != nil {
   459  			return "", err
   460  		}
   461  
   462  		_, err = name.WriteRune('_')
   463  		if err != nil {
   464  			return "", err
   465  		}
   466  
   467  		var value string
   468  
   469  		bsonValue := elem.Value()
   470  		switch bsonValue.Type {
   471  		case bsontype.Int32:
   472  			value = fmt.Sprintf("%d", bsonValue.Int32())
   473  		case bsontype.Int64:
   474  			value = fmt.Sprintf("%d", bsonValue.Int64())
   475  		case bsontype.String:
   476  			value = bsonValue.StringValue()
   477  		default:
   478  			return "", ErrInvalidIndexValue
   479  		}
   480  
   481  		_, err = name.WriteString(value)
   482  		if err != nil {
   483  			return "", err
   484  		}
   485  
   486  		first = false
   487  	}
   488  
   489  	return name.String(), nil
   490  }