|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423 |
- // 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 (
- "context"
- "errors"
- "fmt"
- "strings"
- "time"
-
- "go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/bson/bsoncodec"
- "go.mongodb.org/mongo-driver/bson/bsontype"
- "go.mongodb.org/mongo-driver/mongo/options"
- "go.mongodb.org/mongo-driver/mongo/readconcern"
- "go.mongodb.org/mongo-driver/mongo/readpref"
- "go.mongodb.org/mongo-driver/mongo/writeconcern"
- "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
- "go.mongodb.org/mongo-driver/x/mongo/driver"
- "go.mongodb.org/mongo-driver/x/mongo/driver/description"
- "go.mongodb.org/mongo-driver/x/mongo/driver/operation"
- "go.mongodb.org/mongo-driver/x/mongo/driver/session"
- )
-
- // Collection performs operations on a given collection.
- type Collection struct {
- client *Client
- db *Database
- name string
- readConcern *readconcern.ReadConcern
- writeConcern *writeconcern.WriteConcern
- readPreference *readpref.ReadPref
- readSelector description.ServerSelector
- writeSelector description.ServerSelector
- registry *bsoncodec.Registry
- }
-
- // aggregateParams is used to store information to configure an Aggregate operation.
- type aggregateParams struct {
- ctx context.Context
- pipeline interface{}
- client *Client
- registry *bsoncodec.Registry
- readConcern *readconcern.ReadConcern
- writeConcern *writeconcern.WriteConcern
- retryRead bool
- db string
- col string
- readSelector description.ServerSelector
- writeSelector description.ServerSelector
- readPreference *readpref.ReadPref
- opts []*options.AggregateOptions
- }
-
- func closeImplicitSession(sess *session.Client) {
- if sess != nil && sess.SessionType == session.Implicit {
- sess.EndSession()
- }
- }
-
- func newCollection(db *Database, name string, opts ...*options.CollectionOptions) *Collection {
- collOpt := options.MergeCollectionOptions(opts...)
-
- rc := db.readConcern
- if collOpt.ReadConcern != nil {
- rc = collOpt.ReadConcern
- }
-
- wc := db.writeConcern
- if collOpt.WriteConcern != nil {
- wc = collOpt.WriteConcern
- }
-
- rp := db.readPreference
- if collOpt.ReadPreference != nil {
- rp = collOpt.ReadPreference
- }
-
- reg := db.registry
- if collOpt.Registry != nil {
- reg = collOpt.Registry
- }
-
- readSelector := description.CompositeSelector([]description.ServerSelector{
- description.ReadPrefSelector(rp),
- description.LatencySelector(db.client.localThreshold),
- })
-
- writeSelector := description.CompositeSelector([]description.ServerSelector{
- description.WriteSelector(),
- description.LatencySelector(db.client.localThreshold),
- })
-
- coll := &Collection{
- client: db.client,
- db: db,
- name: name,
- readPreference: rp,
- readConcern: rc,
- writeConcern: wc,
- readSelector: readSelector,
- writeSelector: writeSelector,
- registry: reg,
- }
-
- return coll
- }
-
- func (coll *Collection) copy() *Collection {
- return &Collection{
- client: coll.client,
- db: coll.db,
- name: coll.name,
- readConcern: coll.readConcern,
- writeConcern: coll.writeConcern,
- readPreference: coll.readPreference,
- readSelector: coll.readSelector,
- writeSelector: coll.writeSelector,
- registry: coll.registry,
- }
- }
-
- // Clone creates a copy of this collection with updated options, if any are given.
- func (coll *Collection) Clone(opts ...*options.CollectionOptions) (*Collection, error) {
- copyColl := coll.copy()
- optsColl := options.MergeCollectionOptions(opts...)
-
- if optsColl.ReadConcern != nil {
- copyColl.readConcern = optsColl.ReadConcern
- }
-
- if optsColl.WriteConcern != nil {
- copyColl.writeConcern = optsColl.WriteConcern
- }
-
- if optsColl.ReadPreference != nil {
- copyColl.readPreference = optsColl.ReadPreference
- }
-
- if optsColl.Registry != nil {
- copyColl.registry = optsColl.Registry
- }
-
- copyColl.readSelector = description.CompositeSelector([]description.ServerSelector{
- description.ReadPrefSelector(copyColl.readPreference),
- description.LatencySelector(copyColl.client.localThreshold),
- })
-
- return copyColl, nil
- }
-
- // Name provides access to the name of the collection.
- func (coll *Collection) Name() string {
- return coll.name
- }
-
- // Database provides access to the database that contains the collection.
- func (coll *Collection) Database() *Database {
- return coll.db
- }
-
- // BulkWrite performs a bulk write operation.
- //
- // See https://docs.mongodb.com/manual/core/bulk-write-operations/.
- func (coll *Collection) BulkWrite(ctx context.Context, models []WriteModel,
- opts ...*options.BulkWriteOptions) (*BulkWriteResult, error) {
-
- if len(models) == 0 {
- return nil, ErrEmptySlice
- }
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- sess := sessionFromContext(ctx)
- if sess == nil && coll.client.topology.SessionPool != nil {
- sess, err := session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return nil, err
- }
- defer sess.EndSession()
- }
-
- err := coll.client.validSession(sess)
- if err != nil {
- return nil, err
- }
-
- wc := coll.writeConcern
- if sess.TransactionRunning() {
- wc = nil
- }
- if !writeconcern.AckWrite(wc) {
- sess = nil
- }
-
- selector := makePinnedSelector(sess, coll.writeSelector)
-
- for _, model := range models {
- if model == nil {
- return nil, ErrNilDocument
- }
- }
-
- bwo := options.MergeBulkWriteOptions(opts...)
-
- op := bulkWrite{
- ordered: bwo.Ordered,
- bypassDocumentValidation: bwo.BypassDocumentValidation,
- models: models,
- session: sess,
- collection: coll,
- selector: selector,
- writeConcern: wc,
- }
-
- err = op.execute(ctx)
-
- return &op.result, replaceErrors(err)
- }
-
- func (coll *Collection) insert(ctx context.Context, documents []interface{},
- opts ...*options.InsertManyOptions) ([]interface{}, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- result := make([]interface{}, len(documents))
- docs := make([]bsoncore.Document, len(documents))
-
- for i, doc := range documents {
- var err error
- docs[i], result[i], err = transformAndEnsureIDv2(coll.registry, doc)
- if err != nil {
- return nil, err
- }
- }
-
- sess := sessionFromContext(ctx)
- if sess == nil && coll.client.topology.SessionPool != nil {
- var err error
- sess, err = session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return nil, err
- }
- defer sess.EndSession()
- }
-
- err := coll.client.validSession(sess)
- if err != nil {
- return nil, err
- }
-
- wc := coll.writeConcern
- if sess.TransactionRunning() {
- wc = nil
- }
- if !writeconcern.AckWrite(wc) {
- sess = nil
- }
-
- selector := makePinnedSelector(sess, coll.writeSelector)
-
- op := operation.NewInsert(docs...).
- Session(sess).WriteConcern(wc).CommandMonitor(coll.client.monitor).
- ServerSelector(selector).ClusterClock(coll.client.clock).
- Database(coll.db.name).Collection(coll.name).
- Deployment(coll.client.topology)
- imo := options.MergeInsertManyOptions(opts...)
- if imo.BypassDocumentValidation != nil && *imo.BypassDocumentValidation {
- op = op.BypassDocumentValidation(*imo.BypassDocumentValidation)
- }
- if imo.Ordered != nil {
- op = op.Ordered(*imo.Ordered)
- }
- retry := driver.RetryNone
- if coll.client.retryWrites {
- retry = driver.RetryOncePerCommand
- }
- op = op.Retry(retry)
-
- return result, op.Execute(ctx)
- }
-
- // InsertOne inserts a single document into the collection.
- func (coll *Collection) InsertOne(ctx context.Context, document interface{},
- opts ...*options.InsertOneOptions) (*InsertOneResult, error) {
-
- imOpts := make([]*options.InsertManyOptions, len(opts))
- for i, opt := range opts {
- imo := options.InsertMany()
- if opt.BypassDocumentValidation != nil && *opt.BypassDocumentValidation {
- imo = imo.SetBypassDocumentValidation(*opt.BypassDocumentValidation)
- }
- imOpts[i] = imo
- }
- res, err := coll.insert(ctx, []interface{}{document}, imOpts...)
-
- rr, err := processWriteError(err)
- if rr&rrOne == 0 {
- return nil, err
- }
- return &InsertOneResult{InsertedID: res[0]}, err
- }
-
- // InsertMany inserts the provided documents.
- func (coll *Collection) InsertMany(ctx context.Context, documents []interface{},
- opts ...*options.InsertManyOptions) (*InsertManyResult, error) {
-
- if len(documents) == 0 {
- return nil, ErrEmptySlice
- }
-
- result, err := coll.insert(ctx, documents, opts...)
- rr, err := processWriteError(err)
- if rr&rrMany == 0 {
- return nil, err
- }
-
- imResult := &InsertManyResult{InsertedIDs: result}
- writeException, ok := err.(WriteException)
- if !ok {
- return imResult, err
- }
-
- // create and return a BulkWriteException
- bwErrors := make([]BulkWriteError, 0, len(writeException.WriteErrors))
- for _, we := range writeException.WriteErrors {
- bwErrors = append(bwErrors, BulkWriteError{
- WriteError{
- Index: we.Index,
- Code: we.Code,
- Message: we.Message,
- },
- nil,
- })
- }
- return imResult, BulkWriteException{
- WriteErrors: bwErrors,
- WriteConcernError: writeException.WriteConcernError,
- }
- }
-
- func (coll *Collection) delete(ctx context.Context, filter interface{}, deleteOne bool, expectedRr returnResult,
- opts ...*options.DeleteOptions) (*DeleteResult, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- f, err := transformBsoncoreDocument(coll.registry, filter)
- if err != nil {
- return nil, err
- }
-
- sess := sessionFromContext(ctx)
- if sess == nil && coll.client.topology.SessionPool != nil {
- sess, err = session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return nil, err
- }
- defer sess.EndSession()
- }
-
- err = coll.client.validSession(sess)
- if err != nil {
- return nil, err
- }
-
- wc := coll.writeConcern
- if sess.TransactionRunning() {
- wc = nil
- }
- if !writeconcern.AckWrite(wc) {
- sess = nil
- }
-
- selector := makePinnedSelector(sess, coll.writeSelector)
-
- var limit int32
- if deleteOne {
- limit = 1
- }
- do := options.MergeDeleteOptions(opts...)
- didx, doc := bsoncore.AppendDocumentStart(nil)
- doc = bsoncore.AppendDocumentElement(doc, "q", f)
- doc = bsoncore.AppendInt32Element(doc, "limit", limit)
- if do.Collation != nil {
- doc = bsoncore.AppendDocumentElement(doc, "collation", do.Collation.ToDocument())
- }
- doc, _ = bsoncore.AppendDocumentEnd(doc, didx)
-
- op := operation.NewDelete(doc).
- Session(sess).WriteConcern(wc).CommandMonitor(coll.client.monitor).
- ServerSelector(selector).ClusterClock(coll.client.clock).
- Database(coll.db.name).Collection(coll.name).
- Deployment(coll.client.topology)
-
- // deleteMany cannot be retried
- retryMode := driver.RetryNone
- if deleteOne && coll.client.retryWrites {
- retryMode = driver.RetryOncePerCommand
- }
- op = op.Retry(retryMode)
- rr, err := processWriteError(op.Execute(ctx))
- if rr&expectedRr == 0 {
- return nil, err
- }
- return &DeleteResult{DeletedCount: int64(op.Result().N)}, err
- }
-
- // DeleteOne deletes a single document from the collection.
- func (coll *Collection) DeleteOne(ctx context.Context, filter interface{},
- opts ...*options.DeleteOptions) (*DeleteResult, error) {
-
- return coll.delete(ctx, filter, true, rrOne, opts...)
- }
-
- // DeleteMany deletes multiple documents from the collection.
- func (coll *Collection) DeleteMany(ctx context.Context, filter interface{},
- opts ...*options.DeleteOptions) (*DeleteResult, error) {
-
- return coll.delete(ctx, filter, false, rrMany, opts...)
- }
-
- func (coll *Collection) updateOrReplace(ctx context.Context, filter bsoncore.Document, update interface{}, multi bool,
- expectedRr returnResult, checkDollarKey bool, opts ...*options.UpdateOptions) (*UpdateResult, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- uo := options.MergeUpdateOptions(opts...)
- uidx, updateDoc := bsoncore.AppendDocumentStart(nil)
- updateDoc = bsoncore.AppendDocumentElement(updateDoc, "q", filter)
-
- u, err := transformUpdateValue(coll.registry, update, checkDollarKey)
- if err != nil {
- return nil, err
- }
- updateDoc = bsoncore.AppendValueElement(updateDoc, "u", u)
- if multi {
- updateDoc = bsoncore.AppendBooleanElement(updateDoc, "multi", multi)
- }
-
- // collation, arrayFilters, and upsert are included on the individual update documents rather than as part of the
- // command
- if uo.Collation != nil {
- updateDoc = bsoncore.AppendDocumentElement(updateDoc, "collation", bsoncore.Document(uo.Collation.ToDocument()))
- }
- if uo.ArrayFilters != nil {
- arr, err := uo.ArrayFilters.ToArrayDocument()
- if err != nil {
- return nil, err
- }
- updateDoc = bsoncore.AppendArrayElement(updateDoc, "arrayFilters", arr)
- }
- if uo.Upsert != nil {
- updateDoc = bsoncore.AppendBooleanElement(updateDoc, "upsert", *uo.Upsert)
- }
- updateDoc, _ = bsoncore.AppendDocumentEnd(updateDoc, uidx)
-
- sess := sessionFromContext(ctx)
- if sess == nil && coll.client.topology.SessionPool != nil {
- var err error
- sess, err = session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return nil, err
- }
- defer sess.EndSession()
- }
-
- err = coll.client.validSession(sess)
- if err != nil {
- return nil, err
- }
-
- wc := coll.writeConcern
- if sess.TransactionRunning() {
- wc = nil
- }
- if !writeconcern.AckWrite(wc) {
- sess = nil
- }
-
- selector := makePinnedSelector(sess, coll.writeSelector)
-
- op := operation.NewUpdate(updateDoc).
- Session(sess).WriteConcern(wc).CommandMonitor(coll.client.monitor).
- ServerSelector(selector).ClusterClock(coll.client.clock).
- Database(coll.db.name).Collection(coll.name).
- Deployment(coll.client.topology)
-
- if uo.BypassDocumentValidation != nil && *uo.BypassDocumentValidation {
- op = op.BypassDocumentValidation(*uo.BypassDocumentValidation)
- }
- retry := driver.RetryNone
- // retryable writes are only enabled updateOne/replaceOne operations
- if !multi && coll.client.retryWrites {
- retry = driver.RetryOncePerCommand
- }
- op = op.Retry(retry)
- err = op.Execute(ctx)
-
- rr, err := processWriteError(err)
- if rr&expectedRr == 0 {
- return nil, err
- }
-
- opRes := op.Result()
- res := &UpdateResult{
- MatchedCount: int64(opRes.N),
- ModifiedCount: int64(opRes.NModified),
- UpsertedCount: int64(len(opRes.Upserted)),
- }
- if len(opRes.Upserted) > 0 {
- res.UpsertedID = opRes.Upserted[0].ID
- res.MatchedCount--
- }
-
- return res, err
- }
-
- // UpdateOne updates a single document in the collection.
- func (coll *Collection) UpdateOne(ctx context.Context, filter interface{}, update interface{},
- opts ...*options.UpdateOptions) (*UpdateResult, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- f, err := transformBsoncoreDocument(coll.registry, filter)
- if err != nil {
- return nil, err
- }
-
- return coll.updateOrReplace(ctx, f, update, false, rrOne, true, opts...)
- }
-
- // UpdateMany updates multiple documents in the collection.
- func (coll *Collection) UpdateMany(ctx context.Context, filter interface{}, update interface{},
- opts ...*options.UpdateOptions) (*UpdateResult, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- f, err := transformBsoncoreDocument(coll.registry, filter)
- if err != nil {
- return nil, err
- }
-
- return coll.updateOrReplace(ctx, f, update, true, rrMany, true, opts...)
- }
-
- // ReplaceOne replaces a single document in the collection.
- func (coll *Collection) ReplaceOne(ctx context.Context, filter interface{},
- replacement interface{}, opts ...*options.ReplaceOptions) (*UpdateResult, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- f, err := transformBsoncoreDocument(coll.registry, filter)
- if err != nil {
- return nil, err
- }
-
- r, err := transformBsoncoreDocument(coll.registry, replacement)
- if err != nil {
- return nil, err
- }
-
- if elem, err := r.IndexErr(0); err == nil && strings.HasPrefix(elem.Key(), "$") {
- return nil, errors.New("replacement document cannot contains keys beginning with '$")
- }
-
- updateOptions := make([]*options.UpdateOptions, 0, len(opts))
- for _, opt := range opts {
- uOpts := options.Update()
- uOpts.BypassDocumentValidation = opt.BypassDocumentValidation
- uOpts.Collation = opt.Collation
- uOpts.Upsert = opt.Upsert
- updateOptions = append(updateOptions, uOpts)
- }
-
- return coll.updateOrReplace(ctx, f, r, false, rrOne, false, updateOptions...)
- }
-
- // Aggregate runs an aggregation framework pipeline.
- //
- // See https://docs.mongodb.com/manual/aggregation/.
- func (coll *Collection) Aggregate(ctx context.Context, pipeline interface{},
- opts ...*options.AggregateOptions) (*Cursor, error) {
- a := aggregateParams{
- ctx: ctx,
- pipeline: pipeline,
- client: coll.client,
- registry: coll.registry,
- readConcern: coll.readConcern,
- writeConcern: coll.writeConcern,
- retryRead: coll.client.retryReads,
- db: coll.db.name,
- col: coll.name,
- readSelector: coll.readSelector,
- writeSelector: coll.writeSelector,
- readPreference: coll.readPreference,
- opts: opts,
- }
- return aggregate(a)
- }
-
- // aggreate is the helper method for Aggregate
- func aggregate(a aggregateParams) (*Cursor, error) {
-
- if a.ctx == nil {
- a.ctx = context.Background()
- }
-
- pipelineArr, hasOutputStage, err := transformAggregatePipelinev2(a.registry, a.pipeline)
- if err != nil {
- return nil, err
- }
-
- sess := sessionFromContext(a.ctx)
- if sess == nil && a.client.topology.SessionPool != nil {
- sess, err = session.NewClientSession(a.client.topology.SessionPool, a.client.id, session.Implicit)
- if err != nil {
- return nil, err
- }
- }
- if err = a.client.validSession(sess); err != nil {
- return nil, err
- }
-
- var wc *writeconcern.WriteConcern
- if hasOutputStage {
- wc = a.writeConcern
- }
- rc := a.readConcern
- if sess.TransactionRunning() {
- wc = nil
- rc = nil
- }
- if !writeconcern.AckWrite(wc) {
- closeImplicitSession(sess)
- sess = nil
- }
-
- defaultSelector := a.readSelector
- if hasOutputStage {
- defaultSelector = a.writeSelector
- }
- selector := makePinnedSelector(sess, defaultSelector)
-
- ao := options.MergeAggregateOptions(a.opts...)
- cursorOpts := driver.CursorOptions{
- CommandMonitor: a.client.monitor,
- }
-
- op := operation.NewAggregate(pipelineArr).Session(sess).WriteConcern(wc).ReadConcern(rc).ReadPreference(a.readPreference).CommandMonitor(a.client.monitor).
- ServerSelector(selector).ClusterClock(a.client.clock).Database(a.db).Collection(a.col).Deployment(a.client.topology)
- if ao.AllowDiskUse != nil {
- op.AllowDiskUse(*ao.AllowDiskUse)
- }
- // ignore batchSize of 0 with $out
- if ao.BatchSize != nil && !(*ao.BatchSize == 0 && hasOutputStage) {
- op.BatchSize(*ao.BatchSize)
- cursorOpts.BatchSize = *ao.BatchSize
- }
- if ao.BypassDocumentValidation != nil && *ao.BypassDocumentValidation {
- op.BypassDocumentValidation(*ao.BypassDocumentValidation)
- }
- if ao.Collation != nil {
- op.Collation(bsoncore.Document(ao.Collation.ToDocument()))
- }
- if ao.MaxTime != nil {
- op.MaxTimeMS(int64(*ao.MaxTime / time.Millisecond))
- }
- if ao.MaxAwaitTime != nil {
- cursorOpts.MaxTimeMS = int64(*ao.MaxAwaitTime / time.Millisecond)
- }
- if ao.Comment != nil {
- op.Comment(*ao.Comment)
- }
- if ao.Hint != nil {
- hintVal, err := transformValue(a.registry, ao.Hint)
- if err != nil {
- closeImplicitSession(sess)
- return nil, err
- }
- op.Hint(hintVal)
- }
-
- retry := driver.RetryNone
- if a.retryRead && !hasOutputStage {
- retry = driver.RetryOncePerCommand
- }
- op = op.Retry(retry)
-
- err = op.Execute(a.ctx)
- if err != nil {
- closeImplicitSession(sess)
- if wce, ok := err.(driver.WriteCommandError); ok && wce.WriteConcernError != nil {
- return nil, *convertDriverWriteConcernError(wce.WriteConcernError)
- }
- return nil, replaceErrors(err)
- }
-
- bc, err := op.Result(cursorOpts)
- if err != nil {
- closeImplicitSession(sess)
- return nil, replaceErrors(err)
- }
- cursor, err := newCursorWithSession(bc, a.registry, sess)
- return cursor, replaceErrors(err)
- }
-
- // CountDocuments gets the number of documents matching the filter.
- // For a fast count of the total documents in a collection see EstimatedDocumentCount.
- func (coll *Collection) CountDocuments(ctx context.Context, filter interface{},
- opts ...*options.CountOptions) (int64, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- countOpts := options.MergeCountOptions(opts...)
-
- pipelineArr, err := countDocumentsAggregatePipeline(coll.registry, filter, countOpts)
- if err != nil {
- return 0, err
- }
-
- sess := sessionFromContext(ctx)
- if sess == nil && coll.client.topology.SessionPool != nil {
- sess, err = session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return 0, err
- }
- defer sess.EndSession()
- }
- if err = coll.client.validSession(sess); err != nil {
- return 0, err
- }
-
- rc := coll.readConcern
- if sess.TransactionRunning() {
- rc = nil
- }
-
- selector := makePinnedSelector(sess, coll.readSelector)
-
- op := operation.NewAggregate(pipelineArr).Session(sess).ReadConcern(rc).ReadPreference(coll.readPreference).
- CommandMonitor(coll.client.monitor).ServerSelector(selector).ClusterClock(coll.client.clock).Database(coll.db.name).
- Collection(coll.name).Deployment(coll.client.topology)
- if countOpts.Collation != nil {
- op.Collation(bsoncore.Document(countOpts.Collation.ToDocument()))
- }
- if countOpts.MaxTime != nil {
- op.MaxTimeMS(int64(*countOpts.MaxTime / time.Millisecond))
- }
- if countOpts.Hint != nil {
- hintVal, err := transformValue(coll.registry, countOpts.Hint)
- if err != nil {
- return 0, err
- }
- op.Hint(hintVal)
- }
- retry := driver.RetryNone
- if coll.client.retryReads {
- retry = driver.RetryOncePerCommand
- }
- op = op.Retry(retry)
-
- err = op.Execute(ctx)
- if err != nil {
- return 0, replaceErrors(err)
- }
-
- batch := op.ResultCursorResponse().FirstBatch
- if batch == nil {
- return 0, errors.New("invalid response from server, no 'firstBatch' field")
- }
-
- docs, err := batch.Documents()
- if err != nil || len(docs) == 0 {
- return 0, nil
- }
-
- val, ok := docs[0].Lookup("n").AsInt64OK()
- if !ok {
- return 0, errors.New("invalid response from server, no 'n' field")
- }
-
- return val, nil
- }
-
- // EstimatedDocumentCount gets an estimate of the count of documents in a collection using collection metadata.
- func (coll *Collection) EstimatedDocumentCount(ctx context.Context,
- opts ...*options.EstimatedDocumentCountOptions) (int64, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- sess := sessionFromContext(ctx)
-
- var err error
- if sess == nil && coll.client.topology.SessionPool != nil {
- sess, err = session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return 0, err
- }
- defer sess.EndSession()
- }
-
- err = coll.client.validSession(sess)
- if err != nil {
- return 0, err
- }
-
- rc := coll.readConcern
- if sess.TransactionRunning() {
- rc = nil
- }
-
- selector := makePinnedSelector(sess, coll.readSelector)
-
- op := operation.NewCount().Session(sess).ClusterClock(coll.client.clock).
- Database(coll.db.name).Collection(coll.name).CommandMonitor(coll.client.monitor).
- Deployment(coll.client.topology).ReadConcern(rc).ReadPreference(coll.readPreference).
- ServerSelector(selector)
-
- co := options.MergeEstimatedDocumentCountOptions(opts...)
- if co.MaxTime != nil {
- op = op.MaxTimeMS(int64(*co.MaxTime / time.Millisecond))
- }
- retry := driver.RetryNone
- if coll.client.retryReads {
- retry = driver.RetryOncePerCommand
- }
- op.Retry(retry)
-
- err = op.Execute(ctx)
-
- return op.Result().N, replaceErrors(err)
- }
-
- // Distinct finds the distinct values for a specified field across a single
- // collection.
- func (coll *Collection) Distinct(ctx context.Context, fieldName string, filter interface{},
- opts ...*options.DistinctOptions) ([]interface{}, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- f, err := transformBsoncoreDocument(coll.registry, filter)
- if err != nil {
- return nil, err
- }
-
- sess := sessionFromContext(ctx)
-
- if sess == nil && coll.client.topology.SessionPool != nil {
- sess, err = session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return nil, err
- }
- defer sess.EndSession()
- }
-
- err = coll.client.validSession(sess)
- if err != nil {
- return nil, err
- }
-
- rc := coll.readConcern
- if sess.TransactionRunning() {
- rc = nil
- }
-
- selector := makePinnedSelector(sess, coll.readSelector)
-
- option := options.MergeDistinctOptions(opts...)
-
- op := operation.NewDistinct(fieldName, bsoncore.Document(f)).
- Session(sess).ClusterClock(coll.client.clock).
- Database(coll.db.name).Collection(coll.name).CommandMonitor(coll.client.monitor).
- Deployment(coll.client.topology).ReadConcern(rc).ReadPreference(coll.readPreference).
- ServerSelector(selector)
-
- if option.Collation != nil {
- op.Collation(bsoncore.Document(option.Collation.ToDocument()))
- }
- if option.MaxTime != nil {
- op.MaxTimeMS(int64(*option.MaxTime / time.Millisecond))
- }
- retry := driver.RetryNone
- if coll.client.retryReads {
- retry = driver.RetryOncePerCommand
- }
- op = op.Retry(retry)
-
- err = op.Execute(ctx)
- if err != nil {
- return nil, replaceErrors(err)
- }
-
- arr, ok := op.Result().Values.ArrayOK()
- if !ok {
- return nil, fmt.Errorf("response field 'values' is type array, but received BSON type %s", op.Result().Values.Type)
- }
-
- values, err := arr.Values()
- if err != nil {
- return nil, err
- }
-
- retArray := make([]interface{}, len(values))
-
- for i, val := range values {
- raw := bson.RawValue{Type: val.Type, Value: val.Data}
- err = raw.Unmarshal(&retArray[i])
- if err != nil {
- return nil, err
- }
- }
-
- return retArray, replaceErrors(err)
- }
-
- // Find finds the documents matching a model.
- func (coll *Collection) Find(ctx context.Context, filter interface{},
- opts ...*options.FindOptions) (*Cursor, error) {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- f, err := transformBsoncoreDocument(coll.registry, filter)
- if err != nil {
- return nil, err
- }
-
- sess := sessionFromContext(ctx)
- if sess == nil && coll.client.topology.SessionPool != nil {
- var err error
- sess, err = session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return nil, err
- }
- }
-
- err = coll.client.validSession(sess)
- if err != nil {
- closeImplicitSession(sess)
- return nil, err
- }
-
- rc := coll.readConcern
- if sess.TransactionRunning() {
- rc = nil
- }
-
- selector := makePinnedSelector(sess, coll.readSelector)
-
- op := operation.NewFind(f).
- Session(sess).ReadConcern(rc).ReadPreference(coll.readPreference).
- CommandMonitor(coll.client.monitor).ServerSelector(selector).
- ClusterClock(coll.client.clock).Database(coll.db.name).Collection(coll.name).
- Deployment(coll.client.topology)
-
- fo := options.MergeFindOptions(opts...)
- cursorOpts := driver.CursorOptions{
- CommandMonitor: coll.client.monitor,
- }
-
- if fo.AllowPartialResults != nil {
- op.AllowPartialResults(*fo.AllowPartialResults)
- }
- if fo.BatchSize != nil {
- cursorOpts.BatchSize = *fo.BatchSize
- op.BatchSize(*fo.BatchSize)
- }
- if fo.Collation != nil {
- op.Collation(bsoncore.Document(fo.Collation.ToDocument()))
- }
- if fo.Comment != nil {
- op.Comment(*fo.Comment)
- }
- if fo.CursorType != nil {
- switch *fo.CursorType {
- case options.Tailable:
- op.Tailable(true)
- case options.TailableAwait:
- op.Tailable(true)
- op.AwaitData(true)
- }
- }
- if fo.Hint != nil {
- hint, err := transformValue(coll.registry, fo.Hint)
- if err != nil {
- closeImplicitSession(sess)
- return nil, err
- }
- op.Hint(hint)
- }
- if fo.Limit != nil {
- limit := *fo.Limit
- if limit < 0 {
- limit = -1 * limit
- op.SingleBatch(true)
- }
- cursorOpts.Limit = int32(limit)
- op.Limit(limit)
- }
- if fo.Max != nil {
- max, err := transformBsoncoreDocument(coll.registry, fo.Max)
- if err != nil {
- closeImplicitSession(sess)
- return nil, err
- }
- op.Max(max)
- }
- if fo.MaxAwaitTime != nil {
- cursorOpts.MaxTimeMS = int64(*fo.MaxAwaitTime / time.Millisecond)
- }
- if fo.MaxTime != nil {
- op.MaxTimeMS(int64(*fo.MaxTime / time.Millisecond))
- }
- if fo.Min != nil {
- min, err := transformBsoncoreDocument(coll.registry, fo.Min)
- if err != nil {
- closeImplicitSession(sess)
- return nil, err
- }
- op.Min(min)
- }
- if fo.NoCursorTimeout != nil {
- op.NoCursorTimeout(*fo.NoCursorTimeout)
- }
- if fo.OplogReplay != nil {
- op.OplogReplay(*fo.OplogReplay)
- }
- if fo.Projection != nil {
- proj, err := transformBsoncoreDocument(coll.registry, fo.Projection)
- if err != nil {
- closeImplicitSession(sess)
- return nil, err
- }
- op.Projection(proj)
- }
- if fo.ReturnKey != nil {
- op.ReturnKey(*fo.ReturnKey)
- }
- if fo.ShowRecordID != nil {
- op.ShowRecordID(*fo.ShowRecordID)
- }
- if fo.Skip != nil {
- op.Skip(*fo.Skip)
- }
- if fo.Snapshot != nil {
- op.Snapshot(*fo.Snapshot)
- }
- if fo.Sort != nil {
- sort, err := transformBsoncoreDocument(coll.registry, fo.Sort)
- if err != nil {
- closeImplicitSession(sess)
- return nil, err
- }
- op.Sort(sort)
- }
- retry := driver.RetryNone
- if coll.client.retryReads {
- retry = driver.RetryOncePerCommand
- }
- op = op.Retry(retry)
-
- if err = op.Execute(ctx); err != nil {
- closeImplicitSession(sess)
- return nil, replaceErrors(err)
- }
-
- bc, err := op.Result(cursorOpts)
- if err != nil {
- closeImplicitSession(sess)
- return nil, replaceErrors(err)
- }
- return newCursorWithSession(bc, coll.registry, sess)
- }
-
- // FindOne returns up to one document that matches the model.
- func (coll *Collection) FindOne(ctx context.Context, filter interface{},
- opts ...*options.FindOneOptions) *SingleResult {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- findOpts := make([]*options.FindOptions, len(opts))
- for i, opt := range opts {
- findOpts[i] = &options.FindOptions{
- AllowPartialResults: opt.AllowPartialResults,
- BatchSize: opt.BatchSize,
- Collation: opt.Collation,
- Comment: opt.Comment,
- CursorType: opt.CursorType,
- Hint: opt.Hint,
- Max: opt.Max,
- MaxAwaitTime: opt.MaxAwaitTime,
- Min: opt.Min,
- NoCursorTimeout: opt.NoCursorTimeout,
- OplogReplay: opt.OplogReplay,
- Projection: opt.Projection,
- ReturnKey: opt.ReturnKey,
- ShowRecordID: opt.ShowRecordID,
- Skip: opt.Skip,
- Snapshot: opt.Snapshot,
- Sort: opt.Sort,
- }
- }
- // Unconditionally send a limit to make sure only one document is returned and the cursor is not kept open
- // by the server.
- findOpts = append(findOpts, options.Find().SetLimit(-1))
-
- cursor, err := coll.Find(ctx, filter, findOpts...)
- return &SingleResult{cur: cursor, reg: coll.registry, err: replaceErrors(err)}
- }
-
- func (coll *Collection) findAndModify(ctx context.Context, op *operation.FindAndModify) *SingleResult {
- if ctx == nil {
- ctx = context.Background()
- }
-
- sess := sessionFromContext(ctx)
- var err error
- if sess == nil && coll.client.topology.SessionPool != nil {
- sess, err = session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return &SingleResult{err: err}
- }
- defer sess.EndSession()
- }
-
- err = coll.client.validSession(sess)
- if err != nil {
- return &SingleResult{err: err}
- }
-
- wc := coll.writeConcern
- if sess.TransactionRunning() {
- wc = nil
- }
- if !writeconcern.AckWrite(wc) {
- sess = nil
- }
-
- selector := makePinnedSelector(sess, coll.writeSelector)
-
- retry := driver.RetryNone
- if coll.client.retryWrites {
- retry = driver.RetryOnce
- }
-
- op = op.Session(sess).
- WriteConcern(wc).
- CommandMonitor(coll.client.monitor).
- ServerSelector(selector).
- ClusterClock(coll.client.clock).
- Database(coll.db.name).
- Collection(coll.name).
- Deployment(coll.client.topology).
- Retry(retry)
-
- _, err = processWriteError(op.Execute(ctx))
- if err != nil {
- return &SingleResult{err: err}
- }
-
- return &SingleResult{rdr: bson.Raw(op.Result().Value), reg: coll.registry}
- }
-
- // FindOneAndDelete find a single document and deletes it, returning the
- // original in result.
- func (coll *Collection) FindOneAndDelete(ctx context.Context, filter interface{},
- opts ...*options.FindOneAndDeleteOptions) *SingleResult {
-
- f, err := transformBsoncoreDocument(coll.registry, filter)
- if err != nil {
- return &SingleResult{err: err}
- }
- fod := options.MergeFindOneAndDeleteOptions(opts...)
- op := operation.NewFindAndModify(f).Remove(true)
- if fod.Collation != nil {
- op = op.Collation(bsoncore.Document(fod.Collation.ToDocument()))
- }
- if fod.MaxTime != nil {
- op = op.MaxTimeMS(int64(*fod.MaxTime / time.Millisecond))
- }
- if fod.Projection != nil {
- proj, err := transformBsoncoreDocument(coll.registry, fod.Projection)
- if err != nil {
- return &SingleResult{err: err}
- }
- op = op.Fields(proj)
- }
- if fod.Sort != nil {
- sort, err := transformBsoncoreDocument(coll.registry, fod.Sort)
- if err != nil {
- return &SingleResult{err: err}
- }
- op = op.Sort(sort)
- }
-
- return coll.findAndModify(ctx, op)
- }
-
- // FindOneAndReplace finds a single document and replaces it, returning either
- // the original or the replaced document.
- func (coll *Collection) FindOneAndReplace(ctx context.Context, filter interface{},
- replacement interface{}, opts ...*options.FindOneAndReplaceOptions) *SingleResult {
-
- f, err := transformBsoncoreDocument(coll.registry, filter)
- if err != nil {
- return &SingleResult{err: err}
- }
- r, err := transformBsoncoreDocument(coll.registry, replacement)
- if err != nil {
- return &SingleResult{err: err}
- }
- if firstElem, err := r.IndexErr(0); err == nil && strings.HasPrefix(firstElem.Key(), "$") {
- return &SingleResult{err: errors.New("replacement document cannot contain keys beginning with '$'")}
- }
-
- fo := options.MergeFindOneAndReplaceOptions(opts...)
- op := operation.NewFindAndModify(f).Update(bsoncore.Value{Type: bsontype.EmbeddedDocument, Data: r})
- if fo.BypassDocumentValidation != nil && *fo.BypassDocumentValidation {
- op = op.BypassDocumentValidation(*fo.BypassDocumentValidation)
- }
- if fo.Collation != nil {
- op = op.Collation(bsoncore.Document(fo.Collation.ToDocument()))
- }
- if fo.MaxTime != nil {
- op = op.MaxTimeMS(int64(*fo.MaxTime / time.Millisecond))
- }
- if fo.Projection != nil {
- proj, err := transformBsoncoreDocument(coll.registry, fo.Projection)
- if err != nil {
- return &SingleResult{err: err}
- }
- op = op.Fields(proj)
- }
- if fo.ReturnDocument != nil {
- op = op.NewDocument(*fo.ReturnDocument == options.After)
- }
- if fo.Sort != nil {
- sort, err := transformBsoncoreDocument(coll.registry, fo.Sort)
- if err != nil {
- return &SingleResult{err: err}
- }
- op = op.Sort(sort)
- }
- if fo.Upsert != nil {
- op = op.Upsert(*fo.Upsert)
- }
-
- return coll.findAndModify(ctx, op)
- }
-
- // FindOneAndUpdate finds a single document and updates it, returning either
- // the original or the updated.
- func (coll *Collection) FindOneAndUpdate(ctx context.Context, filter interface{},
- update interface{}, opts ...*options.FindOneAndUpdateOptions) *SingleResult {
-
- if ctx == nil {
- ctx = context.Background()
- }
-
- f, err := transformBsoncoreDocument(coll.registry, filter)
- if err != nil {
- return &SingleResult{err: err}
- }
-
- fo := options.MergeFindOneAndUpdateOptions(opts...)
- op := operation.NewFindAndModify(f)
-
- u, err := transformUpdateValue(coll.registry, update, true)
- if err != nil {
- return &SingleResult{err: err}
- }
- op = op.Update(u)
-
- if fo.ArrayFilters != nil {
- filtersDoc, err := fo.ArrayFilters.ToArrayDocument()
- if err != nil {
- return &SingleResult{err: err}
- }
- op = op.ArrayFilters(bsoncore.Document(filtersDoc))
- }
- if fo.BypassDocumentValidation != nil && *fo.BypassDocumentValidation {
- op = op.BypassDocumentValidation(*fo.BypassDocumentValidation)
- }
- if fo.Collation != nil {
- op = op.Collation(bsoncore.Document(fo.Collation.ToDocument()))
- }
- if fo.MaxTime != nil {
- op = op.MaxTimeMS(int64(*fo.MaxTime / time.Millisecond))
- }
- if fo.Projection != nil {
- proj, err := transformBsoncoreDocument(coll.registry, fo.Projection)
- if err != nil {
- return &SingleResult{err: err}
- }
- op = op.Fields(proj)
- }
- if fo.ReturnDocument != nil {
- op = op.NewDocument(*fo.ReturnDocument == options.After)
- }
- if fo.Sort != nil {
- sort, err := transformBsoncoreDocument(coll.registry, fo.Sort)
- if err != nil {
- return &SingleResult{err: err}
- }
- op = op.Sort(sort)
- }
- if fo.Upsert != nil {
- op = op.Upsert(*fo.Upsert)
- }
-
- return coll.findAndModify(ctx, op)
- }
-
- // Watch returns a change stream cursor used to receive notifications of changes to the collection.
- //
- // This method is preferred to running a raw aggregation with a $changeStream stage because it
- // supports resumability in the case of some errors. The collection must have read concern majority or no read concern
- // for a change stream to be created successfully.
- func (coll *Collection) Watch(ctx context.Context, pipeline interface{},
- opts ...*options.ChangeStreamOptions) (*ChangeStream, error) {
-
- csConfig := changeStreamConfig{
- readConcern: coll.readConcern,
- readPreference: coll.readPreference,
- client: coll.client,
- registry: coll.registry,
- streamType: CollectionStream,
- collectionName: coll.Name(),
- databaseName: coll.db.Name(),
- }
- return newChangeStream(ctx, csConfig, pipeline, opts...)
- }
-
- // Indexes returns the index view for this collection.
- func (coll *Collection) Indexes() IndexView {
- return IndexView{coll: coll}
- }
-
- // Drop drops this collection from database.
- func (coll *Collection) Drop(ctx context.Context) error {
- if ctx == nil {
- ctx = context.Background()
- }
-
- sess := sessionFromContext(ctx)
- if sess == nil && coll.client.topology.SessionPool != nil {
- var err error
- sess, err = session.NewClientSession(coll.client.topology.SessionPool, coll.client.id, session.Implicit)
- if err != nil {
- return err
- }
- defer sess.EndSession()
- }
-
- err := coll.client.validSession(sess)
- if err != nil {
- return err
- }
-
- wc := coll.writeConcern
- if sess.TransactionRunning() {
- wc = nil
- }
- if !writeconcern.AckWrite(wc) {
- sess = nil
- }
-
- selector := makePinnedSelector(sess, coll.writeSelector)
-
- op := operation.NewDropCollection().
- Session(sess).WriteConcern(wc).CommandMonitor(coll.client.monitor).
- ServerSelector(selector).ClusterClock(coll.client.clock).
- Database(coll.db.name).Collection(coll.name).
- Deployment(coll.client.topology)
- err = op.Execute(ctx)
-
- // ignore namespace not found erorrs
- driverErr, ok := err.(driver.Error)
- if !ok || (ok && !driverErr.NamespaceNotFound()) {
- return replaceErrors(err)
- }
- return nil
- }
-
- // makePinnedSelector makes a selector for a pinned session with a pinned server. Will attempt to do server selection on
- // the pinned server but if that fails it will go through a list of default selectors
- func makePinnedSelector(sess *session.Client, defaultSelector description.ServerSelector) description.ServerSelectorFunc {
- return func(t description.Topology, svrs []description.Server) ([]description.Server, error) {
- if sess != nil && sess.PinnedServer != nil {
- return sess.PinnedServer.SelectServer(t, svrs)
- }
-
- return defaultSelector.SelectServer(t, svrs)
- }
- }
|