1.修改代码适配阿里云的服务器

This commit is contained in:
whm
2026-03-17 14:27:32 +08:00
parent 826617d737
commit 20e7f3a65d
1777 changed files with 775041 additions and 10 deletions

View File

@@ -0,0 +1,53 @@
// 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 address provides structured representations of network addresses.
package address
import (
"net"
"strings"
)
const defaultPort = "27017"
// Address is a network address. It can either be an IP address or a DNS name.
type Address string
// Network is the network protocol for this address. In most cases this will be
// "tcp" or "unix".
func (a Address) Network() string {
if strings.HasSuffix(string(a), "sock") {
return "unix"
}
return "tcp"
}
// String is the canonical version of this address, e.g. localhost:27017,
// 1.2.3.4:27017, example.com:27017.
func (a Address) String() string {
s := string(a)
if a.Network() != "unix" {
// TODO: unicode case folding?
s = strings.ToLower(string(a))
}
if len(s) == 0 {
return ""
}
if a.Network() != "unix" {
_, _, err := net.SplitHostPort(s)
if err != nil && strings.Contains(err.Error(), "missing port in address") {
s += ":" + defaultPort
}
}
return s
}
// Canonicalize creates a canonicalized address.
func (a Address) Canonicalize() Address {
return Address(a.String())
}

View File

@@ -0,0 +1,34 @@
// 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"
// backgroundContext is an implementation of the context.Context interface that wraps a child Context. Value requests
// are forwarded to the child Context but the Done and Err functions are overridden to ensure the new context does not
// time out or get cancelled.
type backgroundContext struct {
context.Context
childValuesCtx context.Context
}
// newBackgroundContext creates a new Context whose behavior matches that of context.Background(), but Value calls are
// forwarded to the provided ctx parameter. If ctx is nil, context.Background() is returned.
func newBackgroundContext(ctx context.Context) context.Context {
if ctx == nil {
return context.Background()
}
return &backgroundContext{
Context: context.Background(),
childValuesCtx: ctx,
}
}
func (b *backgroundContext) Value(key any) any {
return b.childValuesCtx.Value(key)
}

View File

@@ -0,0 +1,70 @@
// Copyright (C) MongoDB, Inc. 2022-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"
"time"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
)
// batchCursor is the interface implemented by types that can provide batches of document results.
// The Cursor type is built on top of this type.
type batchCursor interface {
// ID returns the ID of the cursor.
ID() int64
// Next returns true if there is a batch available.
Next(context.Context) bool
// Batch will return a DocumentSequence for the current batch of documents. The returned
// DocumentSequence is only valid until the next call to Next or Close.
Batch() *bsoncore.Iterator
// Server returns a pointer to the cursor's server.
Server() driver.Server
// Err returns the last error encountered.
Err() error
// Close closes the cursor.
Close(context.Context) error
// SetBatchSize is a modifier function used to adjust the batch size of
// the cursor that implements it.
SetBatchSize(int32)
// SetMaxAwaitTime will set the maximum amount of time the server will allow
// the operations to execute. The server will error if this field is set
// but the cursor is not configured with awaitData=true.
//
// The time.Duration value passed by this setter will be converted and
// rounded down to the nearest millisecond.
SetMaxAwaitTime(time.Duration)
// SetComment will set a user-configurable comment that can be used to
// identify the operation in server logs.
SetComment(any)
// MaxAwaitTime returns the maximum amount of time the server will allow
// the operations to execute. This is only valid for tailable awaitData
// cursors.
MaxAwaitTime() *time.Duration
}
// changeStreamCursor is the interface implemented by batch cursors that also provide the functionality for retrieving
// a postBatchResumeToken from commands and allows for the cursor to be killed rather than closed
type changeStreamCursor interface {
batchCursor
// PostBatchResumeToken returns the latest seen post batch resume token.
PostBatchResumeToken() bsoncore.Document
// KillCursor kills cursor on server without closing batch cursor
KillCursor(context.Context) error
}

View File

@@ -0,0 +1,639 @@
// 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"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
)
type bulkWriteBatch struct {
models []WriteModel
canRetry bool
indexes []int
}
// bulkWrite performs a bulkwrite operation
type bulkWrite struct {
comment any
ordered *bool
bypassDocumentValidation *bool
models []WriteModel
session *session.Client
collection *Collection
selector description.ServerSelector
writeConcern *writeconcern.WriteConcern
result BulkWriteResult
let any
rawData *bool
additionalCmd bson.D
}
func (bw *bulkWrite) execute(ctx context.Context) error {
ordered := true
if bw.ordered != nil {
ordered = *bw.ordered
}
batches := createBatches(bw.models, ordered)
bw.result = BulkWriteResult{
UpsertedIDs: make(map[int64]any),
}
bwErr := BulkWriteException{
WriteErrors: make([]BulkWriteError, 0),
}
var lastErr error
continueOnError := !ordered
for _, batch := range batches {
if len(batch.models) == 0 {
continue
}
batchRes, batchErr, err := bw.runBatch(ctx, batch)
bw.mergeResults(batchRes)
bwErr.WriteConcernError = batchErr.WriteConcernError
bwErr.Labels = append(bwErr.Labels, batchErr.Labels...)
bwErr.WriteErrors = append(bwErr.WriteErrors, batchErr.WriteErrors...)
commandErrorOccurred := err != nil && !errors.Is(err, driver.ErrUnacknowledgedWrite)
writeErrorOccurred := len(batchErr.WriteErrors) > 0 || batchErr.WriteConcernError != nil
if !continueOnError && (commandErrorOccurred || writeErrorOccurred) {
if err != nil {
return err
}
return bwErr
}
if err != nil {
lastErr = err
}
}
bw.result.MatchedCount -= bw.result.UpsertedCount
rr, err := processWriteError(lastErr)
if err != nil {
return err
}
bw.result.Acknowledged = rr.isAcknowledged()
if len(bwErr.WriteErrors) > 0 || bwErr.WriteConcernError != nil {
return bwErr
}
return nil
}
func (bw *bulkWrite) runBatch(ctx context.Context, batch bulkWriteBatch) (BulkWriteResult, BulkWriteException, error) {
batchRes := BulkWriteResult{
UpsertedIDs: make(map[int64]any),
}
batchErr := BulkWriteException{}
var writeErrors []driver.WriteError
switch batch.models[0].(type) {
case *InsertOneModel:
res, err := bw.runInsert(ctx, batch)
if err != nil {
var writeErr driver.WriteCommandError
if !errors.As(err, &writeErr) {
return BulkWriteResult{}, batchErr, err
}
writeErrors = writeErr.WriteErrors
batchErr.Labels = writeErr.Labels
batchErr.WriteConcernError = convertDriverWriteConcernError(writeErr.WriteConcernError)
}
batchRes.InsertedCount = res.N
case *DeleteOneModel, *DeleteManyModel:
res, err := bw.runDelete(ctx, batch)
if err != nil {
var writeErr driver.WriteCommandError
if !errors.As(err, &writeErr) {
return BulkWriteResult{}, batchErr, err
}
writeErrors = writeErr.WriteErrors
batchErr.Labels = writeErr.Labels
batchErr.WriteConcernError = convertDriverWriteConcernError(writeErr.WriteConcernError)
}
batchRes.DeletedCount = res.N
case *ReplaceOneModel, *UpdateOneModel, *UpdateManyModel:
res, err := bw.runUpdate(ctx, batch)
if err != nil {
var writeErr driver.WriteCommandError
if !errors.As(err, &writeErr) {
return BulkWriteResult{}, batchErr, err
}
writeErrors = writeErr.WriteErrors
batchErr.Labels = writeErr.Labels
batchErr.WriteConcernError = convertDriverWriteConcernError(writeErr.WriteConcernError)
}
batchRes.MatchedCount = res.N
batchRes.ModifiedCount = res.NModified
batchRes.UpsertedCount = int64(len(res.Upserted))
for _, upsert := range res.Upserted {
batchRes.UpsertedIDs[int64(batch.indexes[upsert.Index])] = upsert.ID
}
}
batchErr.WriteErrors = make([]BulkWriteError, 0, len(writeErrors))
convWriteErrors := writeErrorsFromDriverWriteErrors(writeErrors)
for _, we := range convWriteErrors {
request := batch.models[we.Index]
we.Index = batch.indexes[we.Index]
batchErr.WriteErrors = append(batchErr.WriteErrors, BulkWriteError{
WriteError: we,
Request: request,
})
}
return batchRes, batchErr, nil
}
func (bw *bulkWrite) runInsert(ctx context.Context, batch bulkWriteBatch) (insertResult, error) {
docs := make([]bsoncore.Document, len(batch.models))
for i, model := range batch.models {
converted := model.(*InsertOneModel)
doc, err := marshal(converted.Document, bw.collection.bsonOpts, bw.collection.registry)
if err != nil {
return insertResult{}, err
}
doc, _, err = ensureID(doc, bson.NilObjectID, bw.collection.bsonOpts, bw.collection.registry)
if err != nil {
return insertResult{}, err
}
docs[i] = doc
}
op := insert{
documents: docs,
session: bw.session,
writeConcern: bw.writeConcern,
monitor: bw.collection.client.monitor,
selector: bw.selector,
clock: bw.collection.client.clock,
database: bw.collection.db.name,
collection: bw.collection.name,
deployment: bw.collection.client.deployment,
crypt: bw.collection.client.cryptFLE,
serverAPI: bw.collection.client.serverAPI,
timeout: bw.collection.client.timeout,
logger: bw.collection.client.logger,
authenticator: bw.collection.client.authenticator,
}
if bw.comment != nil {
comment, err := marshalValue(bw.comment, bw.collection.bsonOpts, bw.collection.registry)
if err != nil {
return op.Result(), err
}
op.comment = comment
}
if bw.bypassDocumentValidation != nil && *bw.bypassDocumentValidation {
op.bypassDocumentValidation = bw.bypassDocumentValidation
}
if bw.ordered != nil {
op.ordered = bw.ordered
}
retry := driver.RetryNone
if bw.collection.client.retryWrites && batch.canRetry {
retry = driver.RetryOncePerCommand
}
op.retry = &retry
if bw.rawData != nil {
op.rawData = bw.rawData
}
if len(bw.additionalCmd) > 0 {
op.additionalCmd = bw.additionalCmd
}
err := op.Execute(ctx)
return op.Result(), err
}
func (bw *bulkWrite) runDelete(ctx context.Context, batch bulkWriteBatch) (operation.DeleteResult, error) {
docs := make([]bsoncore.Document, len(batch.models))
var i int
var hasHint bool
for _, model := range batch.models {
var doc bsoncore.Document
var err error
switch converted := model.(type) {
case *DeleteOneModel:
doc, err = createDeleteDoc(
converted.Filter,
converted.Collation,
converted.Hint,
true,
bw.collection.bsonOpts,
bw.collection.registry)
hasHint = hasHint || (converted.Hint != nil)
case *DeleteManyModel:
doc, err = createDeleteDoc(
converted.Filter,
converted.Collation,
converted.Hint,
false,
bw.collection.bsonOpts,
bw.collection.registry)
hasHint = hasHint || (converted.Hint != nil)
}
if err != nil {
return operation.DeleteResult{}, err
}
docs[i] = doc
i++
}
op := operation.NewDelete(docs...).
Session(bw.session).WriteConcern(bw.writeConcern).CommandMonitor(bw.collection.client.monitor).
ServerSelector(bw.selector).ClusterClock(bw.collection.client.clock).
Database(bw.collection.db.name).Collection(bw.collection.name).
Deployment(bw.collection.client.deployment).Crypt(bw.collection.client.cryptFLE).Hint(hasHint).
ServerAPI(bw.collection.client.serverAPI).Timeout(bw.collection.client.timeout).
Logger(bw.collection.client.logger).Authenticator(bw.collection.client.authenticator)
if bw.comment != nil {
comment, err := marshalValue(bw.comment, bw.collection.bsonOpts, bw.collection.registry)
if err != nil {
return op.Result(), err
}
op.Comment(comment)
}
if bw.let != nil {
let, err := marshal(bw.let, bw.collection.bsonOpts, bw.collection.registry)
if err != nil {
return operation.DeleteResult{}, err
}
op = op.Let(let)
}
if bw.ordered != nil {
op = op.Ordered(*bw.ordered)
}
retry := driver.RetryNone
if bw.collection.client.retryWrites && batch.canRetry {
retry = driver.RetryOncePerCommand
}
op = op.Retry(retry)
if bw.rawData != nil {
op.RawData(*bw.rawData)
}
err := op.Execute(ctx)
return op.Result(), err
}
func createDeleteDoc(
filter any,
collation *options.Collation,
hint any,
deleteOne bool,
bsonOpts *options.BSONOptions,
registry *bson.Registry,
) (bsoncore.Document, error) {
if filter == nil {
return nil, fmt.Errorf("delete filter cannot be nil")
}
f, err := marshal(filter, bsonOpts, registry)
if err != nil {
return nil, err
}
var limit int32
if deleteOne {
limit = 1
}
didx, doc := bsoncore.AppendDocumentStart(nil)
doc = bsoncore.AppendDocumentElement(doc, "q", f)
doc = bsoncore.AppendInt32Element(doc, "limit", limit)
if collation != nil {
doc = bsoncore.AppendDocumentElement(doc, "collation", toDocument(collation))
}
if hint != nil {
if isUnorderedMap(hint) {
return nil, ErrMapForOrderedArgument{"hint"}
}
hintVal, err := marshalValue(hint, bsonOpts, registry)
if err != nil {
return nil, err
}
doc = bsoncore.AppendValueElement(doc, "hint", hintVal)
}
doc, _ = bsoncore.AppendDocumentEnd(doc, didx)
return doc, nil
}
func (bw *bulkWrite) runUpdate(ctx context.Context, batch bulkWriteBatch) (operation.UpdateResult, error) {
docs := make([]bsoncore.Document, len(batch.models))
var hasHint bool
var hasArrayFilters bool
for i, model := range batch.models {
var doc bsoncore.Document
var err error
switch converted := model.(type) {
case *ReplaceOneModel:
doc, err = updateDoc{
filter: converted.Filter,
update: converted.Replacement,
hint: converted.Hint,
sort: converted.Sort,
collation: converted.Collation,
upsert: converted.Upsert,
}.marshal(bw.collection.bsonOpts, bw.collection.registry)
hasHint = hasHint || (converted.Hint != nil)
case *UpdateOneModel:
doc, err = updateDoc{
filter: converted.Filter,
update: converted.Update,
hint: converted.Hint,
sort: converted.Sort,
arrayFilters: converted.ArrayFilters,
collation: converted.Collation,
upsert: converted.Upsert,
checkDollarKey: true,
}.marshal(bw.collection.bsonOpts, bw.collection.registry)
hasHint = hasHint || (converted.Hint != nil)
hasArrayFilters = hasArrayFilters || (converted.ArrayFilters != nil)
case *UpdateManyModel:
doc, err = updateDoc{
filter: converted.Filter,
update: converted.Update,
hint: converted.Hint,
arrayFilters: converted.ArrayFilters,
collation: converted.Collation,
upsert: converted.Upsert,
multi: true,
checkDollarKey: true,
}.marshal(bw.collection.bsonOpts, bw.collection.registry)
hasHint = hasHint || (converted.Hint != nil)
hasArrayFilters = hasArrayFilters || (converted.ArrayFilters != nil)
}
if err != nil {
return operation.UpdateResult{}, err
}
docs[i] = doc
}
op := operation.NewUpdate(docs...).
Session(bw.session).WriteConcern(bw.writeConcern).CommandMonitor(bw.collection.client.monitor).
ServerSelector(bw.selector).ClusterClock(bw.collection.client.clock).
Database(bw.collection.db.name).Collection(bw.collection.name).
Deployment(bw.collection.client.deployment).Crypt(bw.collection.client.cryptFLE).Hint(hasHint).
ArrayFilters(hasArrayFilters).ServerAPI(bw.collection.client.serverAPI).
Timeout(bw.collection.client.timeout).Logger(bw.collection.client.logger).
Authenticator(bw.collection.client.authenticator)
if bw.comment != nil {
comment, err := marshalValue(bw.comment, bw.collection.bsonOpts, bw.collection.registry)
if err != nil {
return op.Result(), err
}
op.Comment(comment)
}
if bw.let != nil {
let, err := marshal(bw.let, bw.collection.bsonOpts, bw.collection.registry)
if err != nil {
return operation.UpdateResult{}, err
}
op = op.Let(let)
}
if bw.ordered != nil {
op = op.Ordered(*bw.ordered)
}
if bw.bypassDocumentValidation != nil && *bw.bypassDocumentValidation {
op = op.BypassDocumentValidation(*bw.bypassDocumentValidation)
}
retry := driver.RetryNone
if bw.collection.client.retryWrites && batch.canRetry {
retry = driver.RetryOncePerCommand
}
op = op.Retry(retry)
if bw.rawData != nil {
op.RawData(*bw.rawData)
}
if len(bw.additionalCmd) > 0 {
op.AdditionalCmd(bw.additionalCmd)
}
err := op.Execute(ctx)
return op.Result(), err
}
type updateDoc struct {
filter any
update any
hint any
sort any
arrayFilters []any
collation *options.Collation
upsert *bool
multi bool
checkDollarKey bool
}
func (doc updateDoc) marshal(bsonOpts *options.BSONOptions, registry *bson.Registry) (bsoncore.Document, error) {
if doc.filter == nil {
return nil, fmt.Errorf("update filter cannot be nil")
}
f, err := marshal(doc.filter, bsonOpts, registry)
if err != nil {
return nil, err
}
uidx, updateDoc := bsoncore.AppendDocumentStart(nil)
updateDoc = bsoncore.AppendDocumentElement(updateDoc, "q", f)
u, err := marshalUpdateValue(doc.update, bsonOpts, registry, doc.checkDollarKey)
if err != nil {
return nil, err
}
updateDoc = bsoncore.AppendValueElement(updateDoc, "u", u)
if doc.multi {
updateDoc = bsoncore.AppendBooleanElement(updateDoc, "multi", doc.multi)
}
if doc.sort != nil {
if isUnorderedMap(doc.sort) {
return nil, ErrMapForOrderedArgument{"sort"}
}
s, err := marshal(doc.sort, bsonOpts, registry)
if err != nil {
return nil, err
}
updateDoc = bsoncore.AppendDocumentElement(updateDoc, "sort", s)
}
if doc.arrayFilters != nil {
reg := registry
arr, err := marshalValue(doc.arrayFilters, bsonOpts, reg)
if err != nil {
return nil, err
}
updateDoc = bsoncore.AppendArrayElement(updateDoc, "arrayFilters", arr.Data)
}
if doc.collation != nil {
updateDoc = bsoncore.AppendDocumentElement(updateDoc, "collation", bsoncore.Document(toDocument(doc.collation)))
}
if doc.upsert != nil {
updateDoc = bsoncore.AppendBooleanElement(updateDoc, "upsert", *doc.upsert)
}
if doc.hint != nil {
if isUnorderedMap(doc.hint) {
return nil, ErrMapForOrderedArgument{"hint"}
}
hintVal, err := marshalValue(doc.hint, bsonOpts, registry)
if err != nil {
return nil, err
}
updateDoc = bsoncore.AppendValueElement(updateDoc, "hint", hintVal)
}
updateDoc, _ = bsoncore.AppendDocumentEnd(updateDoc, uidx)
return updateDoc, nil
}
func createBatches(models []WriteModel, ordered bool) []bulkWriteBatch {
if ordered {
return createOrderedBatches(models)
}
batches := make([]bulkWriteBatch, 5)
batches[insertCommand].canRetry = true
batches[deleteOneCommand].canRetry = true
batches[updateOneCommand].canRetry = true
// TODO(GODRIVER-1157): fix batching once operation retryability is fixed
for i, model := range models {
switch model.(type) {
case *InsertOneModel:
batches[insertCommand].models = append(batches[insertCommand].models, model)
batches[insertCommand].indexes = append(batches[insertCommand].indexes, i)
case *DeleteOneModel:
batches[deleteOneCommand].models = append(batches[deleteOneCommand].models, model)
batches[deleteOneCommand].indexes = append(batches[deleteOneCommand].indexes, i)
case *DeleteManyModel:
batches[deleteManyCommand].models = append(batches[deleteManyCommand].models, model)
batches[deleteManyCommand].indexes = append(batches[deleteManyCommand].indexes, i)
case *ReplaceOneModel, *UpdateOneModel:
batches[updateOneCommand].models = append(batches[updateOneCommand].models, model)
batches[updateOneCommand].indexes = append(batches[updateOneCommand].indexes, i)
case *UpdateManyModel:
batches[updateManyCommand].models = append(batches[updateManyCommand].models, model)
batches[updateManyCommand].indexes = append(batches[updateManyCommand].indexes, i)
}
}
return batches
}
func createOrderedBatches(models []WriteModel) []bulkWriteBatch {
var batches []bulkWriteBatch
var prevKind writeCommandKind = -1
i := -1 // batch index
for ind, model := range models {
var createNewBatch bool
var canRetry bool
var newKind writeCommandKind
// TODO(GODRIVER-1157): fix batching once operation retryability is fixed
switch model.(type) {
case *InsertOneModel:
createNewBatch = prevKind != insertCommand
canRetry = true
newKind = insertCommand
case *DeleteOneModel:
createNewBatch = prevKind != deleteOneCommand
canRetry = true
newKind = deleteOneCommand
case *DeleteManyModel:
createNewBatch = prevKind != deleteManyCommand
newKind = deleteManyCommand
case *ReplaceOneModel, *UpdateOneModel:
createNewBatch = prevKind != updateOneCommand
canRetry = true
newKind = updateOneCommand
case *UpdateManyModel:
createNewBatch = prevKind != updateManyCommand
newKind = updateManyCommand
}
if createNewBatch {
batches = append(batches, bulkWriteBatch{
models: []WriteModel{model},
canRetry: canRetry,
indexes: []int{ind},
})
i++
} else {
batches[i].models = append(batches[i].models, model)
if !canRetry {
batches[i].canRetry = false // don't make it true if it was already false
}
batches[i].indexes = append(batches[i].indexes, ind)
}
prevKind = newKind
}
return batches
}
func (bw *bulkWrite) mergeResults(newResult BulkWriteResult) {
bw.result.InsertedCount += newResult.InsertedCount
bw.result.MatchedCount += newResult.MatchedCount
bw.result.ModifiedCount += newResult.ModifiedCount
bw.result.DeletedCount += newResult.DeletedCount
bw.result.UpsertedCount += newResult.UpsertedCount
for index, upsertID := range newResult.UpsertedIDs {
bw.result.UpsertedIDs[index] = upsertID
}
}
// WriteCommandKind is the type of command represented by a Write
type writeCommandKind int8
// These constants represent the valid types of write commands.
const (
insertCommand writeCommandKind = iota
updateOneCommand
updateManyCommand
deleteOneCommand
deleteManyCommand
)

View File

@@ -0,0 +1,342 @@
// 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 (
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
// WriteModel is an interface implemented by models that can be used in a BulkWrite operation. Each WriteModel
// represents a write.
//
// This interface is implemented by InsertOneModel, DeleteOneModel, DeleteManyModel, ReplaceOneModel, UpdateOneModel,
// and UpdateManyModel. Custom implementations of this interface must not be used.
type WriteModel interface {
writeModel()
}
// InsertOneModel is used to insert a single document in a BulkWrite operation.
//
// See corresponding setter methods for documentation.
type InsertOneModel struct {
Document any
}
// NewInsertOneModel creates a new InsertOneModel.
func NewInsertOneModel() *InsertOneModel {
return &InsertOneModel{}
}
// SetDocument specifies the document to be inserted. The document cannot be nil. If it does not have an _id field when
// transformed into BSON, one will be added automatically to the marshalled document. The original document will not be
// modified.
func (iom *InsertOneModel) SetDocument(doc any) *InsertOneModel {
iom.Document = doc
return iom
}
func (*InsertOneModel) writeModel() {}
// DeleteOneModel is used to delete at most one document in a BulkWriteOperation.
//
// See corresponding setter methods for documentation.
type DeleteOneModel struct {
Filter any
Collation *options.Collation
Hint any
}
// NewDeleteOneModel creates a new DeleteOneModel.
func NewDeleteOneModel() *DeleteOneModel {
return &DeleteOneModel{}
}
// SetFilter specifies a filter to use to select the document to delete. The filter must be a document containing query
// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
// documents.
func (dom *DeleteOneModel) SetFilter(filter any) *DeleteOneModel {
dom.Filter = filter
return dom
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (dom *DeleteOneModel) SetCollation(collation *options.Collation) *DeleteOneModel {
dom.Collation = collation
return dom
}
// SetHint specifies the index to use for the operation. This should either be
// the index name as a string or the index specification as a document. This
// option is only valid for MongoDB versions >= 4.4. Server versions < 4.4 will
// return an error if this option is specified. The driver will return an error
// if this option is specified during an unacknowledged write operation. The
// driver will return an error if the hint parameter is a multi-key map. The
// default value is nil, which means that no hint will be sent.
func (dom *DeleteOneModel) SetHint(hint any) *DeleteOneModel {
dom.Hint = hint
return dom
}
func (*DeleteOneModel) writeModel() {}
// DeleteManyModel is used to delete multiple documents in a BulkWrite operation.
//
// See corresponding setter methods for documentation.
type DeleteManyModel struct {
Filter any
Collation *options.Collation
Hint any
}
// NewDeleteManyModel creates a new DeleteManyModel.
func NewDeleteManyModel() *DeleteManyModel {
return &DeleteManyModel{}
}
// SetFilter specifies a filter to use to select documents to delete. The filter must be a document containing query
// operators. It cannot be nil.
func (dmm *DeleteManyModel) SetFilter(filter any) *DeleteManyModel {
dmm.Filter = filter
return dmm
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (dmm *DeleteManyModel) SetCollation(collation *options.Collation) *DeleteManyModel {
dmm.Collation = collation
return dmm
}
// SetHint specifies the index to use for the operation. This should either be
// the index name as a string or the index specification as a document. This
// option is only valid for MongoDB versions >= 4.4. Server versions < 4.4 will
// return an error if this option is specified. The driver will return an error
// if this option is specified during an unacknowledged write operation. The
// driver will return an error if the hint parameter is a multi-key map. The
// default value is nil, which means that no hint will be sent.
func (dmm *DeleteManyModel) SetHint(hint any) *DeleteManyModel {
dmm.Hint = hint
return dmm
}
func (*DeleteManyModel) writeModel() {}
// ReplaceOneModel is used to replace at most one document in a BulkWrite operation.
//
// See corresponding setter methods for documentation.
type ReplaceOneModel struct {
Collation *options.Collation
Upsert *bool
Filter any
Replacement any
Hint any
Sort any
}
// NewReplaceOneModel creates a new ReplaceOneModel.
func NewReplaceOneModel() *ReplaceOneModel {
return &ReplaceOneModel{}
}
// SetHint specifies the index to use for the operation. This should either be
// the index name as a string or the index specification as a document. This
// option is only valid for MongoDB versions >= 4.2. Server versions < 4.2 will
// return an error if this option is specified. The driver will return an error
// if this option is specified during an unacknowledged write operation. The
// driver will return an error if the hint parameter is a multi-key map. The
// default value is nil, which means that no hint will be sent.
func (rom *ReplaceOneModel) SetHint(hint any) *ReplaceOneModel {
rom.Hint = hint
return rom
}
// SetFilter specifies a filter to use to select the document to replace. The filter must be a document containing query
// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
// documents.
func (rom *ReplaceOneModel) SetFilter(filter any) *ReplaceOneModel {
rom.Filter = filter
return rom
}
// SetReplacement specifies a document that will be used to replace the selected document. It cannot be nil and cannot
// contain any update operators (https://www.mongodb.com/docs/manual/reference/operator/update/).
func (rom *ReplaceOneModel) SetReplacement(rep any) *ReplaceOneModel {
rom.Replacement = rep
return rom
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (rom *ReplaceOneModel) SetCollation(collation *options.Collation) *ReplaceOneModel {
rom.Collation = collation
return rom
}
// SetUpsert specifies whether or not the replacement document should be inserted if no document matching the filter is
// found. If an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the
// BulkWriteResult.
func (rom *ReplaceOneModel) SetUpsert(upsert bool) *ReplaceOneModel {
rom.Upsert = &upsert
return rom
}
// SetSort specifies which document the operation replaces if the query matches multiple documents. The first document
// matched by the sort order will be replaced. This option is only valid for MongoDB versions >= 8.0. The sort parameter
// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The
// default value is nil.
func (rom *ReplaceOneModel) SetSort(sort any) *ReplaceOneModel {
rom.Sort = sort
return rom
}
func (*ReplaceOneModel) writeModel() {}
// UpdateOneModel is used to update at most one document in a BulkWrite operation.
//
// See corresponding setter methods for documentation.
type UpdateOneModel struct {
Collation *options.Collation
Upsert *bool
Filter any
Update any
ArrayFilters []any
Hint any
Sort any
}
// NewUpdateOneModel creates a new UpdateOneModel.
func NewUpdateOneModel() *UpdateOneModel {
return &UpdateOneModel{}
}
// SetHint specifies the index to use for the operation. This should either be
// the index name as a string or the index specification as a document. This
// option is only valid for MongoDB versions >= 4.2. Server versions < 4.2 will
// return an error if this option is specified. The driver will return an error
// if this option is specified during an unacknowledged write operation. The
// driver will return an error if the hint parameter is a multi-key map. The
// default value is nil, which means that no hint will be sent.
func (uom *UpdateOneModel) SetHint(hint any) *UpdateOneModel {
uom.Hint = hint
return uom
}
// SetFilter specifies a filter to use to select the document to update. The filter must be a document containing query
// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
// documents.
func (uom *UpdateOneModel) SetFilter(filter any) *UpdateOneModel {
uom.Filter = filter
return uom
}
// SetUpdate specifies the modifications to be made to the selected document. The value must be a document containing
// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty.
func (uom *UpdateOneModel) SetUpdate(update any) *UpdateOneModel {
uom.Update = update
return uom
}
// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array
// field.
func (uom *UpdateOneModel) SetArrayFilters(filters []any) *UpdateOneModel {
uom.ArrayFilters = filters
return uom
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (uom *UpdateOneModel) SetCollation(collation *options.Collation) *UpdateOneModel {
uom.Collation = collation
return uom
}
// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If
// an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the
// BulkWriteResult.
func (uom *UpdateOneModel) SetUpsert(upsert bool) *UpdateOneModel {
uom.Upsert = &upsert
return uom
}
// SetSort specifies which document the operation updates if the query matches multiple documents. The first document
// matched by the sort order will be updated. This option is only valid for MongoDB versions >= 8.0. The sort parameter
// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The
// default value is nil.
func (uom *UpdateOneModel) SetSort(sort any) *UpdateOneModel {
uom.Sort = sort
return uom
}
func (*UpdateOneModel) writeModel() {}
// UpdateManyModel is used to update multiple documents in a BulkWrite operation.
//
// See corresponding setter methods for documentation.
type UpdateManyModel struct {
Collation *options.Collation
Upsert *bool
Filter any
Update any
ArrayFilters []any
Hint any
}
// NewUpdateManyModel creates a new UpdateManyModel.
func NewUpdateManyModel() *UpdateManyModel {
return &UpdateManyModel{}
}
// SetHint specifies the index to use for the operation. This should either be
// the index name as a string or the index specification as a document. This
// option is only valid for MongoDB versions >= 4.2. Server versions < 4.2 will
// return an error if this option is specified. The driver will return an error
// if this option is specified during an unacknowledged write operation. The
// driver will return an error if the hint parameter is a multi-key map. The
// default value is nil, which means that no hint will be sent.
func (umm *UpdateManyModel) SetHint(hint any) *UpdateManyModel {
umm.Hint = hint
return umm
}
// SetFilter specifies a filter to use to select documents to update. The filter must be a document containing query
// operators. It cannot be nil.
func (umm *UpdateManyModel) SetFilter(filter any) *UpdateManyModel {
umm.Filter = filter
return umm
}
// SetUpdate specifies the modifications to be made to the selected documents. The value must be a document containing
// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty.
func (umm *UpdateManyModel) SetUpdate(update any) *UpdateManyModel {
umm.Update = update
return umm
}
// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array
// field.
func (umm *UpdateManyModel) SetArrayFilters(filters []any) *UpdateManyModel {
umm.ArrayFilters = filters
return umm
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (umm *UpdateManyModel) SetCollation(collation *options.Collation) *UpdateManyModel {
umm.Collation = collation
return umm
}
// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If
// an upsert is performed, the _id of the upserted document can be retrieved from the UpsertedIDs field of the
// BulkWriteResult.
func (umm *UpdateManyModel) SetUpsert(upsert bool) *UpdateManyModel {
umm.Upsert = &upsert
return umm
}
func (*UpdateManyModel) writeModel() {}

View File

@@ -0,0 +1,782 @@
// 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"
"reflect"
"strconv"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/csot"
"go.mongodb.org/mongo-driver/v2/internal/driverutil"
"go.mongodb.org/mongo-driver/v2/internal/mongoutil"
"go.mongodb.org/mongo-driver/v2/internal/serverselector"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/mongo/readconcern"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/mnet"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
)
var (
// ErrMissingResumeToken indicates that a change stream notification from the server did not contain a resume token.
ErrMissingResumeToken = errors.New("cannot provide resume functionality when the resume token is missing")
// ErrNilCursor indicates that the underlying cursor for the change stream is nil.
ErrNilCursor = errors.New("cursor is nil")
minResumableLabelWireVersion int32 = 9 // Wire version at which the server includes the resumable error label
networkErrorLabel = "NetworkError"
resumableErrorLabel = "ResumableChangeStreamError"
errorCursorNotFound int32 = 43 // CursorNotFound error code
// Allowlist of error codes that are considered resumable.
resumableChangeStreamErrors = map[int32]struct{}{
6: {}, // HostUnreachable
7: {}, // HostNotFound
89: {}, // NetworkTimeout
91: {}, // ShutdownInProgress
189: {}, // PrimarySteppedDown
262: {}, // ExceededTimeLimit
9001: {}, // SocketException
10107: {}, // NotPrimary
11600: {}, // InterruptedAtShutdown
11602: {}, // InterruptedDueToReplStateChange
13435: {}, // NotPrimaryNoSecondaryOK
13436: {}, // NotPrimaryOrSecondary
63: {}, // StaleShardVersion
150: {}, // StaleEpoch
13388: {}, // StaleConfig
234: {}, // RetryChangeStream
133: {}, // FailedToSatisfyReadPreference
}
)
// ChangeStream is used to iterate over a stream of events. Each event can be decoded into a Go type via the Decode
// method or accessed as raw BSON via the Current field. This type is not goroutine safe and must not be used
// concurrently by multiple goroutines. For more information about change streams, see
// https://www.mongodb.com/docs/manual/changeStreams/.
type ChangeStream struct {
// Current is the BSON bytes of the current event. This property is only valid until the next call to Next or
// TryNext. If continued access is required, a copy must be made.
Current bson.Raw
aggregate *operation.Aggregate
pipelineSlice []bsoncore.Document
pipelineOptions map[string]bsoncore.Value
cursor changeStreamCursor
cursorOptions driver.CursorOptions
batch []bsoncore.Document
resumeToken bson.Raw
err error
sess *session.Client
client *Client
bsonOpts *options.BSONOptions
registry *bson.Registry
streamType StreamType
options *options.ChangeStreamOptions
selector description.ServerSelector
operationTime *bson.Timestamp
wireVersion *description.VersionRange
}
type changeStreamConfig struct {
readConcern *readconcern.ReadConcern
readPreference *readpref.ReadPref
client *Client
bsonOpts *options.BSONOptions
registry *bson.Registry
streamType StreamType
collectionName string
databaseName string
crypt driver.Crypt
}
func newChangeStream(ctx context.Context, config changeStreamConfig, pipeline any,
opts ...options.Lister[options.ChangeStreamOptions],
) (*ChangeStream, error) {
if ctx == nil {
ctx = context.Background()
}
cursorOpts := config.client.createBaseCursorOptions()
cursorOpts.MarshalValueEncoderFn = newEncoderFn(config.bsonOpts, config.registry)
args, err := mongoutil.NewOptions[options.ChangeStreamOptions](opts...)
if err != nil {
return nil, err
}
cs := &ChangeStream{
client: config.client,
bsonOpts: config.bsonOpts,
registry: config.registry,
streamType: config.streamType,
options: args,
selector: &serverselector.Composite{
Selectors: []description.ServerSelector{
&serverselector.ReadPref{ReadPref: config.readPreference},
&serverselector.Latency{Latency: config.client.localThreshold},
},
},
cursorOptions: cursorOpts,
}
cs.sess = sessionFromContext(ctx)
if cs.sess == nil && cs.client.sessionPool != nil {
cs.sess = session.NewImplicitClientSession(cs.client.sessionPool, cs.client.id)
}
if cs.err = cs.client.validSession(cs.sess); cs.err != nil {
closeImplicitSession(cs.sess)
return nil, cs.Err()
}
cs.aggregate = operation.NewAggregate(nil).
ReadPreference(config.readPreference).ReadConcern(config.readConcern).
Deployment(cs.client.deployment).ClusterClock(cs.client.clock).
CommandMonitor(cs.client.monitor).Session(cs.sess).ServerSelector(cs.selector).Retry(driver.RetryNone).
ServerAPI(cs.client.serverAPI).Crypt(config.crypt).Timeout(cs.client.timeout).
Authenticator(cs.client.authenticator)
if cs.options.Collation != nil {
cs.aggregate.Collation(bsoncore.Document(toDocument(cs.options.Collation)))
}
if cs.options.Comment != nil {
comment, err := marshalValue(cs.options.Comment, cs.bsonOpts, cs.registry)
if err != nil {
return nil, err
}
cs.aggregate.Comment(comment)
cs.cursorOptions.Comment = comment
}
if cs.options.BatchSize != nil {
cs.aggregate.BatchSize(*cs.options.BatchSize)
cs.cursorOptions.BatchSize = *cs.options.BatchSize
}
if cs.options.MaxAwaitTime != nil {
cs.cursorOptions.SetMaxAwaitTime(*cs.options.MaxAwaitTime)
}
if cs.options.Custom != nil {
// Marshal all custom options before passing to the initial aggregate. Return
// any errors from Marshaling.
customOptions := make(map[string]bsoncore.Value)
for optionName, optionValue := range cs.options.Custom {
optionValueBSON, err := marshalValue(optionValue, nil, cs.registry)
if err != nil {
cs.err = err
closeImplicitSession(cs.sess)
return nil, cs.Err()
}
customOptions[optionName] = optionValueBSON
}
cs.aggregate.CustomOptions(customOptions)
}
if cs.options.CustomPipeline != nil {
// Marshal all custom pipeline options before building pipeline slice. Return
// any errors from Marshaling.
cs.pipelineOptions = make(map[string]bsoncore.Value)
for optionName, optionValue := range cs.options.CustomPipeline {
optionValueBSON, err := marshalValue(optionValue, nil, cs.registry)
if err != nil {
cs.err = err
closeImplicitSession(cs.sess)
return nil, cs.Err()
}
cs.pipelineOptions[optionName] = optionValueBSON
}
}
switch cs.streamType {
case ClientStream:
cs.aggregate.Database("admin")
case DatabaseStream:
cs.aggregate.Database(config.databaseName)
case CollectionStream:
cs.aggregate.Collection(config.collectionName).Database(config.databaseName)
default:
closeImplicitSession(cs.sess)
return nil, fmt.Errorf("must supply a valid StreamType in config, instead of %v", cs.streamType)
}
// When starting a change stream, cache startAfter as the first resume token if it is set. If not, cache
// resumeAfter. If neither is set, do not cache a resume token.
resumeToken := cs.options.StartAfter
if resumeToken == nil {
resumeToken = cs.options.ResumeAfter
}
var marshaledToken bson.Raw
if resumeToken != nil {
if marshaledToken, cs.err = bson.Marshal(resumeToken); cs.err != nil {
closeImplicitSession(cs.sess)
return nil, cs.Err()
}
}
cs.resumeToken = marshaledToken
if cs.err = cs.buildPipelineSlice(pipeline); cs.err != nil {
closeImplicitSession(cs.sess)
return nil, cs.Err()
}
var pipelineArr bsoncore.Document
pipelineArr, cs.err = cs.pipelineToBSON()
cs.aggregate.Pipeline(pipelineArr)
if cs.err = cs.executeOperation(ctx, false); cs.err != nil {
closeImplicitSession(cs.sess)
return nil, cs.Err()
}
return cs, cs.Err()
}
func (cs *ChangeStream) createOperationDeployment(server driver.Server, connection *mnet.Connection) driver.Deployment {
return &changeStreamDeployment{
topologyKind: cs.client.deployment.Kind(),
server: server,
conn: connection,
}
}
func (cs *ChangeStream) executeOperation(ctx context.Context, resuming bool) error {
var server driver.Server
var conn *mnet.Connection
// Apply the client-level timeout if the operation-level timeout is not set.
ctx, cancel := csot.WithTimeout(ctx, cs.client.timeout)
defer cancel()
connCtx, cancel := csot.WithServerSelectionTimeout(ctx, cs.client.deployment.GetServerSelectionTimeout())
defer cancel()
if server, cs.err = cs.client.deployment.SelectServer(connCtx, cs.selector); cs.err != nil {
return cs.Err()
}
if conn, cs.err = server.Connection(connCtx); cs.err != nil {
return cs.Err()
}
defer conn.Close()
cs.wireVersion = conn.Description().WireVersion
cs.aggregate.Deployment(cs.createOperationDeployment(server, conn))
if resuming {
cs.replaceOptions(cs.wireVersion)
csOptDoc, err := cs.createPipelineOptionsDoc()
if err != nil {
return err
}
pipIdx, pipDoc := bsoncore.AppendDocumentStart(nil)
pipDoc = bsoncore.AppendDocumentElement(pipDoc, "$changeStream", csOptDoc)
if pipDoc, cs.err = bsoncore.AppendDocumentEnd(pipDoc, pipIdx); cs.err != nil {
return cs.Err()
}
cs.pipelineSlice[0] = pipDoc
var plArr bsoncore.Document
if plArr, cs.err = cs.pipelineToBSON(); cs.err != nil {
return cs.Err()
}
cs.aggregate.Pipeline(plArr)
}
// Execute the aggregate, retrying on retryable errors once (1) if retryable reads are enabled and
// infinitely (-1) if context is a Timeout context.
var retries int
if cs.client.retryReads {
retries = 1
}
if csot.IsTimeoutContext(ctx) {
retries = -1
}
var err error
AggregateExecuteLoop:
for {
err = cs.aggregate.Execute(ctx)
// If no error or no retries remain, do not retry.
if err == nil || retries == 0 {
break AggregateExecuteLoop
}
var tt driver.Error
if errors.As(err, &tt) {
// If error is not retryable, do not retry.
if !tt.RetryableRead() {
break AggregateExecuteLoop
}
connCtx, cancel := csot.WithServerSelectionTimeout(ctx, cs.client.deployment.GetServerSelectionTimeout())
defer cancel()
// If error is retryable: subtract 1 from retries, redo server selection, checkout
// a connection, and restart loop.
retries--
server, err = cs.client.deployment.SelectServer(connCtx, cs.selector)
if err != nil {
break AggregateExecuteLoop
}
conn.Close()
conn, err = server.Connection(connCtx)
if err != nil {
break AggregateExecuteLoop
}
defer conn.Close()
// Update the wire version with data from the new connection.
cs.wireVersion = conn.Description().WireVersion
// Reset deployment.
cs.aggregate.Deployment(cs.createOperationDeployment(server, conn))
} else {
// Do not retry if error is not a driver error.
break AggregateExecuteLoop
}
}
if err != nil {
cs.err = wrapErrors(err)
return cs.err
}
cr := cs.aggregate.ResultCursorResponse()
cr.Server = server
cs.cursor, cs.err = driver.NewBatchCursor(cr, cs.sess, cs.client.clock, cs.cursorOptions)
if cs.err = wrapErrors(cs.err); cs.err != nil {
return cs.Err()
}
cs.updatePbrtFromCommand()
if cs.options.StartAtOperationTime == nil && cs.options.ResumeAfter == nil &&
cs.options.StartAfter == nil && cs.wireVersion.Max >= 7 &&
cs.emptyBatch() && cs.resumeToken == nil {
cs.operationTime = cs.sess.OperationTime
}
return cs.Err()
}
// Updates the post batch resume token after a successful aggregate or getMore operation.
func (cs *ChangeStream) updatePbrtFromCommand() {
// Only cache the pbrt if an empty batch was returned and a pbrt was included
if pbrt := cs.cursor.PostBatchResumeToken(); cs.emptyBatch() && pbrt != nil {
cs.resumeToken = bson.Raw(pbrt)
}
}
func (cs *ChangeStream) storeResumeToken() error {
// If cs.Current is the last document in the batch and a pbrt is included, cache the pbrt
// Otherwise, cache the _id of the document
var tokenDoc bson.Raw
if len(cs.batch) == 0 {
if pbrt := cs.cursor.PostBatchResumeToken(); pbrt != nil {
tokenDoc = bson.Raw(pbrt)
}
}
if tokenDoc == nil {
var ok bool
tokenDoc, ok = cs.Current.Lookup("_id").DocumentOK()
if !ok {
_ = cs.Close(context.Background())
return ErrMissingResumeToken
}
}
cs.resumeToken = tokenDoc
return nil
}
func (cs *ChangeStream) buildPipelineSlice(pipeline any) error {
val := reflect.ValueOf(pipeline)
if !val.IsValid() || (val.Kind() != reflect.Slice) {
cs.err = errors.New("can only marshal slices and arrays into aggregation pipelines, but got invalid")
return cs.err
}
cs.pipelineSlice = make([]bsoncore.Document, 0, val.Len()+1)
csIdx, csDoc := bsoncore.AppendDocumentStart(nil)
csDocTemp, err := cs.createPipelineOptionsDoc()
if err != nil {
return err
}
csDoc = bsoncore.AppendDocumentElement(csDoc, "$changeStream", csDocTemp)
csDoc, cs.err = bsoncore.AppendDocumentEnd(csDoc, csIdx)
if cs.err != nil {
return cs.err
}
cs.pipelineSlice = append(cs.pipelineSlice, csDoc)
for i := 0; i < val.Len(); i++ {
var elem []byte
elem, cs.err = marshal(val.Index(i).Interface(), cs.bsonOpts, cs.registry)
if cs.err != nil {
return cs.err
}
cs.pipelineSlice = append(cs.pipelineSlice, elem)
}
return cs.err
}
func (cs *ChangeStream) createPipelineOptionsDoc() (bsoncore.Document, error) {
plDocIdx, plDoc := bsoncore.AppendDocumentStart(nil)
if cs.streamType == ClientStream {
plDoc = bsoncore.AppendBooleanElement(plDoc, "allChangesForCluster", true)
}
if cs.options.FullDocument != nil && *cs.options.FullDocument != options.Default {
plDoc = bsoncore.AppendStringElement(plDoc, "fullDocument", string(*cs.options.FullDocument))
}
if cs.options.FullDocumentBeforeChange != nil {
plDoc = bsoncore.AppendStringElement(plDoc, "fullDocumentBeforeChange", string(*cs.options.FullDocumentBeforeChange))
}
if cs.options.ResumeAfter != nil {
var raDoc bsoncore.Document
raDoc, cs.err = marshal(cs.options.ResumeAfter, cs.bsonOpts, cs.registry)
if cs.err != nil {
return nil, cs.err
}
plDoc = bsoncore.AppendDocumentElement(plDoc, "resumeAfter", raDoc)
}
if cs.options.ShowExpandedEvents != nil {
plDoc = bsoncore.AppendBooleanElement(plDoc, "showExpandedEvents", *cs.options.ShowExpandedEvents)
}
if cs.options.StartAfter != nil {
var saDoc bsoncore.Document
saDoc, cs.err = marshal(cs.options.StartAfter, cs.bsonOpts, cs.registry)
if cs.err != nil {
return nil, cs.err
}
plDoc = bsoncore.AppendDocumentElement(plDoc, "startAfter", saDoc)
}
if cs.options.StartAtOperationTime != nil {
plDoc = bsoncore.AppendTimestampElement(plDoc, "startAtOperationTime", cs.options.StartAtOperationTime.T, cs.options.StartAtOperationTime.I)
}
// Append custom pipeline options.
for optionName, optionValue := range cs.pipelineOptions {
plDoc = bsoncore.AppendValueElement(plDoc, optionName, optionValue)
}
if plDoc, cs.err = bsoncore.AppendDocumentEnd(plDoc, plDocIdx); cs.err != nil {
return nil, cs.err
}
return plDoc, nil
}
func (cs *ChangeStream) pipelineToBSON() (bsoncore.Document, error) {
pipelineDocIdx, pipelineArr := bsoncore.AppendArrayStart(nil)
for i, doc := range cs.pipelineSlice {
pipelineArr = bsoncore.AppendDocumentElement(pipelineArr, strconv.Itoa(i), doc)
}
if pipelineArr, cs.err = bsoncore.AppendArrayEnd(pipelineArr, pipelineDocIdx); cs.err != nil {
return nil, cs.err
}
return pipelineArr, cs.err
}
func (cs *ChangeStream) replaceOptions(wireVersion *description.VersionRange) {
// Cached resume token: use the resume token as the resumeAfter option and set no other resume options
if cs.resumeToken != nil {
cs.options.ResumeAfter = cs.resumeToken
cs.options.StartAfter = nil
cs.options.StartAtOperationTime = nil
return
}
// No cached resume token but cached operation time: use the operation time as the startAtOperationTime option and
// set no other resume options
if (cs.sess.OperationTime != nil || cs.options.StartAtOperationTime != nil) && wireVersion.Max >= 7 {
opTime := cs.options.StartAtOperationTime
if cs.operationTime != nil {
opTime = cs.sess.OperationTime
}
cs.options.StartAtOperationTime = opTime
cs.options.ResumeAfter = nil
cs.options.StartAfter = nil
return
}
// No cached resume token or operation time: set none of the resume options
cs.options.ResumeAfter = nil
cs.options.StartAfter = nil
cs.options.StartAtOperationTime = nil
}
// ID returns the ID for this change stream, or 0 if the cursor has been closed or exhausted.
func (cs *ChangeStream) ID() int64 {
if cs.cursor == nil {
return 0
}
return cs.cursor.ID()
}
// RemainingBatchLength returns the number of documents left in the current batch. If this returns zero, the subsequent
// call to Next or TryNext will do a network request to fetch the next batch.
func (cs *ChangeStream) RemainingBatchLength() int {
return len(cs.batch)
}
// SetBatchSize sets the number of documents to fetch from the database with
// each iteration of the ChangeStream's "Next" or "TryNext" method. This setting
// only affects subsequent document batches fetched from the database.
func (cs *ChangeStream) SetBatchSize(size int32) {
// Set batch size on the cursor options also so any "resumed" change stream
// cursors will pick up the latest batch size setting.
cs.cursorOptions.BatchSize = size
cs.cursor.SetBatchSize(size)
}
// Decode will unmarshal the current event document into val and return any errors from the unmarshalling process
// without any modification. If val is nil or is a typed nil, an error will be returned.
func (cs *ChangeStream) Decode(val any) error {
if cs.cursor == nil {
return ErrNilCursor
}
dec := getDecoder(cs.Current, cs.bsonOpts, cs.registry)
return dec.Decode(val)
}
// Err returns the last error seen by the change stream, or nil if no errors has occurred.
func (cs *ChangeStream) Err() error {
if cs.err != nil {
return wrapErrors(cs.err)
}
if cs.cursor == nil {
return nil
}
return wrapErrors(cs.cursor.Err())
}
// Close closes this change stream and the underlying cursor. Next and TryNext must not be called after Close has been
// called. Close is idempotent. After the first call, any subsequent calls will not change the state.
func (cs *ChangeStream) Close(ctx context.Context) error {
if ctx == nil {
ctx = context.Background()
}
defer closeImplicitSession(cs.sess)
if cs.cursor == nil {
return nil // cursor is already closed
}
cs.err = wrapErrors(cs.cursor.Close(ctx))
cs.cursor = nil
return cs.Err()
}
// ResumeToken returns the last cached resume token for this change stream, or nil if a resume token has not been
// stored.
func (cs *ChangeStream) ResumeToken() bson.Raw {
return cs.resumeToken
}
// Next gets the next event for this change stream. It returns true if there
// were no errors and the next event document is available.
//
// Next blocks until an event is available, an error occurs, or ctx expires.
// If ctx expires, the error will be set to ctx.Err(). In an error case, Next
// will return false.
//
// If Next returns false, subsequent calls will also return false.
func (cs *ChangeStream) Next(ctx context.Context) bool {
return cs.next(ctx, false)
}
// TryNext attempts to get the next event for this change stream. It returns
// true if there were no errors and the next event document is available.
//
// TryNext returns false if the change stream is closed by the server, an error
// occurs when getting changes from the server, the next change is not yet
// available, or ctx expires.
//
// If ctx expires, the error will be set to ctx.Err(). Users can either call
// TryNext again or close the existing change stream and create a new one. It is
// suggested to close and re-create the stream with ah higher timeout if the
// timeout occurs before any events have been received, which is a signal that
// the server is timing out before it can finish processing the existing oplog.
//
// If TryNext returns false and an error occurred or the change stream was
// closed (i.e. cs.Err() != nil || cs.ID() == 0), subsequent attempts will also
// return false. Otherwise, it is safe to call TryNext again until a change is
// available.
//
// This method requires driver version >= 1.2.0.
func (cs *ChangeStream) TryNext(ctx context.Context) bool {
return cs.next(ctx, true)
}
func (cs *ChangeStream) next(ctx context.Context, nonBlocking bool) bool {
// return false right away if the change stream has already errored or if cursor is closed.
if cs.err != nil {
return false
}
if ctx == nil {
ctx = context.Background()
}
if len(cs.batch) == 0 {
cs.loopNext(ctx, nonBlocking)
if cs.err != nil {
cs.err = wrapErrors(cs.err)
return false
}
if len(cs.batch) == 0 {
return false
}
}
// successfully got non-empty batch
cs.Current = bson.Raw(cs.batch[0])
cs.batch = cs.batch[1:]
if cs.err = cs.storeResumeToken(); cs.err != nil {
return false
}
return true
}
func (cs *ChangeStream) loopNext(ctx context.Context, nonBlocking bool) {
// To avoid unnecessary socket timeouts, we attempt to short-circuit tailable
// awaitData "getMore" operations by ensuring that the maxAwaitTimeMS is less
// than the operation timeout.
//
// The specifications assume that drivers iteratively apply the timeout
// provided at the constructor level (e.g., (*collection).Find) for tailable
// awaitData cursors:
//
// If set, drivers MUST apply the timeoutMS option to the initial aggregate
// operation. Drivers MUST also apply the original timeoutMS value to each
// next call on the change stream but MUST NOT use it to derive a maxTimeMS
// field for getMore commands.
//
// The Go Driver might decide to support the above behavior with DRIVERS-2722.
// The principal concern is that it would be unexpected for users to apply an
// operation-level timeout via contexts to a constructor and then that timeout
// later be applied while working with a resulting cursor. Instead, it is more
// idiomatic to apply the timeout to the context passed to Next or TryNext.
if cs.options != nil && !nonBlocking {
maxAwaitTime := cs.cursorOptions.MaxAwaitTime
// If maxAwaitTime is not set, this check is unnecessary.
if maxAwaitTime != nil && !mongoutil.TimeoutWithinContext(ctx, *maxAwaitTime) {
cs.err = fmt.Errorf("MaxAwaitTime must be less than the operation timeout")
return
}
}
// Apply the client-level timeout if the operation-level timeout is not set.
// This calculation is also done in "executeOperation" but cursor.Next is also
// blocking and should honor client-level timeouts.
ctx, cancel := csot.WithTimeout(ctx, cs.client.timeout)
defer cancel()
for {
if cs.cursor == nil {
return
}
if cs.cursor.Next(ctx) {
// non-empty batch returned
cs.batch, cs.err = cs.cursor.Batch().Documents()
return
}
cs.err = wrapErrors(cs.cursor.Err())
if cs.err == nil {
// Check if cursor is alive
if cs.ID() == 0 {
return
}
// If a getMore was done but the batch was empty, the batch cursor will return false with no error.
// Update the tracked resume token to catch the post batch resume token from the server response.
cs.updatePbrtFromCommand()
if nonBlocking {
// stop after a successful getMore, even though the batch was empty
return
}
continue // loop getMore until a non-empty batch is returned or an error occurs
}
if !cs.isResumableError() {
return
}
// ignore error from cursor close because if the cursor is deleted or errors we tried to close it and will remake and try to get next batch
_ = cs.cursor.Close(ctx)
if cs.err = cs.executeOperation(ctx, true); cs.err != nil {
return
}
}
}
func (cs *ChangeStream) isResumableError() bool {
var commandErr CommandError
if !errors.As(cs.err, &commandErr) || commandErr.HasErrorLabel(networkErrorLabel) {
// All non-server errors or network errors are resumable.
return true
}
if commandErr.Code == errorCursorNotFound {
return true
}
// For wire versions 9 and above, a server error is resumable if it has the ResumableChangeStreamError label.
if cs.wireVersion != nil && driverutil.VersionRangeIncludes(*cs.wireVersion, minResumableLabelWireVersion) {
return commandErr.HasErrorLabel(resumableErrorLabel)
}
// For wire versions below 9, a server error is resumable if its code is on the allowlist.
_, resumable := resumableChangeStreamErrors[commandErr.Code]
return resumable
}
// Returns true if the underlying cursor's batch is empty
func (cs *ChangeStream) emptyBatch() bool {
return cs.cursor.Batch().Empty()
}
// StreamType represents the cluster type against which a ChangeStream was created.
type StreamType uint8
// These constants represent valid change stream types. A change stream can be initialized over a collection, all
// collections in a database, or over a cluster.
const (
CollectionStream StreamType = iota
DatabaseStream
ClientStream
)

View File

@@ -0,0 +1,59 @@
// 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"
"time"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/mnet"
)
type changeStreamDeployment struct {
topologyKind description.TopologyKind
server driver.Server
conn *mnet.Connection
}
var (
_ driver.Deployment = (*changeStreamDeployment)(nil)
_ driver.Server = (*changeStreamDeployment)(nil)
_ driver.ErrorProcessor = (*changeStreamDeployment)(nil)
)
func (c *changeStreamDeployment) SelectServer(context.Context, description.ServerSelector) (driver.Server, error) {
return c, nil
}
func (c *changeStreamDeployment) Kind() description.TopologyKind {
return c.topologyKind
}
func (c *changeStreamDeployment) Connection(context.Context) (*mnet.Connection, error) {
return c.conn, nil
}
func (c *changeStreamDeployment) RTTMonitor() driver.RTTMonitor {
return c.server.RTTMonitor()
}
func (c *changeStreamDeployment) ProcessError(err error, describer mnet.Describer) driver.ProcessErrorResult {
ep, ok := c.server.(driver.ErrorProcessor)
if !ok {
return driver.NoChange
}
return ep.ProcessError(err, describer)
}
// GetServerSelectionTimeout returns zero as a server selection timeout is not
// applicable for change stream deployments.
func (*changeStreamDeployment) GetServerSelectionTimeout() time.Duration {
return 0
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,731 @@
// Copyright (C) MongoDB, Inc. 2024-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"
"io"
"strconv"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/driverutil"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/wiremessage"
)
const (
database = "admin"
)
type clientBulkWritePair struct {
namespace string
model any
}
type clientBulkWrite struct {
writePairs []clientBulkWritePair
errorsOnly bool
ordered *bool
bypassDocumentValidation *bool
comment any
let any
session *session.Client
client *Client
selector description.ServerSelector
writeConcern *writeconcern.WriteConcern
rawData *bool
additionalCmd bson.D
result ClientBulkWriteResult
}
func (bw *clientBulkWrite) execute(ctx context.Context) error {
if len(bw.writePairs) == 0 {
return fmt.Errorf("invalid writes: %w", ErrEmptySlice)
}
for i, m := range bw.writePairs {
if m.model == nil {
return fmt.Errorf("error from model at index %d: %w", i, ErrNilDocument)
}
}
batches := &modelBatches{
session: bw.session,
client: bw.client,
ordered: bw.ordered == nil || *bw.ordered,
writePairs: bw.writePairs,
result: &bw.result,
retryMode: driver.RetryOnce,
}
err := driver.Operation{
CommandFn: bw.newCommand(),
ProcessResponseFn: batches.processResponse,
Client: bw.session,
Clock: bw.client.clock,
RetryMode: &batches.retryMode,
Type: driver.Write,
Batches: batches,
CommandMonitor: bw.client.monitor,
Database: database,
Deployment: bw.client.deployment,
Selector: bw.selector,
WriteConcern: bw.writeConcern,
Crypt: bw.client.cryptFLE,
ServerAPI: bw.client.serverAPI,
Timeout: bw.client.timeout,
Logger: bw.client.logger,
Authenticator: bw.client.authenticator,
Name: driverutil.BulkWriteOp,
}.Execute(ctx)
var exception *ClientBulkWriteException
var ce CommandError
if errors.As(err, &ce) {
exception = &ClientBulkWriteException{
WriteError: &WriteError{
Code: int(ce.Code),
Message: ce.Message,
Raw: ce.Raw,
},
}
}
if len(batches.writeConcernErrors) > 0 || len(batches.writeErrors) > 0 {
if exception == nil {
exception = new(ClientBulkWriteException)
}
exception.WriteConcernErrors = batches.writeConcernErrors
exception.WriteErrors = batches.writeErrors
}
if exception != nil {
var hasSuccess bool
if batches.ordered {
_, ok := batches.writeErrors[0]
hasSuccess = !ok
} else {
hasSuccess = len(batches.writeErrors) < len(bw.writePairs)
}
if hasSuccess {
exception.PartialResult = batches.result
}
return *exception
}
return err
}
func (bw *clientBulkWrite) newCommand() func([]byte, description.SelectedServer) ([]byte, error) {
return func(dst []byte, desc description.SelectedServer) ([]byte, error) {
dst = bsoncore.AppendInt32Element(dst, "bulkWrite", 1)
dst = bsoncore.AppendBooleanElement(dst, "errorsOnly", bw.errorsOnly)
if bw.bypassDocumentValidation != nil && (desc.WireVersion != nil && driverutil.VersionRangeIncludes(*desc.WireVersion, 4)) {
dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *bw.bypassDocumentValidation)
}
if bw.comment != nil {
comment, err := marshalValue(bw.comment, bw.client.bsonOpts, bw.client.registry)
if err != nil {
return nil, err
}
dst = bsoncore.AppendValueElement(dst, "comment", comment)
}
dst = bsoncore.AppendBooleanElement(dst, "ordered", bw.ordered == nil || *bw.ordered)
if bw.let != nil {
let, err := marshal(bw.let, bw.client.bsonOpts, bw.client.registry)
if err != nil {
return nil, err
}
dst = bsoncore.AppendDocumentElement(dst, "let", let)
}
// Set rawData for 8.2+ servers.
if bw.rawData != nil && desc.WireVersion != nil && driverutil.VersionRangeIncludes(*desc.WireVersion, 27) {
dst = bsoncore.AppendBooleanElement(dst, "rawData", *bw.rawData)
}
if len(bw.additionalCmd) > 0 {
doc, err := bson.Marshal(bw.additionalCmd)
if err != nil {
return nil, err
}
dst = append(dst, doc[4:len(doc)-1]...)
}
return dst, nil
}
}
type cursorInfo struct {
Ok bool
Idx int32
Code *int32
Errmsg *string
ErrInfo bson.Raw
N int32
NModified *int32
Upserted *struct {
ID any `bson:"_id"`
}
}
func (cur *cursorInfo) extractError() *WriteError {
if cur.Ok {
return nil
}
err := &WriteError{
Index: int(cur.Idx),
Details: cur.ErrInfo,
}
if cur.Code != nil {
err.Code = int(*cur.Code)
}
if cur.Errmsg != nil {
err.Message = *cur.Errmsg
}
return err
}
type modelBatches struct {
session *session.Client
client *Client
ordered bool
writePairs []clientBulkWritePair
offset int
retryMode driver.RetryMode // RetryNone by default
cursorHandlers []func(*cursorInfo, bson.Raw) bool
newIDMap map[int]any
result *ClientBulkWriteResult
writeConcernErrors []WriteConcernError
writeErrors map[int]WriteError
}
var _ driver.OperationBatches = &modelBatches{}
func (mb *modelBatches) IsOrdered() *bool {
return &mb.ordered
}
func (mb *modelBatches) AdvanceBatches(n int) {
mb.offset += n
if mb.offset > len(mb.writePairs) {
mb.offset = len(mb.writePairs)
}
}
func (mb *modelBatches) Size() int {
if mb.offset > len(mb.writePairs) {
return 0
}
return len(mb.writePairs) - mb.offset
}
func (mb *modelBatches) AppendBatchSequence(dst []byte, maxCount, totalSize int) (int, []byte, error) {
fn := functionSet{
appendStart: func(dst []byte, identifier string) (int32, []byte) {
var idx int32
dst = wiremessage.AppendMsgSectionType(dst, wiremessage.DocumentSequence)
idx, dst = bsoncore.ReserveLength(dst)
dst = append(dst, identifier...)
dst = append(dst, 0x00)
return idx, dst
},
appendDocument: func(dst []byte, _ string, doc []byte) []byte {
dst = append(dst, doc...)
return dst
},
updateLength: func(dst []byte, idx, length int32) []byte {
dst = bsoncore.UpdateLength(dst, idx, length)
return dst
},
}
return mb.appendBatches(fn, dst, maxCount, totalSize)
}
func (mb *modelBatches) AppendBatchArray(dst []byte, maxCount, totalSize int) (int, []byte, error) {
fn := functionSet{
appendStart: bsoncore.AppendArrayElementStart,
appendDocument: bsoncore.AppendDocumentElement,
updateLength: func(dst []byte, idx, _ int32) []byte {
dst, _ = bsoncore.AppendArrayEnd(dst, idx)
return dst
},
}
return mb.appendBatches(fn, dst, maxCount, totalSize)
}
type functionSet struct {
appendStart func([]byte, string) (int32, []byte)
appendDocument func([]byte, string, []byte) []byte
updateLength func([]byte, int32, int32) []byte
}
func (mb *modelBatches) appendBatches(fn functionSet, dst []byte, maxCount, totalSize int) (int, []byte, error) {
if mb.Size() == 0 {
return 0, dst, io.EOF
}
mb.cursorHandlers = mb.cursorHandlers[:0]
mb.newIDMap = make(map[int]any)
nsMap := make(map[string]int)
getNsIndex := func(namespace string) (int, bool) {
v, ok := nsMap[namespace]
if ok {
return v, ok
}
nsIdx := len(nsMap)
nsMap[namespace] = nsIdx
return nsIdx, ok
}
canRetry := true
l := len(dst)
opsIdx, dst := fn.appendStart(dst, "ops")
nsIdx, nsDst := fn.appendStart(nil, "nsInfo")
totalSize -= 1000
size := len(dst) + len(nsDst)
var n int
for i := mb.offset; i < len(mb.writePairs); i++ {
if n == maxCount {
break
}
ns := mb.writePairs[i].namespace
nsIdx, exists := getNsIndex(ns)
var doc bsoncore.Document
var err error
switch model := mb.writePairs[i].model.(type) {
case *ClientInsertOneModel:
mb.cursorHandlers = append(mb.cursorHandlers, mb.appendInsertResult)
var id any
id, doc, err = (&clientInsertDoc{
namespace: nsIdx,
document: model.Document,
}).marshal(mb.client.bsonOpts, mb.client.registry)
if err != nil {
break
}
mb.newIDMap[i] = id
case *ClientUpdateOneModel:
mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult)
doc, err = (&clientUpdateDoc{
namespace: nsIdx,
filter: model.Filter,
update: model.Update,
hint: model.Hint,
arrayFilters: model.ArrayFilters,
collation: model.Collation,
upsert: model.Upsert,
sort: model.Sort,
multi: false,
checkDollarKey: true,
}).marshal(mb.client.bsonOpts, mb.client.registry)
case *ClientUpdateManyModel:
canRetry = false
mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult)
doc, err = (&clientUpdateDoc{
namespace: nsIdx,
filter: model.Filter,
update: model.Update,
hint: model.Hint,
arrayFilters: model.ArrayFilters,
collation: model.Collation,
upsert: model.Upsert,
multi: true,
checkDollarKey: true,
}).marshal(mb.client.bsonOpts, mb.client.registry)
case *ClientReplaceOneModel:
mb.cursorHandlers = append(mb.cursorHandlers, mb.appendUpdateResult)
doc, err = (&clientUpdateDoc{
namespace: nsIdx,
filter: model.Filter,
update: model.Replacement,
hint: model.Hint,
arrayFilters: nil,
collation: model.Collation,
upsert: model.Upsert,
sort: model.Sort,
multi: false,
checkDollarKey: false,
}).marshal(mb.client.bsonOpts, mb.client.registry)
case *ClientDeleteOneModel:
mb.cursorHandlers = append(mb.cursorHandlers, mb.appendDeleteResult)
doc, err = (&clientDeleteDoc{
namespace: nsIdx,
filter: model.Filter,
collation: model.Collation,
hint: model.Hint,
multi: false,
}).marshal(mb.client.bsonOpts, mb.client.registry)
case *ClientDeleteManyModel:
canRetry = false
mb.cursorHandlers = append(mb.cursorHandlers, mb.appendDeleteResult)
doc, err = (&clientDeleteDoc{
namespace: nsIdx,
filter: model.Filter,
collation: model.Collation,
hint: model.Hint,
multi: true,
}).marshal(mb.client.bsonOpts, mb.client.registry)
default:
mb.cursorHandlers = append(mb.cursorHandlers, nil)
}
if err != nil {
return 0, nil, err
}
length := len(doc)
if !exists {
length += len(ns)
}
size += length
if size >= totalSize {
break
}
dst = fn.appendDocument(dst, strconv.Itoa(n), doc)
if !exists {
idx, doc := bsoncore.AppendDocumentStart(nil)
doc = bsoncore.AppendStringElement(doc, "ns", ns)
doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
nsDst = fn.appendDocument(nsDst, strconv.Itoa(n), doc)
}
n++
}
if n == 0 {
return 0, dst[:l], nil
}
dst = fn.updateLength(dst, opsIdx, int32(len(dst[opsIdx:])))
nsDst = fn.updateLength(nsDst, nsIdx, int32(len(nsDst[nsIdx:])))
dst = append(dst, nsDst...)
mb.retryMode = driver.RetryNone
if mb.client.retryWrites && canRetry {
mb.retryMode = driver.RetryOnce
}
return n, dst, nil
}
func (mb *modelBatches) processResponse(ctx context.Context, resp bsoncore.Document, info driver.ResponseInfo) error {
var writeCmdErr driver.WriteCommandError
if errors.As(info.Error, &writeCmdErr) && writeCmdErr.WriteConcernError != nil {
wce := convertDriverWriteConcernError(writeCmdErr.WriteConcernError)
if wce != nil {
mb.writeConcernErrors = append(mb.writeConcernErrors, *wce)
}
}
if len(resp) == 0 {
return nil
}
var res struct {
Ok bool
Cursor bsoncore.Document
NDeleted int32
NInserted int32
NMatched int32
NModified int32
NUpserted int32
NErrors int32
Code int32
Errmsg string
}
err := bson.Unmarshal(resp, &res)
if err != nil {
return err
}
if !res.Ok {
return ClientBulkWriteException{
WriteError: &WriteError{
Code: int(res.Code),
Message: res.Errmsg,
Raw: bson.Raw(resp),
},
WriteConcernErrors: mb.writeConcernErrors,
WriteErrors: mb.writeErrors,
PartialResult: mb.result,
}
}
if mb.result.Acknowledged {
mb.result.DeletedCount += int64(res.NDeleted)
mb.result.InsertedCount += int64(res.NInserted)
mb.result.MatchedCount += int64(res.NMatched)
mb.result.ModifiedCount += int64(res.NModified)
mb.result.UpsertedCount += int64(res.NUpserted)
}
var cursorRes driver.CursorResponse
cursorRes, err = driver.NewCursorResponse(res.Cursor, info)
if err != nil {
return err
}
var bCursor *driver.BatchCursor
bCursor, err = driver.NewBatchCursor(cursorRes, mb.session, mb.client.clock,
driver.CursorOptions{
CommandMonitor: mb.client.monitor,
Crypt: mb.client.cryptFLE,
ServerAPI: mb.client.serverAPI,
MarshalValueEncoderFn: newEncoderFn(mb.client.bsonOpts, mb.client.registry),
},
)
if err != nil {
return err
}
var cursor *Cursor
cursor, err = newCursor(bCursor, mb.client.bsonOpts, mb.client.registry,
// This op doesn't return a cursor to the user, so setting the client
// timeout should be a no-op.
withCursorOptionClientTimeout(mb.client.timeout))
if err != nil {
return err
}
defer cursor.Close(ctx)
ok := true
for cursor.Next(ctx) {
var cur cursorInfo
err = cursor.Decode(&cur)
if err != nil {
return err
}
if int(cur.Idx) >= len(mb.cursorHandlers) {
continue
}
ok = mb.cursorHandlers[int(cur.Idx)](&cur, cursor.Current) && ok
}
err = cursor.Err()
if err != nil {
return err
}
if mb.ordered && (writeCmdErr.WriteConcernError != nil || !ok || !res.Ok || res.NErrors > 0) {
return ClientBulkWriteException{
WriteConcernErrors: mb.writeConcernErrors,
WriteErrors: mb.writeErrors,
PartialResult: mb.result,
}
}
return nil
}
func (mb *modelBatches) appendDeleteResult(cur *cursorInfo, raw bson.Raw) bool {
idx := int(cur.Idx) + mb.offset
if err := cur.extractError(); err != nil {
err.Raw = raw
if mb.writeErrors == nil {
mb.writeErrors = make(map[int]WriteError)
}
mb.writeErrors[idx] = *err
return false
}
if mb.result.Acknowledged {
if mb.result.DeleteResults == nil {
mb.result.DeleteResults = make(map[int]ClientBulkWriteDeleteResult)
}
mb.result.DeleteResults[idx] = ClientBulkWriteDeleteResult{int64(cur.N)}
}
return true
}
func (mb *modelBatches) appendInsertResult(cur *cursorInfo, raw bson.Raw) bool {
idx := int(cur.Idx) + mb.offset
if err := cur.extractError(); err != nil {
err.Raw = raw
if mb.writeErrors == nil {
mb.writeErrors = make(map[int]WriteError)
}
mb.writeErrors[idx] = *err
return false
}
if mb.result.Acknowledged {
if mb.result.InsertResults == nil {
mb.result.InsertResults = make(map[int]ClientBulkWriteInsertResult)
}
mb.result.InsertResults[idx] = ClientBulkWriteInsertResult{mb.newIDMap[idx]}
}
return true
}
func (mb *modelBatches) appendUpdateResult(cur *cursorInfo, raw bson.Raw) bool {
idx := int(cur.Idx) + mb.offset
if err := cur.extractError(); err != nil {
err.Raw = raw
if mb.writeErrors == nil {
mb.writeErrors = make(map[int]WriteError)
}
mb.writeErrors[idx] = *err
return false
}
if mb.result.Acknowledged {
if mb.result.UpdateResults == nil {
mb.result.UpdateResults = make(map[int]ClientBulkWriteUpdateResult)
}
result := ClientBulkWriteUpdateResult{
MatchedCount: int64(cur.N),
}
if cur.NModified != nil {
result.ModifiedCount = int64(*cur.NModified)
}
if cur.Upserted != nil {
result.UpsertedID = cur.Upserted.ID
}
mb.result.UpdateResults[idx] = result
}
return true
}
type clientInsertDoc struct {
namespace int
document any
}
func (d *clientInsertDoc) marshal(bsonOpts *options.BSONOptions, registry *bson.Registry) (any, bsoncore.Document, error) {
uidx, doc := bsoncore.AppendDocumentStart(nil)
doc = bsoncore.AppendInt32Element(doc, "insert", int32(d.namespace))
f, err := marshal(d.document, bsonOpts, registry)
if err != nil {
return nil, nil, err
}
var id any
f, id, err = ensureID(f, bson.NilObjectID, bsonOpts, registry)
if err != nil {
return nil, nil, err
}
doc = bsoncore.AppendDocumentElement(doc, "document", f)
doc, err = bsoncore.AppendDocumentEnd(doc, uidx)
return id, doc, err
}
type clientUpdateDoc struct {
namespace int
filter any
update any
hint any
arrayFilters []any
collation *options.Collation
sort any
upsert *bool
multi bool
checkDollarKey bool
}
func (d *clientUpdateDoc) marshal(bsonOpts *options.BSONOptions, registry *bson.Registry) (bsoncore.Document, error) {
uidx, doc := bsoncore.AppendDocumentStart(nil)
doc = bsoncore.AppendInt32Element(doc, "update", int32(d.namespace))
if d.filter == nil {
return nil, fmt.Errorf("update filter cannot be nil")
}
f, err := marshal(d.filter, bsonOpts, registry)
if err != nil {
return nil, err
}
doc = bsoncore.AppendDocumentElement(doc, "filter", f)
u, err := marshalUpdateValue(d.update, bsonOpts, registry, d.checkDollarKey)
if err != nil {
return nil, err
}
doc = bsoncore.AppendValueElement(doc, "updateMods", u)
doc = bsoncore.AppendBooleanElement(doc, "multi", d.multi)
if d.arrayFilters != nil {
reg := registry
arr, err := marshalValue(d.arrayFilters, bsonOpts, reg)
if err != nil {
return nil, err
}
doc = bsoncore.AppendArrayElement(doc, "arrayFilters", arr.Data)
}
if d.collation != nil {
doc = bsoncore.AppendDocumentElement(doc, "collation", toDocument(d.collation))
}
if d.upsert != nil {
doc = bsoncore.AppendBooleanElement(doc, "upsert", *d.upsert)
}
if d.hint != nil {
if isUnorderedMap(d.hint) {
return nil, ErrMapForOrderedArgument{"hint"}
}
hintVal, err := marshalValue(d.hint, bsonOpts, registry)
if err != nil {
return nil, err
}
doc = bsoncore.AppendValueElement(doc, "hint", hintVal)
}
if d.sort != nil {
if isUnorderedMap(d.sort) {
return nil, ErrMapForOrderedArgument{"sort"}
}
sortVal, err := marshalValue(d.sort, bsonOpts, registry)
if err != nil {
return nil, err
}
doc = bsoncore.AppendValueElement(doc, "sort", sortVal)
}
return bsoncore.AppendDocumentEnd(doc, uidx)
}
type clientDeleteDoc struct {
namespace int
filter any
collation *options.Collation
hint any
multi bool
}
func (d *clientDeleteDoc) marshal(bsonOpts *options.BSONOptions, registry *bson.Registry) (bsoncore.Document, error) {
didx, doc := bsoncore.AppendDocumentStart(nil)
doc = bsoncore.AppendInt32Element(doc, "delete", int32(d.namespace))
if d.filter == nil {
return nil, fmt.Errorf("delete filter cannot be nil")
}
f, err := marshal(d.filter, bsonOpts, registry)
if err != nil {
return nil, err
}
doc = bsoncore.AppendDocumentElement(doc, "filter", f)
doc = bsoncore.AppendBooleanElement(doc, "multi", d.multi)
if d.collation != nil {
doc = bsoncore.AppendDocumentElement(doc, "collation", toDocument(d.collation))
}
if d.hint != nil {
if isUnorderedMap(d.hint) {
return nil, ErrMapForOrderedArgument{"hint"}
}
hintVal, err := marshalValue(d.hint, bsonOpts, registry)
if err != nil {
return nil, err
}
doc = bsoncore.AppendValueElement(doc, "hint", hintVal)
}
return bsoncore.AppendDocumentEnd(doc, didx)
}

View File

@@ -0,0 +1,318 @@
// Copyright (C) MongoDB, Inc. 2024-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 (
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
// ClientWriteModel is an interface implemented by models that can be used in a client-level BulkWrite operation. Each
// ClientWriteModel represents a write.
//
// This interface is implemented by ClientDeleteOneModel, ClientDeleteManyModel, ClientInsertOneModel,
// ClientReplaceOneModel, ClientUpdateOneModel, and ClientUpdateManyModel. Custom implementations of this interface must
// not be used.
type ClientWriteModel interface {
clientWriteModel()
}
// ClientInsertOneModel is used to insert a single document in a client-level BulkWrite operation.
//
// See corresponding setter methods for documentation.
type ClientInsertOneModel struct {
Document any
}
// NewClientInsertOneModel creates a new ClientInsertOneModel.
func NewClientInsertOneModel() *ClientInsertOneModel {
return &ClientInsertOneModel{}
}
func (*ClientInsertOneModel) clientWriteModel() {}
// SetDocument specifies the document to be inserted. The document cannot be nil. If it does not have an _id field when
// transformed into BSON, one will be added automatically to the marshalled document. The original document will not be
// modified.
func (iom *ClientInsertOneModel) SetDocument(doc any) *ClientInsertOneModel {
iom.Document = doc
return iom
}
// ClientUpdateOneModel is used to update at most one document in a client-level BulkWrite operation.
//
// See corresponding setter methods for documentation.
type ClientUpdateOneModel struct {
Collation *options.Collation
Upsert *bool
Filter any
Update any
ArrayFilters []any
Hint any
Sort any
}
// NewClientUpdateOneModel creates a new ClientUpdateOneModel.
func NewClientUpdateOneModel() *ClientUpdateOneModel {
return &ClientUpdateOneModel{}
}
func (*ClientUpdateOneModel) clientWriteModel() {}
// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
// specification as a document. The default value is nil, which means that no hint will be sent.
func (uom *ClientUpdateOneModel) SetHint(hint any) *ClientUpdateOneModel {
uom.Hint = hint
return uom
}
// SetFilter specifies a filter to use to select the document to update. The filter must be a document containing query
// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
// documents.
func (uom *ClientUpdateOneModel) SetFilter(filter any) *ClientUpdateOneModel {
uom.Filter = filter
return uom
}
// SetUpdate specifies the modifications to be made to the selected document. The value must be a document containing
// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty.
func (uom *ClientUpdateOneModel) SetUpdate(update any) *ClientUpdateOneModel {
uom.Update = update
return uom
}
// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array
// field.
func (uom *ClientUpdateOneModel) SetArrayFilters(filters []any) *ClientUpdateOneModel {
uom.ArrayFilters = filters
return uom
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (uom *ClientUpdateOneModel) SetCollation(collation *options.Collation) *ClientUpdateOneModel {
uom.Collation = collation
return uom
}
// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If
// an upsert is performed, the _id of the upserted document can be retrieved from the UpdateResults field of the
// ClientBulkWriteResult.
func (uom *ClientUpdateOneModel) SetUpsert(upsert bool) *ClientUpdateOneModel {
uom.Upsert = &upsert
return uom
}
// SetSort specifies which document the operation updates if the query matches multiple documents. The first document
// matched by the sort order will be updated. This option is only valid for MongoDB versions >= 8.0. The sort parameter
// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The
// default value is nil.
func (uom *ClientUpdateOneModel) SetSort(sort any) *ClientUpdateOneModel {
uom.Sort = sort
return uom
}
// ClientUpdateManyModel is used to update multiple documents in a client-level BulkWrite operation.
//
// See corresponding setter methods for documentation.
type ClientUpdateManyModel struct {
Collation *options.Collation
Upsert *bool
Filter any
Update any
ArrayFilters []any
Hint any
}
// NewClientUpdateManyModel creates a new ClientUpdateManyModel.
func NewClientUpdateManyModel() *ClientUpdateManyModel {
return &ClientUpdateManyModel{}
}
func (*ClientUpdateManyModel) clientWriteModel() {}
// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
// specification as a document. The default value is nil, which means that no hint will be sent.
func (umm *ClientUpdateManyModel) SetHint(hint any) *ClientUpdateManyModel {
umm.Hint = hint
return umm
}
// SetFilter specifies a filter to use to select documents to update. The filter must be a document containing query
// operators. It cannot be nil.
func (umm *ClientUpdateManyModel) SetFilter(filter any) *ClientUpdateManyModel {
umm.Filter = filter
return umm
}
// SetUpdate specifies the modifications to be made to the selected documents. The value must be a document containing
// update operators (https://www.mongodb.com/docs/manual/reference/operator/update/). It cannot be nil or empty.
func (umm *ClientUpdateManyModel) SetUpdate(update any) *ClientUpdateManyModel {
umm.Update = update
return umm
}
// SetArrayFilters specifies a set of filters to determine which elements should be modified when updating an array
// field.
func (umm *ClientUpdateManyModel) SetArrayFilters(filters []any) *ClientUpdateManyModel {
umm.ArrayFilters = filters
return umm
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (umm *ClientUpdateManyModel) SetCollation(collation *options.Collation) *ClientUpdateManyModel {
umm.Collation = collation
return umm
}
// SetUpsert specifies whether or not a new document should be inserted if no document matching the filter is found. If
// an upsert is performed, the _id of the upserted document can be retrieved from the UpdateResults field of the
// ClientBulkWriteResult.
func (umm *ClientUpdateManyModel) SetUpsert(upsert bool) *ClientUpdateManyModel {
umm.Upsert = &upsert
return umm
}
// ClientReplaceOneModel is used to replace at most one document in a client-level BulkWrite operation.
//
// See corresponding setter methods for documentation.
type ClientReplaceOneModel struct {
Collation *options.Collation
Upsert *bool
Filter any
Replacement any
Hint any
Sort any
}
// NewClientReplaceOneModel creates a new ClientReplaceOneModel.
func NewClientReplaceOneModel() *ClientReplaceOneModel {
return &ClientReplaceOneModel{}
}
func (*ClientReplaceOneModel) clientWriteModel() {}
// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
// specification as a document. The default value is nil, which means that no hint will be sent.
func (rom *ClientReplaceOneModel) SetHint(hint any) *ClientReplaceOneModel {
rom.Hint = hint
return rom
}
// SetFilter specifies a filter to use to select the document to replace. The filter must be a document containing query
// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
// documents.
func (rom *ClientReplaceOneModel) SetFilter(filter any) *ClientReplaceOneModel {
rom.Filter = filter
return rom
}
// SetReplacement specifies a document that will be used to replace the selected document. It cannot be nil and cannot
// contain any update operators (https://www.mongodb.com/docs/manual/reference/operator/update/).
func (rom *ClientReplaceOneModel) SetReplacement(rep any) *ClientReplaceOneModel {
rom.Replacement = rep
return rom
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (rom *ClientReplaceOneModel) SetCollation(collation *options.Collation) *ClientReplaceOneModel {
rom.Collation = collation
return rom
}
// SetUpsert specifies whether or not the replacement document should be inserted if no document matching the filter is
// found. If an upsert is performed, the _id of the upserted document can be retrieved from the UpdateResults field of the
// BulkWriteResult.
func (rom *ClientReplaceOneModel) SetUpsert(upsert bool) *ClientReplaceOneModel {
rom.Upsert = &upsert
return rom
}
// SetSort specifies which document the operation replaces if the query matches multiple documents. The first document
// matched by the sort order will be replaced. This option is only valid for MongoDB versions >= 8.0. The sort parameter
// is evaluated sequentially, so the driver will return an error if it is a multi-key map (which is unordeded). The
// default value is nil.
func (rom *ClientReplaceOneModel) SetSort(sort any) *ClientReplaceOneModel {
rom.Sort = sort
return rom
}
// ClientDeleteOneModel is used to delete at most one document in a client-level BulkWriteOperation.
//
// See corresponding setter methods for documentation.
type ClientDeleteOneModel struct {
Filter any
Collation *options.Collation
Hint any
}
// NewClientDeleteOneModel creates a new ClientDeleteOneModel.
func NewClientDeleteOneModel() *ClientDeleteOneModel {
return &ClientDeleteOneModel{}
}
func (*ClientDeleteOneModel) clientWriteModel() {}
// SetFilter specifies a filter to use to select the document to delete. The filter must be a document containing query
// operators. It cannot be nil. If the filter matches multiple documents, one will be selected from the matching
// documents.
func (dom *ClientDeleteOneModel) SetFilter(filter any) *ClientDeleteOneModel {
dom.Filter = filter
return dom
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (dom *ClientDeleteOneModel) SetCollation(collation *options.Collation) *ClientDeleteOneModel {
dom.Collation = collation
return dom
}
// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
// specification as a document. The default value is nil, which means that no hint will be sent.
func (dom *ClientDeleteOneModel) SetHint(hint any) *ClientDeleteOneModel {
dom.Hint = hint
return dom
}
// ClientDeleteManyModel is used to delete multiple documents in a client-level BulkWrite operation.
//
// See corresponding setter methods for documentation.
type ClientDeleteManyModel struct {
Filter any
Collation *options.Collation
Hint any
}
// NewClientDeleteManyModel creates a new ClientDeleteManyModel.
func NewClientDeleteManyModel() *ClientDeleteManyModel {
return &ClientDeleteManyModel{}
}
func (*ClientDeleteManyModel) clientWriteModel() {}
// SetFilter specifies a filter to use to select documents to delete. The filter must be a document containing query
// operators. It cannot be nil.
func (dmm *ClientDeleteManyModel) SetFilter(filter any) *ClientDeleteManyModel {
dmm.Filter = filter
return dmm
}
// SetCollation specifies a collation to use for string comparisons. The default is nil, meaning no collation will be
// used.
func (dmm *ClientDeleteManyModel) SetCollation(collation *options.Collation) *ClientDeleteManyModel {
dmm.Collation = collation
return dmm
}
// SetHint specifies the index to use for the operation. This should either be the index name as a string or the index
// specification as a document. The default value is nil, which means that no hint will be sent.
func (dmm *ClientDeleteManyModel) SetHint(hint any) *ClientDeleteManyModel {
dmm.Hint = hint
return dmm
}

View File

@@ -0,0 +1,554 @@
// 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"
"strings"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/mongoutil"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/mongocrypt"
mcopts "go.mongodb.org/mongo-driver/v2/x/mongo/driver/mongocrypt/options"
)
// ClientEncryption is used to create data keys and explicitly encrypt and decrypt BSON values.
type ClientEncryption struct {
crypt driver.Crypt
keyVaultClient *Client
keyVaultColl *Collection
closed bool
}
// NewClientEncryption creates a new ClientEncryption instance configured with the given options.
func NewClientEncryption(keyVaultClient *Client, opts ...options.Lister[options.ClientEncryptionOptions]) (*ClientEncryption, error) {
if keyVaultClient == nil {
return nil, errors.New("keyVaultClient must not be nil")
}
ce := &ClientEncryption{
keyVaultClient: keyVaultClient,
}
cea, err := mongoutil.NewOptions(opts...)
if err != nil {
return nil, err
}
// create keyVaultColl
db, coll := splitNamespace(cea.KeyVaultNamespace)
ce.keyVaultColl = ce.keyVaultClient.Database(db).Collection(coll, keyVaultCollOpts)
kmsProviders, err := marshal(cea.KmsProviders, nil, nil)
if err != nil {
return nil, fmt.Errorf("error creating KMS providers map: %w", err)
}
mc, err := mongocrypt.NewMongoCrypt(&mcopts.MongoCryptOptions{
KmsProviders: kmsProviders,
// Explicitly disable loading the crypt_shared library for the Crypt used for
// ClientEncryption because it's only needed for AutoEncryption and we don't expect users to
// have the crypt_shared library installed if they're using ClientEncryption.
CryptSharedLibDisabled: true,
HTTPClient: cea.HTTPClient,
KeyExpiration: cea.KeyExpiration,
})
if err != nil {
return nil, err
}
// create Crypt
kr := keyRetriever{coll: ce.keyVaultColl}
cir := collInfoRetriever{client: ce.keyVaultClient}
ce.crypt = driver.NewCrypt(&driver.CryptOptions{
MongoCrypt: mc,
KeyFn: kr.cryptKeys,
CollInfoFn: cir.cryptCollInfo,
TLSConfig: cea.TLSConfig,
})
return ce, nil
}
// CreateEncryptedCollection creates a new collection for Queryable Encryption with the help of automatic generation of new encryption data keys for null keyIds.
// It returns the created collection and the encrypted fields document used to create it.
func (ce *ClientEncryption) CreateEncryptedCollection(ctx context.Context,
db *Database, coll string, createOpts options.Lister[options.CreateCollectionOptions],
kmsProvider string, masterKey any,
) (*Collection, bson.M, error) {
if ce.closed {
return nil, nil, ErrClientDisconnected
}
if createOpts == nil {
return nil, nil, errors.New("nil CreateCollectionOptions")
}
createArgs, err := mongoutil.NewOptions[options.CreateCollectionOptions](createOpts)
if err != nil {
return nil, nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
ef := createArgs.EncryptedFields
if ef == nil {
return nil, nil, errors.New("no EncryptedFields defined for the collection")
}
efBSON, err := marshal(ef, db.bsonOpts, db.registry)
if err != nil {
return nil, nil, err
}
r := bson.NewDocumentReader(bytes.NewReader(efBSON))
dec := bson.NewDecoder(r)
dec.DefaultDocumentM()
var m bson.M
err = dec.Decode(&m)
if err != nil {
return nil, nil, err
}
if v, ok := m["fields"]; ok {
if fields, ok := v.(bson.A); ok {
for _, field := range fields {
if f, ok := field.(bson.M); !ok {
continue
} else if v, ok := f["keyId"]; ok && v == nil {
dkOpts := options.DataKey()
if masterKey != nil {
dkOpts.SetMasterKey(masterKey)
}
keyid, err := ce.CreateDataKey(ctx, kmsProvider, dkOpts)
if err != nil {
createArgs.EncryptedFields = m
return nil, m, err
}
f["keyId"] = keyid
}
}
createArgs.EncryptedFields = m
}
}
updatedCreateOpts := mongoutil.NewOptionsLister(createArgs, nil)
err = db.CreateCollection(ctx, coll, updatedCreateOpts)
if err != nil {
return nil, m, err
}
return db.Collection(coll), m, nil
}
// AddKeyAltName adds a keyAltName to the keyAltNames array of the key document in the key vault collection with the
// given UUID (BSON binary subtype 0x04). Returns the previous version of the key document.
func (ce *ClientEncryption) AddKeyAltName(ctx context.Context, id bson.Binary, keyAltName string) *SingleResult {
if ce.closed {
return &SingleResult{err: ErrClientDisconnected}
}
filter := bsoncore.NewDocumentBuilder().AppendBinary("_id", id.Subtype, id.Data).Build()
keyAltNameDoc := bsoncore.NewDocumentBuilder().AppendString("keyAltNames", keyAltName).Build()
update := bsoncore.NewDocumentBuilder().AppendDocument("$addToSet", keyAltNameDoc).Build()
return ce.keyVaultColl.FindOneAndUpdate(ctx, filter, update)
}
// CreateDataKey creates a new key document and inserts into the key vault collection. Returns the _id of the created
// document as a UUID (BSON binary subtype 0x04).
func (ce *ClientEncryption) CreateDataKey(
ctx context.Context,
kmsProvider string,
opts ...options.Lister[options.DataKeyOptions],
) (bson.Binary, error) {
if ce.closed {
return bson.Binary{}, ErrClientDisconnected
}
args, err := mongoutil.NewOptions[options.DataKeyOptions](opts...)
if err != nil {
return bson.Binary{}, fmt.Errorf("failed to construct options from builder: %w", err)
}
co := &mcopts.DataKeyOptions{
KeyAltNames: args.KeyAltNames,
KeyMaterial: args.KeyMaterial,
}
if args.MasterKey != nil {
keyDoc, err := marshal(
args.MasterKey,
ce.keyVaultClient.bsonOpts,
ce.keyVaultClient.registry)
if err != nil {
return bson.Binary{}, err
}
co.MasterKey = keyDoc
}
// create data key document
dataKeyDoc, err := ce.crypt.CreateDataKey(ctx, kmsProvider, co)
if err != nil {
return bson.Binary{}, err
}
// insert key into key vault
_, err = ce.keyVaultColl.InsertOne(ctx, dataKeyDoc)
if err != nil {
return bson.Binary{}, err
}
subtype, data := bson.Raw(dataKeyDoc).Lookup("_id").Binary()
return bson.Binary{Subtype: subtype, Data: data}, nil
}
// transformExplicitEncryptionOptions creates explicit encryption options to be passed to libmongocrypt.
func transformExplicitEncryptionOptions(opts ...options.Lister[options.EncryptOptions]) (*mcopts.ExplicitEncryptionOptions, error) {
args, err := mongoutil.NewOptions[options.EncryptOptions](opts...)
if err != nil {
return nil, err
}
transformed := &mcopts.ExplicitEncryptionOptions{
KeyID: args.KeyID,
KeyAltName: args.KeyAltName,
Algorithm: args.Algorithm,
QueryType: args.QueryType,
ContentionFactor: args.ContentionFactor,
}
if args.RangeOptions != nil {
rangeArgs, err := mongoutil.NewOptions[options.RangeOptions](args.RangeOptions)
if err != nil {
return nil, err
}
var transformedRange mcopts.ExplicitRangeOptions
if rangeArgs.Min != nil {
transformedRange.Min = &bsoncore.Value{Type: bsoncore.Type(rangeArgs.Min.Type), Data: rangeArgs.Min.Value}
}
if rangeArgs.Max != nil {
transformedRange.Max = &bsoncore.Value{Type: bsoncore.Type(rangeArgs.Max.Type), Data: rangeArgs.Max.Value}
}
if rangeArgs.Precision != nil {
transformedRange.Precision = rangeArgs.Precision
}
if rangeArgs.Sparsity != nil {
transformedRange.Sparsity = rangeArgs.Sparsity
}
if rangeArgs.TrimFactor != nil {
transformedRange.TrimFactor = rangeArgs.TrimFactor
}
transformed.RangeOptions = &transformedRange
}
if args.TextOptions != nil {
textArgs, err := mongoutil.NewOptions[options.TextOptions](args.TextOptions)
if err != nil {
return nil, err
}
transformedText := mcopts.ExplicitTextOptions{
CaseSensitive: textArgs.CaseSensitive,
DiacriticSensitive: textArgs.DiacriticSensitive,
}
if textArgs.Substring != nil {
substringOpts := mcopts.SubstringOptions(*textArgs.Substring)
transformedText.Substring = &substringOpts
}
if textArgs.Prefix != nil {
prefixOpts := mcopts.PrefixOptions(*textArgs.Prefix)
transformedText.Prefix = &prefixOpts
}
if textArgs.Suffix != nil {
suffixOpts := mcopts.SuffixOptions(*textArgs.Suffix)
transformedText.Suffix = &suffixOpts
}
transformed.SetTextOptions(transformedText)
}
return transformed, nil
}
// Encrypt encrypts a BSON value with the given key and algorithm. Returns an encrypted value (BSON binary of subtype 6).
func (ce *ClientEncryption) Encrypt(
ctx context.Context,
val bson.RawValue,
opts ...options.Lister[options.EncryptOptions],
) (bson.Binary, error) {
if ce.closed {
return bson.Binary{}, ErrClientDisconnected
}
transformed, err := transformExplicitEncryptionOptions(opts...)
if err != nil {
return bson.Binary{}, err
}
subtype, data, err := ce.crypt.EncryptExplicit(ctx, bsoncore.Value{Type: bsoncore.Type(val.Type), Data: val.Value}, transformed)
if err != nil {
return bson.Binary{}, err
}
return bson.Binary{Subtype: subtype, Data: data}, nil
}
// EncryptExpression encrypts an expression to query a range index.
// On success, `result` is populated with the resulting BSON document.
// `expr` is expected to be a BSON document of one of the following forms:
// 1. A Match Expression of this form:
// {$and: [{<field>: {$gt: <value1>}}, {<field>: {$lt: <value2> }}]}
// 2. An Aggregate Expression of this form:
// {$and: [{$gt: [<fieldpath>, <value1>]}, {$lt: [<fieldpath>, <value2>]}]
// $gt may also be $gte. $lt may also be $lte.
// Only supported for queryType "range"
func (ce *ClientEncryption) EncryptExpression(ctx context.Context, expr any, result any, opts ...options.Lister[options.EncryptOptions]) error {
if ce.closed {
return ErrClientDisconnected
}
transformed, err := transformExplicitEncryptionOptions(opts...)
if err != nil {
return err
}
exprDoc, err := marshal(expr, nil, nil)
if err != nil {
return err
}
encryptedExprDoc, err := ce.crypt.EncryptExplicitExpression(ctx, exprDoc, transformed)
if err != nil {
return err
}
if raw, ok := result.(*bson.Raw); ok {
// Avoid the cost of Unmarshal.
*raw = bson.Raw(encryptedExprDoc)
return nil
}
err = bson.Unmarshal([]byte(encryptedExprDoc), result)
if err != nil {
return err
}
return nil
}
// Decrypt decrypts an encrypted value (BSON binary of subtype 6) and returns the original BSON value.
func (ce *ClientEncryption) Decrypt(ctx context.Context, val bson.Binary) (bson.RawValue, error) {
if ce.closed {
return bson.RawValue{}, ErrClientDisconnected
}
decrypted, err := ce.crypt.DecryptExplicit(ctx, val.Subtype, val.Data)
if err != nil {
return bson.RawValue{}, err
}
return bson.RawValue{Type: bson.Type(decrypted.Type), Value: decrypted.Data}, nil
}
// Close cleans up any resources associated with the ClientEncryption instance. This includes disconnecting the
// key-vault Client instance.
func (ce *ClientEncryption) Close(ctx context.Context) error {
if ce.closed {
return ErrClientDisconnected
}
ce.crypt.Close()
err := ce.keyVaultClient.Disconnect(ctx)
if err == nil {
ce.closed = true
}
return err
}
// DeleteKey removes the key document with the given UUID (BSON binary subtype 0x04) from the key vault collection.
// Returns the result of the internal deleteOne() operation on the key vault collection.
func (ce *ClientEncryption) DeleteKey(ctx context.Context, id bson.Binary) (*DeleteResult, error) {
if ce.closed {
return nil, ErrClientDisconnected
}
filter := bsoncore.NewDocumentBuilder().AppendBinary("_id", id.Subtype, id.Data).Build()
return ce.keyVaultColl.DeleteOne(ctx, filter)
}
// GetKeyByAltName returns a key document in the key vault collection with the given keyAltName.
func (ce *ClientEncryption) GetKeyByAltName(ctx context.Context, keyAltName string) *SingleResult {
if ce.closed {
return &SingleResult{err: ErrClientDisconnected}
}
filter := bsoncore.NewDocumentBuilder().AppendString("keyAltNames", keyAltName).Build()
return ce.keyVaultColl.FindOne(ctx, filter)
}
// GetKey finds a single key document with the given UUID (BSON binary subtype 0x04). Returns the result of the
// internal find() operation on the key vault collection.
func (ce *ClientEncryption) GetKey(ctx context.Context, id bson.Binary) *SingleResult {
if ce.closed {
return &SingleResult{err: ErrClientDisconnected}
}
filter := bsoncore.NewDocumentBuilder().AppendBinary("_id", id.Subtype, id.Data).Build()
return ce.keyVaultColl.FindOne(ctx, filter)
}
// GetKeys finds all documents in the key vault collection. Returns the result of the internal find() operation on the
// key vault collection.
func (ce *ClientEncryption) GetKeys(ctx context.Context) (*Cursor, error) {
if ce.closed {
return nil, ErrClientDisconnected
}
return ce.keyVaultColl.Find(ctx, bson.D{})
}
// RemoveKeyAltName removes a keyAltName from the keyAltNames array of the key document in the key vault collection with
// the given UUID (BSON binary subtype 0x04). Returns the previous version of the key document.
func (ce *ClientEncryption) RemoveKeyAltName(ctx context.Context, id bson.Binary, keyAltName string) *SingleResult {
if ce.closed {
return &SingleResult{err: ErrClientDisconnected}
}
filter := bsoncore.NewDocumentBuilder().AppendBinary("_id", id.Subtype, id.Data).Build()
update := bson.A{bson.D{{"$set", bson.D{{"keyAltNames", bson.D{{"$cond", bson.A{bson.D{{
"$eq",
bson.A{"$keyAltNames", bson.A{keyAltName}},
}}, "$$REMOVE", bson.D{{
"$filter",
bson.D{{"input", "$keyAltNames"}, {"cond", bson.D{{"$ne", bson.A{"$$this", keyAltName}}}}},
}}}}}}}}}}
return ce.keyVaultColl.FindOneAndUpdate(ctx, filter, update)
}
// setRewrapManyDataKeyWriteModels will prepare the WriteModel slice for a bulk updating rewrapped documents.
func setRewrapManyDataKeyWriteModels(rewrappedDocuments []bsoncore.Document, writeModels *[]WriteModel) error {
const idKey = "_id"
const keyMaterial = "keyMaterial"
const masterKey = "masterKey"
if writeModels == nil {
return fmt.Errorf("writeModels pointer not set for location referenced")
}
// Append a slice of WriteModel with the update document per each rewrappedDoc _id filter.
for _, rewrappedDocument := range rewrappedDocuments {
// Prepare the new master key for update.
masterKeyValue, err := rewrappedDocument.LookupErr(masterKey)
if err != nil {
return err
}
masterKeyDoc := masterKeyValue.Document()
// Prepare the new material key for update.
keyMaterialValue, err := rewrappedDocument.LookupErr(keyMaterial)
if err != nil {
return err
}
keyMaterialSubtype, keyMaterialData := keyMaterialValue.Binary()
keyMaterialBinary := bson.Binary{Subtype: keyMaterialSubtype, Data: keyMaterialData}
// Prepare the _id filter for documents to update.
id, err := rewrappedDocument.LookupErr(idKey)
if err != nil {
return err
}
idSubtype, idData, ok := id.BinaryOK()
if !ok {
return fmt.Errorf("expected to assert %q as binary, got type %T", idKey, id)
}
binaryID := bson.Binary{Subtype: idSubtype, Data: idData}
// Append the mutable document to the slice for bulk update.
*writeModels = append(*writeModels, NewUpdateOneModel().
SetFilter(bson.D{{idKey, binaryID}}).
SetUpdate(
bson.D{
{"$set", bson.D{{keyMaterial, keyMaterialBinary}, {masterKey, masterKeyDoc}}},
{"$currentDate", bson.D{{"updateDate", true}}},
},
))
}
return nil
}
// RewrapManyDataKey decrypts and encrypts all matching data keys with a possibly new masterKey value. For all
// matching documents, this method will overwrite the "masterKey", "updateDate", and "keyMaterial". On error, some
// matching data keys may have been rewrapped.
// libmongocrypt 1.5.2 is required. An error is returned if the detected version of libmongocrypt is less than 1.5.2.
func (ce *ClientEncryption) RewrapManyDataKey(
ctx context.Context,
filter any,
opts ...options.Lister[options.RewrapManyDataKeyOptions],
) (*RewrapManyDataKeyResult, error) {
// libmongocrypt versions 1.5.0 and 1.5.1 have a severe bug in RewrapManyDataKey.
// Check if the version string starts with 1.5.0 or 1.5.1. This accounts for pre-release versions, like 1.5.0-rc0.
if ce.closed {
return nil, ErrClientDisconnected
}
libmongocryptVersion := mongocrypt.Version()
if strings.HasPrefix(libmongocryptVersion, "1.5.0") || strings.HasPrefix(libmongocryptVersion, "1.5.1") {
return nil, fmt.Errorf("RewrapManyDataKey requires libmongocrypt 1.5.2 or newer. Detected version: %v", libmongocryptVersion)
}
if ctx == nil {
ctx = context.Background()
}
args, err := mongoutil.NewOptions[options.RewrapManyDataKeyOptions](opts...)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
// Transfer rmdko options to /x/ package options to publish the mongocrypt feed.
co := &mcopts.RewrapManyDataKeyOptions{
Provider: args.Provider,
}
if args.MasterKey != nil {
keyDoc, err := marshal(
args.MasterKey,
ce.keyVaultClient.bsonOpts,
ce.keyVaultClient.registry)
if err != nil {
return nil, err
}
co.MasterKey = keyDoc
}
// Prepare the filters and rewrap the data key using mongocrypt.
filterdoc, err := marshal(filter, ce.keyVaultClient.bsonOpts, ce.keyVaultClient.registry)
if err != nil {
return nil, err
}
rewrappedDocuments, err := ce.crypt.RewrapDataKey(ctx, filterdoc, co)
if err != nil {
return nil, err
}
if len(rewrappedDocuments) == 0 {
// If there are no documents to rewrap, then do nothing.
return new(RewrapManyDataKeyResult), nil
}
// Prepare the WriteModel slice for bulk updating the rewrapped data keys.
models := []WriteModel{}
if err := setRewrapManyDataKeyWriteModels(rewrappedDocuments, &models); err != nil {
return nil, err
}
bulkWriteResults, err := ce.keyVaultColl.BulkWrite(ctx, models)
return &RewrapManyDataKeyResult{BulkWriteResult: bulkWriteResults}, err
}
// splitNamespace takes a namespace in the form "database.collection" and returns (database name, collection name)
func splitNamespace(ns string) (string, string) {
firstDot := strings.Index(ns, ".")
if firstDot == -1 {
return "", ns
}
return ns[:firstDot], ns[firstDot+1:]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
// 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"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
)
// keyRetriever gets keys from the key vault collection.
type keyRetriever struct {
coll *Collection
}
func (kr *keyRetriever) cryptKeys(ctx context.Context, filter bsoncore.Document) ([]bsoncore.Document, error) {
// Remove the explicit session from the context if one is set.
// The explicit session may be from a different client.
ctx = NewSessionContext(ctx, nil)
cursor, err := kr.coll.Find(ctx, filter)
if err != nil {
return nil, EncryptionKeyVaultError{Wrapped: err}
}
defer cursor.Close(ctx)
var results []bsoncore.Document
for cursor.Next(ctx) {
cur := make([]byte, len(cursor.Current))
copy(cur, cursor.Current)
results = append(results, cur)
}
if err = cursor.Err(); err != nil {
return nil, EncryptionKeyVaultError{Wrapped: err}
}
return results, nil
}
// collInfoRetriever gets info for collections from a database.
type collInfoRetriever struct {
client *Client
}
func (cir *collInfoRetriever) cryptCollInfo(ctx context.Context, db string, filter bsoncore.Document) (bsoncore.Document, error) {
// Remove the explicit session from the context if one is set.
// The explicit session may be from a different client.
ctx = NewSessionContext(ctx, nil)
cursor, err := cir.client.Database(db).ListCollections(ctx, filter)
if err != nil {
return nil, err
}
defer cursor.Close(ctx)
if !cursor.Next(ctx) {
return nil, cursor.Err()
}
res := make([]byte, len(cursor.Current))
copy(res, cursor.Current)
return res, nil
}

View File

@@ -0,0 +1,495 @@
// 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"
"io"
"reflect"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/mongoutil"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
)
// Cursor is used to iterate over a stream of documents. Each document can be decoded into a Go type via the Decode
// method or accessed as raw BSON via the Current field. This type is not goroutine safe and must not be used
// concurrently by multiple goroutines.
type Cursor struct {
// Current contains the BSON bytes of the current change document. This property is only valid until the next call
// to Next or TryNext. If continued access is required, a copy must be made.
Current bson.Raw
bc batchCursor
batch *bsoncore.Iterator
batchLength int
bsonOpts *options.BSONOptions
registry *bson.Registry
clientSession *session.Client
clientTimeout time.Duration
hasClientTimeout bool
err error
}
type cursorOptions struct {
clientTimeout time.Duration
hasClientTimeout bool
}
type cursorOption func(*cursorOptions)
func withCursorOptionClientTimeout(dur *time.Duration) cursorOption {
return func(opts *cursorOptions) {
if dur != nil && *dur > 0 {
opts.clientTimeout = *dur
opts.hasClientTimeout = true
}
}
}
func newCursor(
bc batchCursor,
bsonOpts *options.BSONOptions,
registry *bson.Registry,
opts ...cursorOption,
) (*Cursor, error) {
return newCursorWithSession(bc, bsonOpts, registry, nil, opts...)
}
func newCursorWithSession(
bc batchCursor,
bsonOpts *options.BSONOptions,
registry *bson.Registry,
clientSession *session.Client,
opts ...cursorOption,
) (*Cursor, error) {
if registry == nil {
registry = defaultRegistry
}
if bc == nil {
return nil, errors.New("batch cursor must not be nil")
}
cursorOpts := &cursorOptions{}
for _, opt := range opts {
opt(cursorOpts)
}
c := &Cursor{
bc: bc,
bsonOpts: bsonOpts,
registry: registry,
clientSession: clientSession,
clientTimeout: cursorOpts.clientTimeout,
hasClientTimeout: cursorOpts.hasClientTimeout,
}
if bc.ID() == 0 {
c.closeImplicitSession()
}
// Initialize just the batchLength here so RemainingBatchLength will return an
// accurate result. The actual batch will be pulled up by the first
// Next/TryNext call.
c.batchLength = c.bc.Batch().Count()
return c, nil
}
func newEmptyCursor() *Cursor {
return &Cursor{bc: driver.NewEmptyBatchCursor()}
}
// NewCursorFromDocuments creates a new Cursor pre-loaded with the provided documents, error and registry. If no registry is provided,
// bson.NewRegistry() will be used.
//
// The documents parameter must be a slice of documents. The slice may be nil or empty, but all elements must be non-nil.
func NewCursorFromDocuments(documents []any, preloadedErr error, registry *bson.Registry) (*Cursor, error) {
if registry == nil {
registry = defaultRegistry
}
buf := new(bytes.Buffer)
enc := new(bson.Encoder)
values := make([]bsoncore.Value, len(documents))
for i, doc := range documents {
switch t := doc.(type) {
case nil:
return nil, fmt.Errorf("invalid document at index %d: %w", i, ErrNilDocument)
case []byte:
// Slight optimization so we'll just use MarshalBSON and not go through the codec machinery.
doc = bson.Raw(t)
}
vw := bson.NewDocumentWriter(buf)
enc.Reset(vw)
enc.SetRegistry(registry)
if err := enc.Encode(doc); err != nil {
return nil, err
}
dup := make([]byte, len(buf.Bytes()))
copy(dup, buf.Bytes())
values[i] = bsoncore.Value{
Type: bsoncore.TypeEmbeddedDocument,
Data: dup,
}
buf.Reset()
}
c := &Cursor{
bc: driver.NewBatchCursorFromList(bsoncore.BuildArray(nil, values...)),
registry: registry,
err: preloadedErr,
}
// Initialize batch and batchLength here. The underlying batch cursor will be preloaded with the
// provided contents, and thus already has a batch before calls to Next/TryNext.
c.batch = c.bc.Batch()
c.batchLength = c.bc.Batch().Count()
return c, nil
}
// ID returns the ID of this cursor, or 0 if the cursor has been closed or exhausted.
func (c *Cursor) ID() int64 { return c.bc.ID() }
// Next gets the next document for this cursor. It returns true if there were no
// errors and the cursor has not been exhausted.
//
// Next blocks until a document is available or an error occurs. If the context
// expires, the cursor's error will be set to ctx.Err(). In case of an error,
// Next will return false.
//
// If MaxAwaitTime is set, the operation will be bound by the Context's
// deadline. If the context does not have a deadline, the operation will be
// bound by the client-level timeout, if one is set. If MaxAwaitTime is greater
// than the user-provided timeout, Next will return false.
//
// If Next returns false, subsequent calls will also return false.
func (c *Cursor) Next(ctx context.Context) bool {
return c.next(ctx, false)
}
// TryNext attempts to get the next document for this cursor. It returns true if there were no errors and the next
// document is available. This is only recommended for use with tailable cursors as a non-blocking alternative to
// Next. See https://www.mongodb.com/docs/manual/core/tailable-cursors/ for more information about tailable cursors.
//
// TryNext returns false if the cursor is exhausted, an error occurs when getting results from the server, the next
// document is not yet available, or ctx expires. If the context expires, the cursor's error will be set to ctx.Err().
//
// If TryNext returns false and an error occurred or the cursor has been exhausted (i.e. c.Err() != nil || c.ID() == 0),
// subsequent attempts will also return false. Otherwise, it is safe to call TryNext again until a document is
// available.
//
// This method requires driver version >= 1.2.0.
func (c *Cursor) TryNext(ctx context.Context) bool {
return c.next(ctx, true)
}
func (c *Cursor) next(ctx context.Context, nonBlocking bool) bool {
// return false right away if the cursor has already errored.
if c.err != nil {
return false
}
if ctx == nil {
ctx = context.Background()
}
// If the context does not have a deadline we defer to a client-level timeout,
// if one is set.
if _, ok := ctx.Deadline(); !ok && c.hasClientTimeout {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, c.clientTimeout)
defer cancel()
}
// To avoid unnecessary socket timeouts, we attempt to short-circuit tailable
// awaitData "getMore" operations by ensuring that the maxAwaitTimeMS is less
// than the operation timeout.
//
// The specifications assume that drivers iteratively apply the timeout
// provided at the constructor level (e.g., (*collection).Find) for tailable
// awaitData cursors:
//
// If set, drivers MUST apply the timeoutMS option to the initial aggregate
// operation. Drivers MUST also apply the original timeoutMS value to each
// next call on the change stream but MUST NOT use it to derive a maxTimeMS
// field for getMore commands.
//
// The Go Driver might decide to support the above behavior with DRIVERS-2722.
// The principal concern is that it would be unexpected for users to apply an
// operation-level timeout via contexts to a constructor and then that timeout
// later be applied while working with a resulting cursor. Instead, it is more
// idiomatic to apply the timeout to the context passed to Next or TryNext.
maxAwaitTime := c.bc.MaxAwaitTime() //
if maxAwaitTime != nil && !nonBlocking && !mongoutil.TimeoutWithinContext(ctx, *maxAwaitTime) {
c.err = fmt.Errorf("MaxAwaitTime must be less than the operation timeout")
return false
}
val, err := c.batch.Next()
switch {
case err == nil:
// Consume the next document in the current batch.
c.batchLength--
c.Current = bson.Raw(val.Data)
return true
case errors.Is(err, io.EOF): // Need to do a getMore
default:
c.err = err
return false
}
// call the Next method in a loop until at least one document is returned in the next batch or
// the context times out.
for {
// If we don't have a next batch
if !c.bc.Next(ctx) {
// Do we have an error? If so we return false.
c.err = wrapErrors(c.bc.Err())
if c.err != nil {
return false
}
// Is the cursor ID zero?
if c.bc.ID() == 0 {
c.closeImplicitSession()
return false
}
// empty batch, but cursor is still valid.
// use nonBlocking to determine if we should continue or return control to the caller.
if nonBlocking {
return false
}
continue
}
// close the implicit session if this was the last getMore
if c.bc.ID() == 0 {
c.closeImplicitSession()
}
// Use the new batch to update the batch and batchLength fields. Consume the first document in the batch.
c.batch = c.bc.Batch()
c.batchLength = c.batch.Count()
val, err = c.batch.Next()
switch {
case err == nil:
c.batchLength--
c.Current = bson.Raw(val.Data)
return true
case errors.Is(err, io.EOF): // Empty batch so we continue
default:
c.err = err
return false
}
}
}
func getDecoder(
data []byte,
opts *options.BSONOptions,
reg *bson.Registry,
) *bson.Decoder {
dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(data)))
if opts != nil {
if opts.AllowTruncatingDoubles {
dec.AllowTruncatingDoubles()
}
if opts.BinaryAsSlice {
dec.BinaryAsSlice()
}
if opts.DefaultDocumentM {
dec.DefaultDocumentM()
}
if opts.DefaultDocumentMap {
dec.DefaultDocumentMap()
}
if opts.ObjectIDAsHexString {
dec.ObjectIDAsHexString()
}
if opts.UseJSONStructTags {
dec.UseJSONStructTags()
}
if opts.UseLocalTimeZone {
dec.UseLocalTimeZone()
}
if opts.ZeroMaps {
dec.ZeroMaps()
}
if opts.ZeroStructs {
dec.ZeroStructs()
}
}
if reg != nil {
dec.SetRegistry(reg)
}
return dec
}
// Decode will unmarshal the current document into val and return any errors from the unmarshalling process without any
// modification. If val is nil or is a typed nil, an error will be returned.
func (c *Cursor) Decode(val any) error {
dec := getDecoder(c.Current, c.bsonOpts, c.registry)
return dec.Decode(val)
}
// Err returns the last error seen by the Cursor, or nil if no error has occurred.
func (c *Cursor) Err() error { return c.err }
// Close closes this cursor. Next and TryNext must not be called after Close has been called. Close is idempotent. After
// the first call, any subsequent calls will not change the state.
func (c *Cursor) Close(ctx context.Context) error {
defer c.closeImplicitSession()
return wrapErrors(c.bc.Close(ctx))
}
// All iterates the cursor and decodes each document into results. The results parameter must be a pointer to a slice.
// The slice pointed to by results will be completely overwritten. A nil slice pointer will not be modified if the cursor
// has been closed, exhausted, or is empty. This method will close the cursor after retrieving all documents. If the
// cursor has been iterated, any previously iterated documents will not be included in results.
//
// This method requires driver version >= 1.1.0.
func (c *Cursor) All(ctx context.Context, results any) error {
resultsVal := reflect.ValueOf(results)
if resultsVal.Kind() != reflect.Ptr {
return fmt.Errorf("results argument must be a pointer to a slice, but was a %s", resultsVal.Kind())
}
sliceVal := resultsVal.Elem()
if sliceVal.Kind() == reflect.Interface {
sliceVal = sliceVal.Elem()
}
if sliceVal.Kind() != reflect.Slice {
return fmt.Errorf("results argument must be a pointer to a slice, but was a pointer to %s", sliceVal.Kind())
}
elementType := sliceVal.Type().Elem()
var index int
var err error
// Defer a call to Close to try to clean up the cursor server-side when all
// documents have not been exhausted. Use context.Background() to ensure Close
// completes even if the context passed to All has errored.
defer c.Close(context.Background())
batch := c.batch // exhaust the current batch before iterating the batch cursor
for {
sliceVal, index, err = c.addFromBatch(sliceVal, elementType, batch, index)
if err != nil {
return err
}
if !c.bc.Next(ctx) {
break
}
batch = c.bc.Batch()
}
if err = wrapErrors(c.bc.Err()); err != nil {
return err
}
resultsVal.Elem().Set(sliceVal.Slice(0, index))
return nil
}
// RemainingBatchLength returns the number of documents left in the current batch. If this returns zero, the subsequent
// call to Next or TryNext will do a network request to fetch the next batch.
func (c *Cursor) RemainingBatchLength() int {
return c.batchLength
}
// addFromBatch adds all documents from batch to sliceVal starting at the given index. It returns the new slice value,
// the next empty index in the slice, and an error if one occurs.
func (c *Cursor) addFromBatch(sliceVal reflect.Value, elemType reflect.Type, batch *bsoncore.Iterator,
index int,
) (reflect.Value, int, error) {
docs, err := batch.Documents()
if err != nil {
return sliceVal, index, err
}
for _, doc := range docs {
if sliceVal.Len() == index {
// slice is full
newElem := reflect.New(elemType)
sliceVal = reflect.Append(sliceVal, newElem.Elem())
sliceVal = sliceVal.Slice(0, sliceVal.Cap())
}
currElem := sliceVal.Index(index).Addr().Interface()
dec := getDecoder(doc, c.bsonOpts, c.registry)
err = dec.Decode(currElem)
if err != nil {
return sliceVal, index, err
}
index++
}
return sliceVal, index, nil
}
func (c *Cursor) closeImplicitSession() {
if c.clientSession != nil && c.clientSession.IsImplicit {
c.clientSession.EndSession()
}
}
// SetBatchSize sets the number of documents to fetch from the database with
// each iteration of the cursor's "Next" method. Note that some operations set
// an initial cursor batch size, so this setting only affects subsequent
// document batches fetched from the database.
func (c *Cursor) SetBatchSize(batchSize int32) {
c.bc.SetBatchSize(batchSize)
}
// SetMaxAwaitTime will set the maximum amount of time the server will allow the
// operations to execute. The server will error if this field is set but the
// cursor is not configured with awaitData=true.
//
// The time.Duration value passed by this setter will be converted and rounded
// down to the nearest millisecond.
func (c *Cursor) SetMaxAwaitTime(dur time.Duration) {
c.bc.SetMaxAwaitTime(dur)
}
// SetComment will set a user-configurable comment that can be used to identify
// the operation in server logs.
func (c *Cursor) SetComment(comment any) {
c.bc.SetComment(comment)
}
// BatchCursorFromCursor returns a driver.BatchCursor for the given Cursor. If there is no underlying
// driver.BatchCursor, nil is returned.
//
// Deprecated: This is an unstable function because the driver.BatchCursor type exists in the "x" package. Neither this
// function nor the driver.BatchCursor type should be used by applications and may be changed or removed in any release.
func BatchCursorFromCursor(c *Cursor) *driver.BatchCursor {
bc, _ := c.bc.(*driver.BatchCursor)
return bc
}

View File

@@ -0,0 +1,988 @@
// 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"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/csfle"
"go.mongodb.org/mongo-driver/v2/internal/csot"
"go.mongodb.org/mongo-driver/v2/internal/mongoutil"
"go.mongodb.org/mongo-driver/v2/internal/optionsutil"
"go.mongodb.org/mongo-driver/v2/internal/serverselector"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/mongo/readconcern"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
)
var defaultRunCmdOpts = []options.Lister[options.RunCmdOptions]{options.RunCmd().SetReadPreference(readpref.Primary())}
// Database is a handle to a MongoDB database. It is safe for concurrent use by multiple goroutines.
type Database struct {
client *Client
name string
readConcern *readconcern.ReadConcern
writeConcern *writeconcern.WriteConcern
readPreference *readpref.ReadPref
readSelector description.ServerSelector
writeSelector description.ServerSelector
bsonOpts *options.BSONOptions
registry *bson.Registry
}
func newDatabase(client *Client, name string, opts ...options.Lister[options.DatabaseOptions]) *Database {
args, _ := mongoutil.NewOptions[options.DatabaseOptions](opts...)
rc := client.readConcern
if args.ReadConcern != nil {
rc = args.ReadConcern
}
rp := client.readPreference
if args.ReadPreference != nil {
rp = args.ReadPreference
}
wc := client.writeConcern
if args.WriteConcern != nil {
wc = args.WriteConcern
}
bsonOpts := client.bsonOpts
if args.BSONOptions != nil {
bsonOpts = args.BSONOptions
}
reg := client.registry
if args.Registry != nil {
reg = args.Registry
}
db := &Database{
client: client,
name: name,
readPreference: rp,
readConcern: rc,
writeConcern: wc,
bsonOpts: bsonOpts,
registry: reg,
}
db.readSelector = &serverselector.Composite{
Selectors: []description.ServerSelector{
&serverselector.ReadPref{ReadPref: db.readPreference},
&serverselector.Latency{Latency: db.client.localThreshold},
},
}
db.writeSelector = &serverselector.Composite{
Selectors: []description.ServerSelector{
&serverselector.Write{},
&serverselector.Latency{Latency: db.client.localThreshold},
},
}
return db
}
// Client returns the Client the Database was created from.
func (db *Database) Client() *Client {
return db.client
}
// Name returns the name of the database.
func (db *Database) Name() string {
return db.name
}
// Collection returns a handle for a collection with the given name and options.
//
// If the collection does not exist on the server, it will be created when a
// write operation is performed.
func (db *Database) Collection(name string, opts ...options.Lister[options.CollectionOptions]) *Collection {
return newCollection(db, name, opts...)
}
// Aggregate executes an aggregate command the database.
//
// The pipeline parameter must be a slice of documents, each representing an aggregation stage. The pipeline
// cannot be nil but can be empty. The stage documents must all be non-nil. For a pipeline of bson.D documents, the
// mongo.Pipeline type can be used. See
// https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/#db-aggregate-stages for a list of valid
// stages in database-level aggregations.
//
// The opts parameter can be used to specify options for this operation (see the options.AggregateOptions documentation).
//
// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/aggregate/.
func (db *Database) Aggregate(
ctx context.Context,
pipeline any,
opts ...options.Lister[options.AggregateOptions],
) (*Cursor, error) {
a := aggregateParams{
ctx: ctx,
pipeline: pipeline,
client: db.client,
registry: db.registry,
readConcern: db.readConcern,
writeConcern: db.writeConcern,
retryRead: db.client.retryReads,
db: db.name,
readSelector: db.readSelector,
writeSelector: db.writeSelector,
readPreference: db.readPreference,
}
return aggregate(a, opts...)
}
func (db *Database) processRunCommand(
ctx context.Context,
cmd any,
cursorCommand bool,
opts ...options.Lister[options.RunCmdOptions],
) (*operation.Command, *session.Client, error) {
args, err := mongoutil.NewOptions[options.RunCmdOptions](append(defaultRunCmdOpts, opts...)...)
if err != nil {
return nil, nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
sess := sessionFromContext(ctx)
if sess == nil && db.client.sessionPool != nil {
sess = session.NewImplicitClientSession(db.client.sessionPool, db.client.id)
}
if err := db.client.validSession(sess); err != nil {
return nil, sess, err
}
if sess != nil && sess.TransactionRunning() && args.ReadPreference != nil && args.ReadPreference.Mode() != readpref.PrimaryMode {
return nil, sess, errors.New("read preference in a transaction must be primary")
}
if isUnorderedMap(cmd) {
return nil, sess, ErrMapForOrderedArgument{"cmd"}
}
runCmdDoc, err := marshal(cmd, db.bsonOpts, db.registry)
if err != nil {
return nil, sess, err
}
var readSelect description.ServerSelector
readSelect = &serverselector.Composite{
Selectors: []description.ServerSelector{
&serverselector.ReadPref{ReadPref: args.ReadPreference},
&serverselector.Latency{Latency: db.client.localThreshold},
},
}
if sess != nil && sess.PinnedServerAddr != nil {
readSelect = makePinnedSelector(sess, readSelect)
}
var op *operation.Command
switch cursorCommand {
case true:
cursorOpts := db.client.createBaseCursorOptions()
cursorOpts.MarshalValueEncoderFn = newEncoderFn(db.bsonOpts, db.registry)
op = operation.NewCursorCommand(runCmdDoc, cursorOpts)
default:
op = operation.NewCommand(runCmdDoc)
}
return op.Session(sess).CommandMonitor(db.client.monitor).
ServerSelector(readSelect).ClusterClock(db.client.clock).
Database(db.name).Deployment(db.client.deployment).
Crypt(db.client.cryptFLE).ReadPreference(args.ReadPreference).ServerAPI(db.client.serverAPI).
Timeout(db.client.timeout).Logger(db.client.logger).Authenticator(db.client.authenticator), sess, nil
}
// RunCommand executes the given command against the database.
//
// This function does not obey the Database's readPreference. To specify a read
// preference, the RunCmdOptions.ReadPreference option must be used.
//
// This function does not obey the Database's readConcern or writeConcern. A
// user must supply these values manually in the user-provided runCommand
// parameter.
//
// The runCommand parameter must be a document for the command to be executed. It cannot be nil.
// This must be an order-preserving type such as bson.D. Map types such as bson.M are not valid.
//
// The opts parameter can be used to specify options for this operation (see the options.RunCmdOptions documentation).
//
// The behavior of RunCommand is undefined if the command document contains any of the following:
// - A session ID or any transaction-specific fields
// - API versioning options when an API version is already declared on the Client
// - maxTimeMS when Timeout is set on the Client
func (db *Database) RunCommand(
ctx context.Context,
runCommand any,
opts ...options.Lister[options.RunCmdOptions],
) *SingleResult {
if ctx == nil {
ctx = context.Background()
}
op, sess, err := db.processRunCommand(ctx, runCommand, false, opts...)
defer closeImplicitSession(sess)
if err != nil {
return &SingleResult{err: err}
}
err = op.Execute(ctx)
// RunCommand can be used to run a write, thus execute may return a write error
rr, convErr := processWriteError(err)
return &SingleResult{
ctx: ctx,
err: convErr,
rdr: bson.Raw(op.Result()),
bsonOpts: db.bsonOpts,
reg: db.registry,
Acknowledged: rr.isAcknowledged(),
}
}
// RunCommandCursor executes the given command against the database and parses the response as a cursor. If the command
// being executed does not return a cursor (e.g. insert), the command will be executed on the server and an error will
// be returned because the server response cannot be parsed as a cursor. This function does not obey the Database's read
// preference. To specify a read preference, the RunCmdOptions.ReadPreference option must be used.
//
// The runCommand parameter must be a document for the command to be executed. It cannot be nil.
// This must be an order-preserving type such as bson.D. Map types such as bson.M are not valid.
//
// The opts parameter can be used to specify options for this operation (see the options.RunCmdOptions documentation).
//
// The behavior of RunCommandCursor is undefined if the command document contains any of the following:
// - A session ID or any transaction-specific fields
// - API versioning options when an API version is already declared on the Client
// - maxTimeMS when Timeout is set on the Client
func (db *Database) RunCommandCursor(
ctx context.Context,
runCommand any,
opts ...options.Lister[options.RunCmdOptions],
) (*Cursor, error) {
if ctx == nil {
ctx = context.Background()
}
op, sess, err := db.processRunCommand(ctx, runCommand, true, opts...)
if err != nil {
closeImplicitSession(sess)
return nil, wrapErrors(err)
}
if err = op.Execute(ctx); err != nil {
closeImplicitSession(sess)
if errors.Is(err, driver.ErrNoCursor) {
return nil, errors.New(
"database response does not contain a cursor; try using RunCommand instead")
}
return nil, wrapErrors(err)
}
bc, err := op.ResultCursor()
if err != nil {
closeImplicitSession(sess)
return nil, wrapErrors(err)
}
cursor, err := newCursorWithSession(bc, db.bsonOpts, db.registry, sess,
withCursorOptionClientTimeout(db.client.timeout))
return cursor, wrapErrors(err)
}
// Drop drops the database on the server. This method ignores "namespace not found" errors so it is safe to drop
// a database that does not exist on the server.
func (db *Database) Drop(ctx context.Context) error {
if ctx == nil {
ctx = context.Background()
}
sess := sessionFromContext(ctx)
if sess == nil && db.client.sessionPool != nil {
sess = session.NewImplicitClientSession(db.client.sessionPool, db.client.id)
defer sess.EndSession()
}
err := db.client.validSession(sess)
if err != nil {
return err
}
wc := db.writeConcern
if sess.TransactionRunning() {
wc = nil
}
if !wc.Acknowledged() {
sess = nil
}
selector := makePinnedSelector(sess, db.writeSelector)
op := operation.NewDropDatabase().
Session(sess).WriteConcern(wc).CommandMonitor(db.client.monitor).
ServerSelector(selector).ClusterClock(db.client.clock).
Database(db.name).Deployment(db.client.deployment).Crypt(db.client.cryptFLE).
ServerAPI(db.client.serverAPI).Authenticator(db.client.authenticator)
err = op.Execute(ctx)
var driverErr driver.Error
if err != nil && (!errors.As(err, &driverErr) || !driverErr.NamespaceNotFound()) {
return wrapErrors(err)
}
return nil
}
// ListCollectionSpecifications executes a listCollections command and returns a slice of CollectionSpecification
// instances representing the collections in the database.
//
// The filter parameter must be a document containing query operators and can be used to select which collections
// are included in the result. It cannot be nil. An empty document (e.g. bson.D{}) should be used to include all
// collections.
//
// The opts parameter can be used to specify options for the operation (see the options.ListCollectionsOptions
// documentation).
//
// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listCollections/.
func (db *Database) ListCollectionSpecifications(
ctx context.Context,
filter any,
opts ...options.Lister[options.ListCollectionsOptions],
) ([]CollectionSpecification, error) {
cursor, err := db.ListCollections(ctx, filter, opts...)
if err != nil {
return nil, err
}
var resp []struct {
Name string `bson:"name"`
Type string `bson:"type"`
Info *struct {
ReadOnly bool `bson:"readOnly"`
UUID *bson.Binary `bson:"uuid"`
} `bson:"info"`
Options bson.Raw `bson:"options"`
IDIndex indexListSpecificationResponse `bson:"idIndex"`
}
err = cursor.All(ctx, &resp)
if err != nil {
return nil, err
}
specs := make([]CollectionSpecification, len(resp))
for idx, spec := range resp {
specs[idx] = CollectionSpecification{
Name: spec.Name,
Type: spec.Type,
Options: spec.Options,
IDIndex: IndexSpecification(spec.IDIndex),
}
if spec.Info != nil {
specs[idx].ReadOnly = spec.Info.ReadOnly
specs[idx].UUID = spec.Info.UUID
}
// Pre-4.4 servers report a namespace in their responses, so we only set Namespace manually if it was not in
// the response.
if specs[idx].IDIndex.Namespace == "" {
specs[idx].IDIndex.Namespace = db.name + "." + specs[idx].Name
}
}
return specs, nil
}
// ListCollections executes a listCollections command and returns a cursor over the collections in the database.
//
// The filter parameter must be a document containing query operators and can be used to select which collections
// are included in the result. It cannot be nil. An empty document (e.g. bson.D{}) should be used to include all
// collections.
//
// The opts parameter can be used to specify options for the operation (see the options.ListCollectionsOptions
// documentation).
//
// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listCollections/.
//
// BUG(benjirewis): ListCollections prevents listing more than 100 collections per database when running against
// MongoDB version 2.6.
func (db *Database) ListCollections(
ctx context.Context,
filter any,
opts ...options.Lister[options.ListCollectionsOptions],
) (*Cursor, error) {
if ctx == nil {
ctx = context.Background()
}
args, err := mongoutil.NewOptions[options.ListCollectionsOptions](opts...)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
filterDoc, err := marshal(filter, db.bsonOpts, db.registry)
if err != nil {
return nil, err
}
sess := sessionFromContext(ctx)
if sess == nil && db.client.sessionPool != nil {
sess = session.NewImplicitClientSession(db.client.sessionPool, db.client.id)
}
err = db.client.validSession(sess)
if err != nil {
closeImplicitSession(sess)
return nil, err
}
var selector description.ServerSelector
selector = &serverselector.Composite{
Selectors: []description.ServerSelector{
&serverselector.ReadPref{ReadPref: readpref.Primary()},
&serverselector.Latency{Latency: db.client.localThreshold},
},
}
selector = makeReadPrefSelector(sess, selector, db.client.localThreshold)
op := operation.NewListCollections(filterDoc).
Session(sess).ReadPreference(db.readPreference).CommandMonitor(db.client.monitor).
ServerSelector(selector).ClusterClock(db.client.clock).
Database(db.name).Deployment(db.client.deployment).Crypt(db.client.cryptFLE).
ServerAPI(db.client.serverAPI).Timeout(db.client.timeout).Authenticator(db.client.authenticator)
cursorOpts := db.client.createBaseCursorOptions()
cursorOpts.MarshalValueEncoderFn = newEncoderFn(db.bsonOpts, db.registry)
if args.NameOnly != nil {
op = op.NameOnly(*args.NameOnly)
}
if args.BatchSize != nil {
cursorOpts.BatchSize = *args.BatchSize
op = op.BatchSize(*args.BatchSize)
}
if args.AuthorizedCollections != nil {
op = op.AuthorizedCollections(*args.AuthorizedCollections)
}
if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
op = op.RawData(rawData)
}
retry := driver.RetryNone
if db.client.retryReads {
retry = driver.RetryOncePerCommand
}
op = op.Retry(retry)
err = op.Execute(ctx)
if err != nil {
closeImplicitSession(sess)
return nil, wrapErrors(err)
}
bc, err := op.Result(cursorOpts)
if err != nil {
closeImplicitSession(sess)
return nil, wrapErrors(err)
}
cursor, err := newCursorWithSession(bc, db.bsonOpts, db.registry, sess,
withCursorOptionClientTimeout(db.client.timeout))
return cursor, wrapErrors(err)
}
// ListCollectionNames executes a listCollections command and returns a slice containing the names of the collections
// in the database. This method requires driver version >= 1.1.0.
//
// The filter parameter must be a document containing query operators and can be used to select which collections
// are included in the result. It cannot be nil. An empty document (e.g. bson.D{}) should be used to include all
// collections.
//
// The opts parameter can be used to specify options for the operation (see the options.ListCollectionsOptions
// documentation).
//
// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listCollections/.
//
// BUG(benjirewis): ListCollectionNames prevents listing more than 100 collections per database when running against
// MongoDB version 2.6.
func (db *Database) ListCollectionNames(
ctx context.Context,
filter any,
opts ...options.Lister[options.ListCollectionsOptions],
) ([]string, error) {
opts = append(opts, options.ListCollections().SetNameOnly(true))
res, err := db.ListCollections(ctx, filter, opts...)
if err != nil {
return nil, err
}
defer res.Close(ctx)
names := make([]string, 0)
for res.Next(ctx) {
elem, err := res.Current.LookupErr("name")
if err != nil {
return nil, err
}
if elem.Type != bson.TypeString {
return nil, fmt.Errorf("incorrect type for 'name'. got %v. want %v", elem.Type, bson.TypeString)
}
elemName := elem.StringValue()
names = append(names, elemName)
}
res.Close(ctx)
return names, nil
}
// Watch returns a change stream for all changes to the corresponding database. See
// https://www.mongodb.com/docs/manual/changeStreams/ for more information about change streams.
//
// The Database must be configured with read concern majority or no read concern for a change stream to be created
// successfully.
//
// The pipeline parameter must be a slice of documents, each representing a pipeline stage. The pipeline cannot be
// nil but can be empty. The stage documents must all be non-nil. See https://www.mongodb.com/docs/manual/changeStreams/ for
// a list of pipeline stages that can be used with change streams. For a pipeline of bson.D documents, the
// mongo.Pipeline{} type can be used.
//
// The opts parameter can be used to specify options for change stream creation (see the options.ChangeStreamOptions
// documentation).
func (db *Database) Watch(ctx context.Context, pipeline any,
opts ...options.Lister[options.ChangeStreamOptions],
) (*ChangeStream, error) {
csConfig := changeStreamConfig{
readConcern: db.readConcern,
readPreference: db.readPreference,
client: db.client,
bsonOpts: db.bsonOpts,
registry: db.registry,
streamType: DatabaseStream,
databaseName: db.Name(),
crypt: db.client.cryptFLE,
}
return newChangeStream(ctx, csConfig, pipeline, opts...)
}
// CreateCollection creates a new collection on the server with the specified
// name and options.
//
// MongoDB versions < 7.0 will return an error if the collection already exists.
// MongoDB versions >= 7.0 will not return an error if an existing collection
// created with the same name and options already exists.
//
// For more information about the command, see
// https://www.mongodb.com/docs/manual/reference/command/create/.
func (db *Database) CreateCollection(ctx context.Context, name string, opts ...options.Lister[options.CreateCollectionOptions]) error {
args, err := mongoutil.NewOptions(opts...)
if err != nil {
return fmt.Errorf("failed to construct options from builder: %w", err)
}
// Follow In-Use Encryption specification to check for encryptedFields.
// Check for encryptedFields from create options.
ef := args.EncryptedFields
// Check for encryptedFields from the client EncryptedFieldsMap.
if ef == nil {
ef = db.getEncryptedFieldsFromMap(name)
}
if ef != nil {
return db.createCollectionWithEncryptedFields(ctx, name, ef, opts...)
}
return db.createCollection(ctx, name, opts...)
}
// getEncryptedFieldsFromServer tries to get an "encryptedFields" document associated with collectionName by running the "listCollections" command.
// Returns nil and no error if the listCollections command succeeds, but "encryptedFields" is not present.
func (db *Database) getEncryptedFieldsFromServer(ctx context.Context, collectionName string) (any, error) {
// Check if collection has an EncryptedFields configured server-side.
collSpecs, err := db.ListCollectionSpecifications(ctx, bson.D{{"name", collectionName}})
if err != nil {
return nil, err
}
if len(collSpecs) == 0 {
return nil, nil
}
if len(collSpecs) > 1 {
return nil, fmt.Errorf("expected 1 or 0 results from listCollections, got %v", len(collSpecs))
}
collSpec := collSpecs[0]
rawValue, err := collSpec.Options.LookupErr("encryptedFields")
if errors.Is(err, bsoncore.ErrElementNotFound) {
return nil, nil
} else if err != nil {
return nil, err
}
encryptedFields, ok := rawValue.DocumentOK()
if !ok {
return nil, fmt.Errorf("expected encryptedFields of %v to be document, got %v", collectionName, rawValue.Type)
}
return encryptedFields, nil
}
// getEncryptedFieldsFromMap tries to get an "encryptedFields" document associated with collectionName by checking the client EncryptedFieldsMap.
// Returns nil and no error if an EncryptedFieldsMap is not configured, or does not contain an entry for collectionName.
func (db *Database) getEncryptedFieldsFromMap(collectionName string) any {
// Check the EncryptedFieldsMap
efMap := db.client.encryptedFieldsMap
if efMap == nil {
return nil
}
namespace := db.name + "." + collectionName
ef, ok := efMap[namespace]
if ok {
return ef
}
return nil
}
// createCollectionWithEncryptedFields creates a collection with an EncryptedFields.
func (db *Database) createCollectionWithEncryptedFields(
ctx context.Context,
name string,
ef any,
opts ...options.Lister[options.CreateCollectionOptions],
) error {
efBSON, err := marshal(ef, db.bsonOpts, db.registry)
if err != nil {
return fmt.Errorf("error transforming document: %w", err)
}
// Check the wire version to ensure server is 7.0.0 or newer.
// After the wire version check, and before creating the collections, it is possible the server state changes.
// That is OK. This wire version check is a best effort to inform users earlier if using a QEv2 driver with a QEv1 server.
{
const QEv2WireVersion = 21
ctx, cancel := csot.WithServerSelectionTimeout(ctx, db.client.deployment.GetServerSelectionTimeout())
defer cancel()
server, err := db.client.deployment.SelectServer(ctx, &serverselector.Write{})
if err != nil {
return fmt.Errorf("error selecting server to check maxWireVersion: %w", err)
}
conn, err := server.Connection(ctx)
if err != nil {
return fmt.Errorf("error getting connection to check maxWireVersion: %w", err)
}
defer conn.Close()
wireVersionRange := conn.Description().WireVersion
if wireVersionRange.Max < QEv2WireVersion {
return fmt.Errorf("driver support of Queryable Encryption is incompatible with server. Upgrade server to use Queryable Encryption. Got maxWireVersion %v but need maxWireVersion >= %v", wireVersionRange.Max, QEv2WireVersion)
}
}
// Create the two encryption-related, associated collections: `escCollection` and `ecocCollection`.
stateCollectionOpts := options.CreateCollection().
SetClusteredIndex(bson.D{{"key", bson.D{{"_id", 1}}}, {"unique", true}})
// Create ESCCollection.
escCollection, err := csfle.GetEncryptedStateCollectionName(efBSON, name, csfle.EncryptedStateCollection)
if err != nil {
return err
}
if err := db.createCollection(ctx, escCollection, stateCollectionOpts); err != nil {
return err
}
// Create ECOCCollection.
ecocCollection, err := csfle.GetEncryptedStateCollectionName(efBSON, name, csfle.EncryptedCompactionCollection)
if err != nil {
return err
}
if err := db.createCollection(ctx, ecocCollection, stateCollectionOpts); err != nil {
return err
}
// Create a data collection with the 'encryptedFields' option.
op, err := db.createCollectionOperation(name, opts...)
if err != nil {
return err
}
op.EncryptedFields(efBSON)
if err := db.executeCreateOperation(ctx, op); err != nil {
return err
}
// Create an index on the __safeContent__ field in the collection @collectionName.
if _, err := db.Collection(name).Indexes().CreateOne(ctx, IndexModel{Keys: bson.D{{"__safeContent__", 1}}}); err != nil {
return fmt.Errorf("error creating safeContent index: %w", err)
}
return nil
}
// createCollection creates a collection without EncryptedFields.
func (db *Database) createCollection(
ctx context.Context,
name string,
opts ...options.Lister[options.CreateCollectionOptions],
) error {
op, err := db.createCollectionOperation(name, opts...)
if err != nil {
return err
}
return db.executeCreateOperation(ctx, op)
}
func (db *Database) createCollectionOperation(
name string,
opts ...options.Lister[options.CreateCollectionOptions],
) (*operation.Create, error) {
args, err := mongoutil.NewOptions[options.CreateCollectionOptions](opts...)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
op := operation.NewCreate(name).ServerAPI(db.client.serverAPI).Authenticator(db.client.authenticator)
if args.Capped != nil {
op.Capped(*args.Capped)
}
if args.Collation != nil {
op.Collation(bsoncore.Document(toDocument(args.Collation)))
}
if args.ChangeStreamPreAndPostImages != nil {
csppi, err := marshal(args.ChangeStreamPreAndPostImages, db.bsonOpts, db.registry)
if err != nil {
return nil, err
}
op.ChangeStreamPreAndPostImages(csppi)
}
if args.DefaultIndexOptions != nil {
defaultIndexArgs, err := mongoutil.NewOptions[options.DefaultIndexOptions](args.DefaultIndexOptions)
if err != nil {
return nil, fmt.Errorf("failed to construct DefaultIndexArgs from options: %w", err)
}
idx, doc := bsoncore.AppendDocumentStart(nil)
if defaultIndexArgs.StorageEngine != nil {
storageEngine, err := marshal(defaultIndexArgs.StorageEngine, db.bsonOpts, db.registry)
if err != nil {
return nil, err
}
doc = bsoncore.AppendDocumentElement(doc, "storageEngine", storageEngine)
}
doc, err = bsoncore.AppendDocumentEnd(doc, idx)
if err != nil {
return nil, err
}
op.IndexOptionDefaults(doc)
}
if args.MaxDocuments != nil {
op.Max(*args.MaxDocuments)
}
if args.SizeInBytes != nil {
op.Size(*args.SizeInBytes)
}
if args.StorageEngine != nil {
storageEngine, err := marshal(args.StorageEngine, db.bsonOpts, db.registry)
if err != nil {
return nil, err
}
op.StorageEngine(storageEngine)
}
if args.ValidationAction != nil {
op.ValidationAction(*args.ValidationAction)
}
if args.ValidationLevel != nil {
op.ValidationLevel(*args.ValidationLevel)
}
if args.Validator != nil {
validator, err := marshal(args.Validator, db.bsonOpts, db.registry)
if err != nil {
return nil, err
}
op.Validator(validator)
}
if args.ExpireAfterSeconds != nil {
op.ExpireAfterSeconds(*args.ExpireAfterSeconds)
}
if args.TimeSeriesOptions != nil {
timeSeriesArgs, err := mongoutil.NewOptions[options.TimeSeriesOptions](args.TimeSeriesOptions)
if err != nil {
return nil, fmt.Errorf("failed to construct DefaultIndexArgs from options: %w", err)
}
idx, doc := bsoncore.AppendDocumentStart(nil)
doc = bsoncore.AppendStringElement(doc, "timeField", timeSeriesArgs.TimeField)
if timeSeriesArgs.MetaField != nil {
doc = bsoncore.AppendStringElement(doc, "metaField", *timeSeriesArgs.MetaField)
}
if timeSeriesArgs.Granularity != nil {
doc = bsoncore.AppendStringElement(doc, "granularity", *timeSeriesArgs.Granularity)
}
if timeSeriesArgs.BucketMaxSpan != nil {
bmss := int64(*timeSeriesArgs.BucketMaxSpan / time.Second)
doc = bsoncore.AppendInt64Element(doc, "bucketMaxSpanSeconds", bmss)
}
if timeSeriesArgs.BucketRounding != nil {
brs := int64(*timeSeriesArgs.BucketRounding / time.Second)
doc = bsoncore.AppendInt64Element(doc, "bucketRoundingSeconds", brs)
}
doc, err = bsoncore.AppendDocumentEnd(doc, idx)
if err != nil {
return nil, err
}
op.TimeSeries(doc)
}
if args.ClusteredIndex != nil {
clusteredIndex, err := marshal(args.ClusteredIndex, db.bsonOpts, db.registry)
if err != nil {
return nil, err
}
op.ClusteredIndex(clusteredIndex)
}
return op, nil
}
// CreateView creates a view on the server.
//
// The viewName parameter specifies the name of the view to create. The viewOn
// parameter specifies the name of the collection or view on which this view
// will be created. The pipeline parameter specifies an aggregation pipeline
// that will be exececuted against the source collection or view to create this
// view.
//
// MongoDB versions < 7.0 will return an error if the view already exists.
// MongoDB versions >= 7.0 will not return an error if an existing view created
// with the same name and options already exists.
//
// See https://www.mongodb.com/docs/manual/core/views/ for more information
// about views.
func (db *Database) CreateView(ctx context.Context, viewName, viewOn string, pipeline any,
opts ...options.Lister[options.CreateViewOptions],
) error {
pipelineArray, _, err := marshalAggregatePipeline(pipeline, db.bsonOpts, db.registry)
if err != nil {
return err
}
op := operation.NewCreate(viewName).
ViewOn(viewOn).
Pipeline(pipelineArray).
ServerAPI(db.client.serverAPI).Authenticator(db.client.authenticator)
args, err := mongoutil.NewOptions(opts...)
if err != nil {
return fmt.Errorf("failed to construct options from builder: %w", err)
}
if args.Collation != nil {
op.Collation(bsoncore.Document(toDocument(args.Collation)))
}
return db.executeCreateOperation(ctx, op)
}
func (db *Database) executeCreateOperation(ctx context.Context, op *operation.Create) error {
sess := sessionFromContext(ctx)
if sess == nil && db.client.sessionPool != nil {
sess = session.NewImplicitClientSession(db.client.sessionPool, db.client.id)
defer sess.EndSession()
}
err := db.client.validSession(sess)
if err != nil {
return err
}
wc := db.writeConcern
if sess.TransactionRunning() {
wc = nil
}
if !wc.Acknowledged() {
sess = nil
}
selector := makePinnedSelector(sess, db.writeSelector)
op = op.Session(sess).
WriteConcern(wc).
CommandMonitor(db.client.monitor).
ServerSelector(selector).
ClusterClock(db.client.clock).
Database(db.name).
Deployment(db.client.deployment).
Crypt(db.client.cryptFLE)
return wrapErrors(op.Execute(ctx))
}
// GridFSBucket is used to construct a GridFS bucket which can be used as a
// container for files.
func (db *Database) GridFSBucket(opts ...options.Lister[options.BucketOptions]) *GridFSBucket {
b := &GridFSBucket{
name: "fs",
chunkSize: DefaultGridFSChunkSize,
db: db,
}
bo, _ := mongoutil.NewOptions[options.BucketOptions](opts...)
if bo.Name != nil {
b.name = *bo.Name
}
if bo.ChunkSizeBytes != nil {
b.chunkSize = *bo.ChunkSizeBytes
}
if bo.WriteConcern != nil {
b.wc = bo.WriteConcern
}
if bo.ReadConcern != nil {
b.rc = bo.ReadConcern
}
if bo.ReadPreference != nil {
b.rp = bo.ReadPreference
}
collOpts := options.Collection().SetWriteConcern(b.wc).SetReadConcern(b.rc).SetReadPreference(b.rp)
b.chunksColl = db.Collection(b.name+".chunks", collOpts)
b.filesColl = db.Collection(b.name+".files", collOpts)
b.readBuf = make([]byte, b.chunkSize)
b.writeBuf = make([]byte, b.chunkSize)
return b
}

View File

@@ -0,0 +1,164 @@
// 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
// NOTE: This documentation should be kept in line with the Example* test functions.
// Package mongo provides a MongoDB Driver API for Go.
//
// Basic usage of the driver starts with creating a Client from a connection
// string. To do so, call Connect:
//
// ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
// defer cancel()
// client, err := mongo.Connect( options.Client().ApplyURI("mongodb://foo:bar@localhost:27017"))
// if err != nil { return err }
//
// This will create a new client and start monitoring the MongoDB server on localhost.
// The Database and Collection types can be used to access the database:
//
// collection := client.Database("baz").Collection("qux")
//
// A Collection can be used to query the database or insert documents:
//
// res, err := collection.InsertOne(context.Background(), bson.M{"hello": "world"})
// if err != nil { return err }
// id := res.InsertedID
//
// Several methods return a cursor, which can be used like this:
//
// cur, err := collection.Find(context.Background(), bson.D{})
// if err != nil { log.Fatal(err) }
// defer cur.Close(context.Background())
// for cur.Next(context.Background()) {
// // To decode into a struct, use cursor.Decode()
// result := struct{
// Foo string
// Bar int32
// }{}
// err := cur.Decode(&result)
// if err != nil { log.Fatal(err) }
// // do something with result...
//
// // To get the raw bson bytes use cursor.Current
// raw := cur.Current
// // do something with raw...
// }
// if err := cur.Err(); err != nil {
// return err
// }
//
// Cursor.All will decode all of the returned elements at once:
//
// var results []struct{
// Foo string
// Bar int32
// }
// if err = cur.All(context.Background(), &results); err != nil {
// log.Fatal(err)
// }
// // do something with results...
//
// Methods that only return a single document will return a *SingleResult, which works
// like a *sql.Row:
//
// result := struct{
// Foo string
// Bar int32
// }{}
// filter := bson.D{{"hello", "world"}}
// err := collection.FindOne(context.Background(), filter).Decode(&result)
// if err != nil { return err }
// // do something with result...
//
// All Client, Collection, and Database methods that take parameters of type any
// will return ErrNilDocument if nil is passed in for an any.
//
// Additional examples can be found under the examples directory in the driver's repository and
// on the MongoDB website.
//
// # Error Handling
//
// Errors from the MongoDB server will implement the ServerError interface, which has functions to check for specific
// error codes, labels, and message substrings. These can be used to check for and handle specific errors. Some methods,
// like InsertMany and BulkWrite, can return an error representing multiple errors, and in those cases the ServerError
// functions will return true if any of the contained errors satisfy the check.
//
// There are also helper functions to check for certain specific types of errors:
//
// IsDuplicateKeyError(error)
// IsNetworkError(error)
// IsTimeout(error)
//
// # Potential DNS Issues
//
// Building with Go 1.11+ and using connection strings with the "mongodb+srv"[1] scheme is unfortunately
// incompatible with some DNS servers in the wild due to the change introduced in
// https://github.com/golang/go/issues/10622. You may receive an error with the message "cannot unmarshal DNS message"
// while running an operation when using DNS servers that non-compliantly compress SRV records. Old versions of kube-dns
// and the native DNS resolver (systemd-resolver) on Ubuntu 18.04 are known to be non-compliant in this manner. We suggest
// using a different DNS server (8.8.8.8 is the common default), and, if that's not possible, avoiding the "mongodb+srv"
// scheme.
//
// # In-Use Encryption
//
// MongoDB provides two approaches to In-Use Encryption: Queryable Encryption (QE) and Client-Side Field Level Encryption (CSFLE).
//
// The Queryable Encryption and CSFLE features share much of the same API with some exceptions.
//
// - AutoEncryptionOptions.SetEncryptedFieldsMap only applies to Queryable Encryption.
// - AutoEncryptionOptions.SetSchemaMap only applies to CSFLE.
//
// In-use encryption is a new feature in MongoDB 4.2 that allows specific data fields to be encrypted. Using this
// feature requires specifying the "cse" build tag during compilation:
//
// go build -tags cse
//
// Note: Auto encryption is an enterprise- and Atlas-only feature.
//
// The libmongocrypt C library is required when using in-use encryption. Specific versions of libmongocrypt
// are required for different versions of the Go Driver:
//
// - Go Driver v1.2.0 requires libmongocrypt v1.0.0 or higher
//
// - Go Driver v1.5.0 requires libmongocrypt v1.1.0 or higher
//
// - Go Driver v1.8.0 requires libmongocrypt v1.3.0 or higher
//
// - Go Driver v1.10.0 requires libmongocrypt v1.5.0 or higher.
// There is a severe bug when calling RewrapManyDataKey with libmongocrypt versions less than 1.5.2.
// This bug may result in data corruption.
// Please use libmongocrypt 1.5.2 or higher when calling RewrapManyDataKey.
//
// - Go Driver v1.12.0 requires libmongocrypt v1.8.0 or higher.
//
// To install libmongocrypt, follow the instructions for your
// operating system:
//
// 1. Linux: follow the instructions listed at
// https://github.com/mongodb/libmongocrypt#installing-libmongocrypt-from-distribution-packages to install the correct
// deb/rpm package.
//
// 2. Mac: Follow the instructions listed at https://github.com/mongodb/libmongocrypt#installing-libmongocrypt-on-macos
// to install packages via brew and compile the libmongocrypt source code.
//
// 3. Windows:
//
// mkdir -p c:/libmongocrypt/bin
// mkdir -p c:/libmongocrypt/include
//
// // Run the curl command in an empty directory as it will create new directories when unpacked.
// curl https://s3.amazonaws.com/mciuploads/libmongocrypt/windows/latest_release/libmongocrypt.tar.gz --output libmongocrypt.tar.gz
// tar -xvzf libmongocrypt.tar.gz
//
// cp ./bin/mongocrypt.dll c:/libmongocrypt/bin
// cp ./include/mongocrypt/*.h c:/libmongocrypt/include
// export PATH=$PATH:/cygdrive/c/libmongocrypt/bin
//
// libmongocrypt communicates with the mongocryptd process or mongo_crypt shared library for automatic encryption.
// See AutoEncryptionOpts.SetExtraOptions for options to configure use of mongocryptd or mongo_crypt.
//
// [1] See https://www.mongodb.com/docs/manual/reference/connection-string/#dns-seedlist-connection-format
package mongo

View File

@@ -0,0 +1,931 @@
// 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"
"net"
"reflect"
"strings"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/codecutil"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/mongocrypt"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/topology"
)
// ErrClientDisconnected is returned when disconnected Client is used to run an operation.
var ErrClientDisconnected = errors.New("client is disconnected")
// InvalidArgumentError wraps an invalid argument error.
type InvalidArgumentError struct {
wrapped error
}
// Error implements the error interface.
func (e InvalidArgumentError) Error() string {
return e.wrapped.Error()
}
// Unwrap returns the underlying error.
func (e InvalidArgumentError) Unwrap() error {
return e.wrapped
}
// ErrMultipleIndexDrop is returned if multiple indexes would be dropped from a call to IndexView.DropOne.
var ErrMultipleIndexDrop error = InvalidArgumentError{errors.New("multiple indexes would be dropped")}
// ErrNilDocument is returned when a nil document is passed to a CRUD method.
var ErrNilDocument error = InvalidArgumentError{errors.New("document is nil")}
// ErrNilValue is returned when a nil value is passed to a CRUD method.
var ErrNilValue error = InvalidArgumentError{errors.New("value is nil")}
// ErrEmptySlice is returned when an empty slice is passed to a CRUD method that requires a non-empty slice.
var ErrEmptySlice error = InvalidArgumentError{errors.New("must provide at least one element in input slice")}
// ErrNotSlice is returned when a type other than slice is passed to InsertMany.
var ErrNotSlice error = InvalidArgumentError{errors.New("must provide a non-empty slice")}
// ErrMapForOrderedArgument is returned when a map with multiple keys is passed to a CRUD method for an ordered parameter
type ErrMapForOrderedArgument struct {
ParamName string
}
// Error implements the error interface.
func (e ErrMapForOrderedArgument) Error() string {
return fmt.Sprintf("multi-key map passed in for ordered parameter %v", e.ParamName)
}
// wrapErrors wraps error types and values that are defined in "internal" and
// "x" packages with error types and values that are defined in this package.
// That allows users to inspect the errors using errors.Is/errors.As without
// relying on "internal" or "x" packages.
func wrapErrors(err error) error {
// Return nil when err is nil to avoid costly reflection logic below.
if err == nil {
return nil
}
// Do not propagate the acknowledgement sentinel error. For DDL commands,
// (creating indexes, dropping collections, etc) acknowledgement should be
// ignored. For non-DDL write commands (insert, update, etc), acknowledgement
// should be be propagated at the result-level: e.g.,
// SingleResult.Acknowledged.
if errors.Is(err, driver.ErrUnacknowledgedWrite) {
return nil
}
if errors.Is(err, topology.ErrTopologyClosed) {
return ErrClientDisconnected
}
var de driver.Error
if errors.As(err, &de) {
return CommandError{
Code: de.Code,
Message: de.Message,
Labels: de.Labels,
Name: de.Name,
Wrapped: err,
Raw: bson.Raw(de.Raw),
// Set wrappedMsgOnly=true here so that the Code and Message are not
// repeated multiple times in the error string. We expect that the
// wrapped driver.Error already contains that info in the error
// string.
wrappedMsgOnly: true,
}
}
var qe driver.QueryFailureError
if errors.As(err, &qe) {
// qe.Message is "command failure"
ce := CommandError{
Name: qe.Message,
Wrapped: err,
Raw: bson.Raw(qe.Response),
// Don't set wrappedMsgOnly=true here because the code below adds
// additional error context that is not provided by the
// driver.QueryFailureError. Additionally, driver.QueryFailureError
// is only returned when parsing OP_QUERY replies (OP_REPLY), so
// it's unlikely this block will ever be run now that MongoDB 3.6 is
// no longer supported.
}
dollarErr, err := qe.Response.LookupErr("$err")
if err == nil {
ce.Message, _ = dollarErr.StringValueOK()
}
code, err := qe.Response.LookupErr("code")
if err == nil {
ce.Code, _ = code.Int32OK()
}
return ce
}
var me mongocrypt.Error
if errors.As(err, &me) {
return MongocryptError{
Code: me.Code,
Message: me.Message,
wrapped: err,
// Set wrappedMsgOnly=true here so that the Code and Message are not
// repeated multiple times in the error string. We expect that the
// wrapped mongocrypt.Error already contains that info in the error
// string.
wrappedMsgOnly: true,
}
}
if errors.Is(err, codecutil.ErrNilValue) {
return ErrNilValue
}
var marshalErr codecutil.MarshalError
if errors.As(err, &marshalErr) {
return MarshalError{
Value: marshalErr.Value,
Err: err,
// Set wrappedMsgOnly=true here so that the Value is not repeated
// multiple times in the error string. We expect that the wrapped
// codecutil.MarshalError already contains that info in the error
// string.
wrappedMsgOnly: true,
}
}
return err
}
// IsDuplicateKeyError returns true if err is a duplicate key error. For BulkWriteExceptions,
// IsDuplicateKeyError returns true if at least one of the errors is a duplicate key error.
func IsDuplicateKeyError(err error) bool {
if se := ServerError(nil); errors.As(err, &se) {
return se.HasErrorCode(11000) || // Duplicate key error.
se.HasErrorCode(11001) || // Duplicate key error on update.
// Duplicate key error in a capped collection. See SERVER-7164.
se.HasErrorCode(12582) ||
// Mongos insert error caused by a duplicate key error. See
// SERVER-11493.
se.HasErrorCodeWithMessage(16460, " E11000 ")
}
return false
}
// timeoutErrs is a list of error values that indicate a timeout happened.
var timeoutErrs = [...]error{
context.DeadlineExceeded,
driver.ErrDeadlineWouldBeExceeded,
}
// IsTimeout returns true if err was caused by a timeout. For error chains,
// IsTimeout returns true if any error in the chain was caused by a timeout.
func IsTimeout(err error) bool {
// Check if the error chain contains any of the timeout error values.
for _, target := range timeoutErrs {
if errors.Is(err, target) {
return true
}
}
// Check if the error chain contains any error types that can indicate
// timeout.
if errors.As(err, &topology.WaitQueueTimeoutError{}) {
return true
}
if ce := (CommandError{}); errors.As(err, &ce) && ce.IsMaxTimeMSExpiredError() {
return true
}
if we := (WriteException{}); errors.As(err, &we) && we.WriteConcernError != nil && we.WriteConcernError.IsMaxTimeMSExpiredError() {
return true
}
if ne := net.Error(nil); errors.As(err, &ne) {
return ne.Timeout()
}
// Check timeout error labels.
if le := LabeledError(nil); errors.As(err, &le) {
if le.HasErrorLabel("NetworkTimeoutError") || le.HasErrorLabel("ExceededTimeLimitError") {
return true
}
}
return false
}
// errorHasLabel returns true if err contains the specified label
func errorHasLabel(err error, label string) bool {
var le LabeledError
return errors.As(err, &le) && le.HasErrorLabel(label)
}
// IsNetworkError returns true if err is a network error
func IsNetworkError(err error) bool {
return errorHasLabel(err, "NetworkError")
}
// MarshalError is returned when attempting to marshal a value into a document
// results in an error.
type MarshalError struct {
Value any
Err error
// If wrappedMsgOnly is true, Error() only returns the error message from
// the "Err" error.
//
// This is typically only set by the wrapErrors function, which uses
// MarshalError to wrap codecutil.MarshalError, allowing users to access the
// "Value" from the underlying error but preventing duplication in the error
// string.
wrappedMsgOnly bool
}
// Error implements the error interface.
func (me MarshalError) Error() string {
// If the MarshalError was created with wrappedMsgOnly=true, only return the
// error from the wrapped error. See the MarshalError.wrappedMsgOnly docs
// for more info.
if me.wrappedMsgOnly {
return me.Err.Error()
}
return fmt.Sprintf("cannot marshal type %s to a BSON Document: %v", reflect.TypeOf(me.Value), me.Err)
}
func (me MarshalError) Unwrap() error { return me.Err }
// MongocryptError represents an libmongocrypt error during in-use encryption.
type MongocryptError struct {
Code int32
Message string
wrapped error
// If wrappedMsgOnly is true, Error() only returns the error message from
// the "wrapped" error.
//
// This is typically only set by the wrapErrors function, which uses
// MarshalError to wrap mongocrypt.Error, allowing users to access the
// "Code" and "Message" from the underlying error but preventing duplication
// in the error string.
wrappedMsgOnly bool
}
// Error implements the error interface.
func (m MongocryptError) Error() string {
// If the MongocryptError was created with wrappedMsgOnly=true, only return
// the error from the wrapped error. See the MongocryptError.wrappedMsgOnly
// docs for more info.
if m.wrappedMsgOnly {
return m.wrapped.Error()
}
return fmt.Sprintf("mongocrypt error %d: %v", m.Code, m.Message)
}
// Unwrap returns the underlying error.
func (m MongocryptError) Unwrap() error { return m.wrapped }
// EncryptionKeyVaultError represents an error while communicating with the key vault collection during in-use
// encryption.
type EncryptionKeyVaultError struct {
Wrapped error
}
// Error implements the error interface.
func (ekve EncryptionKeyVaultError) Error() string {
return fmt.Sprintf("key vault communication error: %v", ekve.Wrapped)
}
// Unwrap returns the underlying error.
func (ekve EncryptionKeyVaultError) Unwrap() error {
return ekve.Wrapped
}
// MongocryptdError represents an error while communicating with mongocryptd during in-use encryption.
type MongocryptdError struct {
Wrapped error
}
// Error implements the error interface.
func (e MongocryptdError) Error() string {
return fmt.Sprintf("mongocryptd communication error: %v", e.Wrapped)
}
// Unwrap returns the underlying error.
func (e MongocryptdError) Unwrap() error {
return e.Wrapped
}
// LabeledError is an interface for errors with labels.
type LabeledError interface {
error
// HasErrorLabel returns true if the error contains the specified label.
HasErrorLabel(string) bool
}
type errorCoder interface {
ErrorCodes() []int
}
var _ errorCoder = ServerError(nil)
// ServerError is the interface implemented by errors returned from the server. Custom implementations of this
// interface should not be used in production.
type ServerError interface {
LabeledError
// HasErrorCode returns true if the error has the specified code.
HasErrorCode(int) bool
// HasErrorMessage returns true if the error contains the specified message.
HasErrorMessage(string) bool
// HasErrorCodeWithMessage returns true if any of the contained errors have the specified code and message.
HasErrorCodeWithMessage(int, string) bool
// ErrorCodes returns all error codes (unsorted) in the servers response.
// This would include nested errors (e.g., write concern errors) for
// supporting implementations (e.g., BulkWriteException) as well as the
// top-level error code.
ErrorCodes() []int
serverError()
}
func hasErrorCode(srvErr ServerError, code int) bool {
for _, srvErrCode := range srvErr.ErrorCodes() {
if code == srvErrCode {
return true
}
}
return false
}
var (
_ ServerError = CommandError{}
_ ServerError = WriteError{}
_ ServerError = WriteException{}
_ ServerError = BulkWriteException{}
)
var _ error = ClientBulkWriteException{}
// CommandError represents a server error during execution of a command. This can be returned by any operation.
type CommandError struct {
Code int32
Message string
Labels []string // Categories to which the error belongs
Name string // A human-readable name corresponding to the error code
Wrapped error // The underlying error, if one exists.
Raw bson.Raw // The original server response containing the error.
// If wrappedMsgOnly is true, Error() only returns the error message from
// the "Wrapped" error.
//
// This is typically only set by the wrapErrors function, which uses
// CommandError to wrap driver.Error, allowing users to access the "Code",
// "Message", "Labels", "Name", and "Raw" from the underlying error but
// preventing duplication in the error string.
wrappedMsgOnly bool
}
// Error implements the error interface.
func (e CommandError) Error() string {
// If the CommandError was created with wrappedMsgOnly=true, only return the
// error from the wrapped error. See the CommandError.wrappedMsgOnly docs
// for more info.
if e.wrappedMsgOnly {
return e.Wrapped.Error()
}
var msg string
if e.Name != "" {
msg += fmt.Sprintf("(%v)", e.Name)
}
if e.Message != "" {
msg += " " + e.Message
}
if e.Wrapped != nil {
msg += ": " + e.Wrapped.Error()
}
return msg
}
// Unwrap returns the underlying error.
func (e CommandError) Unwrap() error {
return e.Wrapped
}
// HasErrorCode returns true if the error has the specified code.
func (e CommandError) HasErrorCode(code int) bool {
return int(e.Code) == code
}
// ErrorCodes returns a list of error codes returned by the server.
func (e CommandError) ErrorCodes() []int {
return []int{int(e.Code)}
}
// HasErrorLabel returns true if the error contains the specified label.
func (e CommandError) HasErrorLabel(label string) bool {
for _, l := range e.Labels {
if l == label {
return true
}
}
return false
}
// HasErrorMessage returns true if the error contains the specified message.
func (e CommandError) HasErrorMessage(message string) bool {
return strings.Contains(e.Message, message)
}
// HasErrorCodeWithMessage returns true if the error has the specified code and Message contains the specified message.
func (e CommandError) HasErrorCodeWithMessage(code int, message string) bool {
return int(e.Code) == code && strings.Contains(e.Message, message)
}
// IsMaxTimeMSExpiredError returns true if the error is a MaxTimeMSExpired error.
func (e CommandError) IsMaxTimeMSExpiredError() bool {
return e.Code == 50 || e.Name == "MaxTimeMSExpired"
}
// serverError implements the ServerError interface.
func (e CommandError) serverError() {}
// WriteError is an error that occurred during execution of a write operation. This error type is only returned as part
// of a WriteException or BulkWriteException.
type WriteError struct {
// The index of the write in the slice passed to an InsertMany or BulkWrite operation that caused this error.
Index int
Code int
Message string
Details bson.Raw
// The original write error from the server response.
Raw bson.Raw
}
func (we WriteError) Error() string {
msg := we.Message
if len(we.Details) > 0 {
msg = fmt.Sprintf("%s: %s", msg, we.Details.String())
}
return msg
}
// HasErrorCode returns true if the error has the specified code.
func (we WriteError) HasErrorCode(code int) bool {
return we.Code == code
}
// ErrorCodes returns a list of error codes returned by the server.
func (we WriteError) ErrorCodes() []int {
return []int{we.Code}
}
// HasErrorLabel returns true if the error contains the specified label. WriteErrors do not contain labels,
// so we always return false.
func (we WriteError) HasErrorLabel(string) bool {
return false
}
// HasErrorMessage returns true if the error contains the specified message.
func (we WriteError) HasErrorMessage(message string) bool {
return strings.Contains(we.Message, message)
}
// HasErrorCodeWithMessage returns true if the error has the specified code and Message contains the specified message.
func (we WriteError) HasErrorCodeWithMessage(code int, message string) bool {
return we.Code == code && strings.Contains(we.Message, message)
}
// serverError implements the ServerError interface.
func (we WriteError) serverError() {}
// WriteErrors is a group of write errors that occurred during execution of a write operation.
type WriteErrors []WriteError
// Error implements the error interface.
func (we WriteErrors) Error() string {
errs := make([]error, len(we))
for i := 0; i < len(we); i++ {
errs[i] = we[i]
}
// WriteErrors isn't returned from batch operations, but we can still use the same formatter.
return "write errors: " + joinBatchErrors(errs)
}
func writeErrorsFromDriverWriteErrors(errs driver.WriteErrors) WriteErrors {
wes := make(WriteErrors, 0, len(errs))
for _, err := range errs {
wes = append(wes, WriteError{
Index: int(err.Index),
Code: int(err.Code),
Message: err.Message,
Details: bson.Raw(err.Details),
Raw: bson.Raw(err.Raw),
})
}
return wes
}
// WriteConcernError represents a write concern failure during execution of a write operation. This error type is only
// returned as part of a WriteException or a BulkWriteException.
type WriteConcernError struct {
Name string
Code int
Message string
Details bson.Raw
Raw bson.Raw // The original write concern error from the server response.
}
// Error implements the error interface.
func (wce WriteConcernError) Error() string {
if wce.Name != "" {
return fmt.Sprintf("(%v) %v", wce.Name, wce.Message)
}
return wce.Message
}
// IsMaxTimeMSExpiredError returns true if the error is a MaxTimeMSExpired error.
func (wce WriteConcernError) IsMaxTimeMSExpiredError() bool {
return wce.Code == 50
}
// WriteException is the error type returned by the InsertOne, DeleteOne, DeleteMany, UpdateOne, UpdateMany, and
// ReplaceOne operations.
type WriteException struct {
// The write concern error that occurred, or nil if there was none.
WriteConcernError *WriteConcernError
// The write errors that occurred during operation execution.
WriteErrors WriteErrors
// The categories to which the exception belongs.
Labels []string
// The original server response containing the error.
Raw bson.Raw
}
// Error implements the error interface.
func (mwe WriteException) Error() string {
causes := make([]string, 0, 2)
if mwe.WriteConcernError != nil {
causes = append(causes, "write concern error: "+mwe.WriteConcernError.Error())
}
if len(mwe.WriteErrors) > 0 {
// The WriteErrors error message already starts with "write errors:", so don't add it to the
// error message again.
causes = append(causes, mwe.WriteErrors.Error())
}
message := "write exception: "
if len(causes) == 0 {
return message + "no causes"
}
return message + strings.Join(causes, ", ")
}
// HasErrorCode returns true if the error has the specified code.
func (mwe WriteException) HasErrorCode(code int) bool {
return hasErrorCode(mwe, code)
}
// ErrorCodes returns a list of error codes returned by the server.
func (mwe WriteException) ErrorCodes() []int {
errorCodes := []int{}
for _, writeError := range mwe.WriteErrors {
errorCodes = append(errorCodes, writeError.Code)
}
if mwe.WriteConcernError != nil {
errorCodes = append(errorCodes, mwe.WriteConcernError.Code)
}
return errorCodes
}
// HasErrorLabel returns true if the error contains the specified label.
func (mwe WriteException) HasErrorLabel(label string) bool {
for _, l := range mwe.Labels {
if l == label {
return true
}
}
return false
}
// HasErrorMessage returns true if the error contains the specified message.
func (mwe WriteException) HasErrorMessage(message string) bool {
if mwe.WriteConcernError != nil && strings.Contains(mwe.WriteConcernError.Message, message) {
return true
}
for _, we := range mwe.WriteErrors {
if strings.Contains(we.Message, message) {
return true
}
}
return false
}
// HasErrorCodeWithMessage returns true if any of the contained errors have the specified code and message.
func (mwe WriteException) HasErrorCodeWithMessage(code int, message string) bool {
if mwe.WriteConcernError != nil &&
mwe.WriteConcernError.Code == code && strings.Contains(mwe.WriteConcernError.Message, message) {
return true
}
for _, we := range mwe.WriteErrors {
if we.Code == code && strings.Contains(we.Message, message) {
return true
}
}
return false
}
// serverError implements the ServerError interface.
func (mwe WriteException) serverError() {}
func convertDriverWriteConcernError(wce *driver.WriteConcernError) *WriteConcernError {
if wce == nil {
return nil
}
return &WriteConcernError{
Name: wce.Name,
Code: int(wce.Code),
Message: wce.Message,
Details: bson.Raw(wce.Details),
Raw: bson.Raw(wce.Raw),
}
}
// BulkWriteError is an error that occurred during execution of one operation in a BulkWrite. This error type is only
// returned as part of a BulkWriteException.
type BulkWriteError struct {
WriteError // The WriteError that occurred.
Request WriteModel // The WriteModel that caused this error.
}
// Error implements the error interface.
func (bwe BulkWriteError) Error() string {
return bwe.WriteError.Error()
}
// BulkWriteException is the error type returned by BulkWrite and InsertMany operations.
type BulkWriteException struct {
// The write concern error that occurred, or nil if there was none.
WriteConcernError *WriteConcernError
// The write errors that occurred during operation execution.
WriteErrors []BulkWriteError
// The categories to which the exception belongs.
Labels []string
}
// Error implements the error interface.
func (bwe BulkWriteException) Error() string {
causes := make([]string, 0, 2)
if bwe.WriteConcernError != nil {
causes = append(causes, "write concern error: "+bwe.WriteConcernError.Error())
}
if len(bwe.WriteErrors) > 0 {
errs := make([]error, len(bwe.WriteErrors))
for i := 0; i < len(bwe.WriteErrors); i++ {
errs[i] = &bwe.WriteErrors[i]
}
causes = append(causes, "write errors: "+joinBatchErrors(errs))
}
message := "bulk write exception: "
if len(causes) == 0 {
return message + "no causes"
}
return "bulk write exception: " + strings.Join(causes, ", ")
}
// HasErrorCode returns true if any of the errors have the specified code.
func (bwe BulkWriteException) HasErrorCode(code int) bool {
return hasErrorCode(bwe, code)
}
// ErrorCodes returns a list of error codes returned by the server.
func (bwe BulkWriteException) ErrorCodes() []int {
errorCodes := []int{}
for _, writeError := range bwe.WriteErrors {
errorCodes = append(errorCodes, writeError.Code)
}
if bwe.WriteConcernError != nil {
errorCodes = append(errorCodes, bwe.WriteConcernError.Code)
}
return errorCodes
}
// HasErrorLabel returns true if the error contains the specified label.
func (bwe BulkWriteException) HasErrorLabel(label string) bool {
for _, l := range bwe.Labels {
if l == label {
return true
}
}
return false
}
// HasErrorMessage returns true if the error contains the specified message.
func (bwe BulkWriteException) HasErrorMessage(message string) bool {
if bwe.WriteConcernError != nil && strings.Contains(bwe.WriteConcernError.Message, message) {
return true
}
for _, we := range bwe.WriteErrors {
if strings.Contains(we.Message, message) {
return true
}
}
return false
}
// HasErrorCodeWithMessage returns true if any of the contained errors have the specified code and message.
func (bwe BulkWriteException) HasErrorCodeWithMessage(code int, message string) bool {
if bwe.WriteConcernError != nil &&
bwe.WriteConcernError.Code == code && strings.Contains(bwe.WriteConcernError.Message, message) {
return true
}
for _, we := range bwe.WriteErrors {
if we.Code == code && strings.Contains(we.Message, message) {
return true
}
}
return false
}
// serverError implements the ServerError interface.
func (bwe BulkWriteException) serverError() {}
// ClientBulkWriteException is the error type returned by ClientBulkWrite operations.
type ClientBulkWriteException struct {
// A top-level error that occurred when attempting to communicate with the server
// or execute the bulk write. This value may not be populated if the exception was
// thrown due to errors occurring on individual writes.
WriteError *WriteError
// The write concern errors that occurred.
WriteConcernErrors []WriteConcernError
// The write errors that occurred during individual operation execution.
// This map will contain at most one entry if the bulk write was ordered.
WriteErrors map[int]WriteError
// The results of any successful operations that were performed before the error
// was encountered.
PartialResult *ClientBulkWriteResult
}
// Error implements the error interface.
func (bwe ClientBulkWriteException) Error() string {
causes := make([]string, 0, 4)
if bwe.WriteError != nil {
causes = append(causes, "top level error: "+bwe.WriteError.Error())
}
if len(bwe.WriteConcernErrors) > 0 {
errs := make([]error, len(bwe.WriteConcernErrors))
for i := 0; i < len(bwe.WriteConcernErrors); i++ {
errs[i] = bwe.WriteConcernErrors[i]
}
causes = append(causes, "write concern errors: "+joinBatchErrors(errs))
}
if len(bwe.WriteErrors) > 0 {
errs := make([]error, 0, len(bwe.WriteErrors))
for _, v := range bwe.WriteErrors {
errs = append(errs, v)
}
causes = append(causes, "write errors: "+joinBatchErrors(errs))
}
if bwe.PartialResult != nil {
causes = append(causes, fmt.Sprintf("result: %v", *bwe.PartialResult))
}
message := "bulk write exception: "
if len(causes) == 0 {
return message + "no causes"
}
return "bulk write exception: " + strings.Join(causes, ", ")
}
// returnResult is used to determine if a function calling processWriteError should return
// the result or return nil. Since the processWriteError function is used by many different
// methods, both *One and *Many, we need a way to differentiate if the method should return
// the result and the error.
type returnResult int
const (
rrNone returnResult = 1 << iota // None means do not return the result ever.
rrOne // One means return the result if this was called by a *One method.
rrMany // Many means return the result is this was called by a *Many method.
rrUnacknowledged
rrAll returnResult = rrOne | rrMany // All means always return the result.
rrAllUnacknowledged returnResult = rrAll | rrUnacknowledged // All + unacknowledged write
)
func (rr returnResult) isAcknowledged() bool {
return rr&rrUnacknowledged == 0
}
// processWriteError handles processing the result of a write operation. If the retrunResult matches
// the calling method's type, it should return the result object in addition to the error.
// This function will wrap the errors from other packages and return them as errors from this package.
//
// WriteConcernError will be returned over WriteErrors if both are present.
func processWriteError(err error) (returnResult, error) {
if err == nil {
return rrAll, nil
}
// Do not propagate the acknowledgement sentinel error. For DDL commands,
// (creating indexes, dropping collections, etc) acknowledgement should be
// ignored. For non-DDL write commands (insert, update, etc), acknowledgement
// should be be propagated at the result-level: e.g.,
// SingleResult.Acknowledged.
if errors.Is(err, driver.ErrUnacknowledgedWrite) {
return rrAllUnacknowledged, nil
}
var wce driver.WriteCommandError
if !errors.As(err, &wce) {
return rrNone, wrapErrors(err)
}
return rrMany, WriteException{
WriteConcernError: convertDriverWriteConcernError(wce.WriteConcernError),
WriteErrors: writeErrorsFromDriverWriteErrors(wce.WriteErrors),
Labels: wce.Labels,
Raw: bson.Raw(wce.Raw),
}
}
// batchErrorsTargetLength is the target length of error messages returned by batch operation
// error types. Try to limit batch error messages to 2kb to prevent problems when printing error
// messages from large batch operations.
const batchErrorsTargetLength = 2000
// joinBatchErrors appends messages from the given errors to a comma-separated string. If the
// string exceeds 2kb, it stops appending error messages and appends the message "+N more errors..."
// to the end.
//
// Example format:
//
// "[message 1, message 2, +8 more errors...]"
func joinBatchErrors(errs []error) string {
var buf bytes.Buffer
fmt.Fprint(&buf, "[")
for idx, err := range errs {
if idx != 0 {
fmt.Fprint(&buf, ", ")
}
// If the error message has exceeded the target error message length, stop appending errors
// to the message and append the number of remaining errors instead.
if buf.Len() > batchErrorsTargetLength {
fmt.Fprintf(&buf, "+%d more errors...", len(errs)-idx)
break
}
fmt.Fprint(&buf, err.Error())
}
fmt.Fprint(&buf, "]")
return buf.String()
}
// ErrorCodes returns the list of server error codes contained in err.
func ErrorCodes(err error) []int {
if err == nil {
return nil
}
var ec errorCoder
// First check if the error is already wrapped (common case)
if errors.As(err, &ec) {
return ec.ErrorCodes()
}
// Only wrap if necessary (for internal errors)
if errors.As(wrapErrors(err), &ec) {
return ec.ErrorCodes()
}
return []int{}
}

View File

@@ -0,0 +1,585 @@
// 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"
"io"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/csot"
"go.mongodb.org/mongo-driver/v2/internal/mongoutil"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/mongo/readconcern"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
)
// TODO: add sessions options
// DefaultGridFSChunkSize is the default size of each file chunk.
const DefaultGridFSChunkSize int32 = 255 * 1024 // 255 KiB
// ErrFileNotFound occurs if a user asks to download a file with a file ID that isn't found in the files collection.
var ErrFileNotFound = errors.New("file with given parameters not found")
// ErrMissingGridFSChunkSize occurs when downloading a file if the files
// collection document is missing the "chunkSize" field.
var ErrMissingGridFSChunkSize = errors.New("files collection document does not contain a 'chunkSize' field")
// GridFSBucket represents a GridFS bucket.
type GridFSBucket struct {
db *Database
chunksColl *Collection // collection to store file chunks
filesColl *Collection // collection to store file metadata
name string
chunkSize int32
wc *writeconcern.WriteConcern
rc *readconcern.ReadConcern
rp *readpref.ReadPref
firstWriteDone bool
readBuf []byte
writeBuf []byte
}
// upload contains options to upload a file to a bucket.
type upload struct {
chunkSize int32
metadata bson.D
}
// OpenUploadStream creates a file ID new upload stream for a file given the
// filename.
//
// The context provided to this method controls the entire lifetime of an
// upload stream io.Writer. If the context does set a deadline, then the
// client-level timeout will be used to cap the lifetime of the stream.
func (b *GridFSBucket) OpenUploadStream(
ctx context.Context,
filename string,
opts ...options.Lister[options.GridFSUploadOptions],
) (*GridFSUploadStream, error) {
return b.OpenUploadStreamWithID(ctx, bson.NewObjectID(), filename, opts...)
}
// OpenUploadStreamWithID creates a new upload stream for a file given the file
// ID and filename.
//
// The context provided to this method controls the entire lifetime of an
// upload stream io.Writer. If the context does set a deadline, then the
// client-level timeout will be used to cap the lifetime of the stream.
func (b *GridFSBucket) OpenUploadStreamWithID(
ctx context.Context,
fileID any,
filename string,
opts ...options.Lister[options.GridFSUploadOptions],
) (*GridFSUploadStream, error) {
ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
if err := b.checkFirstWrite(ctx); err != nil {
return nil, err
}
upload, err := b.parseGridFSUploadOptions(opts...)
if err != nil {
return nil, err
}
return newUploadStream(ctx, cancel, upload, fileID, filename, b.chunksColl, b.filesColl), nil
}
// UploadFromStream creates a fileID and uploads a file given a source stream.
//
// If this upload requires a custom write deadline to be set on the bucket, it
// cannot be done concurrently with other write operations operations on this
// bucket that also require a custom deadline.
//
// The context provided to this method controls the entire lifetime of an
// upload stream io.Writer. If the context does set a deadline, then the
// client-level timeout will be used to cap the lifetime of the stream.
func (b *GridFSBucket) UploadFromStream(
ctx context.Context,
filename string,
source io.Reader,
opts ...options.Lister[options.GridFSUploadOptions],
) (bson.ObjectID, error) {
fileID := bson.NewObjectID()
err := b.UploadFromStreamWithID(ctx, fileID, filename, source, opts...)
return fileID, err
}
// UploadFromStreamWithID uploads a file given a source stream.
//
// If this upload requires a custom write deadline to be set on the bucket, it
// cannot be done concurrently with other write operations operations on this
// bucket that also require a custom deadline.
//
// The context provided to this method controls the entire lifetime of an
// upload stream io.Writer. If the context does set a deadline, then the
// client-level timeout will be used to cap the lifetime of the stream.
func (b *GridFSBucket) UploadFromStreamWithID(
ctx context.Context,
fileID any,
filename string,
source io.Reader,
opts ...options.Lister[options.GridFSUploadOptions],
) error {
ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
defer cancel()
us, err := b.OpenUploadStreamWithID(ctx, fileID, filename, opts...)
if err != nil {
return err
}
for {
n, err := source.Read(b.readBuf)
if err != nil && err != io.EOF {
_ = us.Abort() // upload considered aborted if source stream returns an error
return err
}
if n > 0 {
_, err := us.Write(b.readBuf[:n])
if err != nil {
return err
}
}
if n == 0 || err == io.EOF {
break
}
}
return us.Close()
}
// OpenDownloadStream creates a stream from which the contents of the file can
// be read.
//
// The context provided to this method controls the entire lifetime of an
// upload stream io.Writer. If the context does set a deadline, then the
// client-level timeout will be used to cap the lifetime of the stream.
func (b *GridFSBucket) OpenDownloadStream(ctx context.Context, fileID any) (*GridFSDownloadStream, error) {
return b.openDownloadStream(ctx, bson.D{{"_id", fileID}})
}
// DownloadToStream downloads the file with the specified fileID and writes it
// to the provided io.Writer. Returns the number of bytes written to the stream
// and an error, or nil if there was no error.
//
// If this download requires a custom read deadline to be set on the bucket, it
// cannot be done concurrently with other read operations operations on this
// bucket that also require a custom deadline.
//
// The context provided to this method controls the entire lifetime of an
// upload stream io.Writer. If the context does set a deadline, then the
// client-level timeout will be used to cap the lifetime of the stream.
func (b *GridFSBucket) DownloadToStream(ctx context.Context, fileID any, stream io.Writer) (int64, error) {
ds, err := b.OpenDownloadStream(ctx, fileID)
if err != nil {
return 0, err
}
return b.downloadToStream(ds, stream)
}
// OpenDownloadStreamByName opens a download stream for the file with the given
// filename.
//
// The context provided to this method controls the entire lifetime of an
// upload stream io.Writer. If the context does set a deadline, then the
// client-level timeout will be used to cap the lifetime of the stream.
func (b *GridFSBucket) OpenDownloadStreamByName(
ctx context.Context,
filename string,
opts ...options.Lister[options.GridFSNameOptions],
) (*GridFSDownloadStream, error) {
args, err := mongoutil.NewOptions[options.GridFSNameOptions](opts...)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
numSkip := options.DefaultRevision
if args.Revision != nil {
numSkip = *args.Revision
}
var sortOrder int32 = 1
if numSkip < 0 {
sortOrder = -1
numSkip = (-1 * numSkip) - 1
}
findOpts := options.FindOne().SetSkip(int64(numSkip)).SetSort(bson.D{{"uploadDate", sortOrder}})
return b.openDownloadStream(ctx, bson.D{{"filename", filename}}, findOpts)
}
// DownloadToStreamByName downloads the file with the given name to the given
// io.Writer.
//
// If this download requires a custom read deadline to be set on the bucket, it
// cannot be done concurrently with other read operations operations on this
// bucket that also require a custom deadline.
//
// The context provided to this method controls the entire lifetime of an
// upload stream io.Writer. If the context does set a deadline, then the
// client-level timeout will be used to cap the lifetime of the stream.
func (b *GridFSBucket) DownloadToStreamByName(
ctx context.Context,
filename string,
stream io.Writer,
opts ...options.Lister[options.GridFSNameOptions],
) (int64, error) {
ds, err := b.OpenDownloadStreamByName(ctx, filename, opts...)
if err != nil {
return 0, err
}
return b.downloadToStream(ds, stream)
}
// Delete deletes all chunks and metadata associated with the file with the
// given file ID and runs the underlying delete operations with the provided
// context.
func (b *GridFSBucket) Delete(ctx context.Context, fileID any) error {
ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
defer cancel()
res, err := b.filesColl.DeleteOne(ctx, bson.D{{"_id", fileID}})
if err == nil && res.DeletedCount == 0 {
err = ErrFileNotFound
}
if err != nil {
_ = b.deleteChunks(ctx, fileID) // Can attempt to delete chunks even if no docs in files collection matched.
return err
}
return b.deleteChunks(ctx, fileID)
}
// Find returns the files collection documents that match the given filter and
// runs the underlying find query with the provided context.
func (b *GridFSBucket) Find(
ctx context.Context,
filter any,
opts ...options.Lister[options.GridFSFindOptions],
) (*Cursor, error) {
args, err := mongoutil.NewOptions[options.GridFSFindOptions](opts...)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
find := options.Find()
if args.AllowDiskUse != nil {
find.SetAllowDiskUse(*args.AllowDiskUse)
}
if args.BatchSize != nil {
find.SetBatchSize(*args.BatchSize)
}
if args.Limit != nil {
find.SetLimit(int64(*args.Limit))
}
if args.NoCursorTimeout != nil {
find.SetNoCursorTimeout(*args.NoCursorTimeout)
}
if args.Skip != nil {
find.SetSkip(int64(*args.Skip))
}
if args.Sort != nil {
find.SetSort(args.Sort)
}
return b.filesColl.Find(ctx, filter, find)
}
// Rename renames the stored file with the specified file ID.
func (b *GridFSBucket) Rename(ctx context.Context, fileID any, newFilename string) error {
res, err := b.filesColl.UpdateOne(ctx,
bson.D{{"_id", fileID}},
bson.D{{"$set", bson.D{{"filename", newFilename}}}},
)
if err != nil {
return err
}
if res.MatchedCount == 0 {
return ErrFileNotFound
}
return nil
}
// Drop drops the files and chunks collections associated with this bucket and
// runs the drop operations with the provided context.
func (b *GridFSBucket) Drop(ctx context.Context) error {
ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
defer cancel()
err := b.filesColl.Drop(ctx)
if err != nil {
return err
}
return b.chunksColl.Drop(ctx)
}
// GetFilesCollection returns a handle to the collection that stores the file documents for this bucket.
func (b *GridFSBucket) GetFilesCollection() *Collection {
return b.filesColl
}
// GetChunksCollection returns a handle to the collection that stores the file chunks for this bucket.
func (b *GridFSBucket) GetChunksCollection() *Collection {
return b.chunksColl
}
func (b *GridFSBucket) openDownloadStream(
ctx context.Context,
filter any,
opts ...options.Lister[options.FindOneOptions],
) (*GridFSDownloadStream, error) {
ctx, cancel := csot.WithTimeout(ctx, b.db.client.timeout)
result := b.filesColl.FindOne(ctx, filter, opts...)
// Unmarshal the data into a File instance, which can be passed to newGridFSDownloadStream. The _id value has to be
// parsed out separately because "_id" will not match the File.ID field and we want to avoid exposing BSON tags
// in the File type. After parsing it, use RawValue.Unmarshal to ensure File.ID is set to the appropriate value.
var resp findFileResponse
if err := result.Decode(&resp); err != nil {
if errors.Is(err, ErrNoDocuments) {
return nil, ErrFileNotFound
}
return nil, fmt.Errorf("error decoding files collection document: %w", err)
}
foundFile := newFileFromResponse(resp)
if foundFile.Length == 0 {
return newGridFSDownloadStream(ctx, cancel, nil, foundFile.ChunkSize, foundFile), nil
}
// For a file with non-zero length, chunkSize must exist so we know what size to expect when downloading chunks.
if foundFile.ChunkSize == 0 {
return nil, ErrMissingGridFSChunkSize
}
chunksCursor, err := b.findChunks(ctx, foundFile.ID)
if err != nil {
return nil, err
}
// The chunk size can be overridden for individual files, so the expected chunk size should be the "chunkSize"
// field from the files collection document, not the bucket's chunk size.
return newGridFSDownloadStream(ctx, cancel, chunksCursor, foundFile.ChunkSize, foundFile), nil
}
func (b *GridFSBucket) downloadToStream(ds *GridFSDownloadStream, stream io.Writer) (int64, error) {
copied, err := io.Copy(stream, ds)
if err != nil {
_ = ds.Close()
return 0, err
}
return copied, ds.Close()
}
func (b *GridFSBucket) deleteChunks(ctx context.Context, fileID any) error {
_, err := b.chunksColl.DeleteMany(ctx, bson.D{{"files_id", fileID}})
return err
}
func (b *GridFSBucket) findChunks(ctx context.Context, fileID any) (*Cursor, error) {
chunksCursor, err := b.chunksColl.Find(ctx,
bson.D{{"files_id", fileID}},
options.Find().SetSort(bson.D{{"n", 1}})) // sort by chunk index
if err != nil {
return nil, err
}
return chunksCursor, nil
}
// returns true if the 2 index documents are equal
func numericalIndexDocsEqual(expected, actual bsoncore.Document) (bool, error) {
if bytes.Equal(expected, actual) {
return true, nil
}
actualElems, err := actual.Elements()
if err != nil {
return false, err
}
expectedElems, err := expected.Elements()
if err != nil {
return false, err
}
if len(actualElems) != len(expectedElems) {
return false, nil
}
for idx, expectedElem := range expectedElems {
actualElem := actualElems[idx]
if actualElem.Key() != expectedElem.Key() {
return false, nil
}
actualVal := actualElem.Value()
expectedVal := expectedElem.Value()
actualInt, actualOK := actualVal.AsInt64OK()
expectedInt, expectedOK := expectedVal.AsInt64OK()
// GridFS indexes always have numeric values
if !actualOK || !expectedOK {
return false, nil
}
if actualInt != expectedInt {
return false, nil
}
}
return true, nil
}
// Create an index if it doesn't already exist
func createNumericalIndexIfNotExists(ctx context.Context, iv IndexView, model IndexModel) error {
c, err := iv.List(ctx)
if err != nil {
return err
}
defer func() {
_ = c.Close(ctx)
}()
modelKeysBytes, err := bson.Marshal(model.Keys)
if err != nil {
return err
}
modelKeysDoc := bsoncore.Document(modelKeysBytes)
for c.Next(ctx) {
keyElem, err := c.Current.LookupErr("key")
if err != nil {
return err
}
keyElemDoc := keyElem.Document()
found, err := numericalIndexDocsEqual(modelKeysDoc, bsoncore.Document(keyElemDoc))
if err != nil {
return err
}
if found {
return nil
}
}
_, err = iv.CreateOne(ctx, model)
return err
}
// create indexes on the files and chunks collection if needed
func (b *GridFSBucket) createIndexes(ctx context.Context) error {
// must use primary read pref mode to check if files coll empty
cloned := b.filesColl.Clone(options.Collection().SetReadPreference(readpref.Primary()))
docRes := cloned.FindOne(ctx, bson.D{}, options.FindOne().SetProjection(bson.D{{"_id", 1}}))
_, err := docRes.Raw()
if !errors.Is(err, ErrNoDocuments) {
// nil, or error that occurred during the FindOne operation
return err
}
filesIv := b.filesColl.Indexes()
chunksIv := b.chunksColl.Indexes()
filesModel := IndexModel{
Keys: bson.D{
{"filename", int32(1)},
{"uploadDate", int32(1)},
},
}
chunksModel := IndexModel{
Keys: bson.D{
{"files_id", int32(1)},
{"n", int32(1)},
},
Options: options.Index().SetUnique(true),
}
if err = createNumericalIndexIfNotExists(ctx, filesIv, filesModel); err != nil {
return err
}
return createNumericalIndexIfNotExists(ctx, chunksIv, chunksModel)
}
func (b *GridFSBucket) checkFirstWrite(ctx context.Context) error {
if !b.firstWriteDone {
// before the first write operation, must determine if files collection is empty
// if so, create indexes if they do not already exist
if err := b.createIndexes(ctx); err != nil {
return err
}
b.firstWriteDone = true
}
return nil
}
func (b *GridFSBucket) parseGridFSUploadOptions(opts ...options.Lister[options.GridFSUploadOptions]) (*upload, error) {
upload := &upload{
chunkSize: b.chunkSize, // upload chunk size defaults to bucket's value
}
args, err := mongoutil.NewOptions[options.GridFSUploadOptions](opts...)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
if args.ChunkSizeBytes != nil {
upload.chunkSize = *args.ChunkSizeBytes
}
if args.Registry == nil {
args.Registry = defaultRegistry
}
if args.Metadata != nil {
// TODO(GODRIVER-2726): Replace with marshal() and unmarshal() once the
// TODO gridfs package is merged into the mongo package.
buf := new(bytes.Buffer)
vw := bson.NewDocumentWriter(buf)
enc := bson.NewEncoder(vw)
enc.SetRegistry(args.Registry)
err := enc.Encode(args.Metadata)
if err != nil {
return nil, err
}
var doc bson.D
dec := bson.NewDecoder(bson.NewDocumentReader(bytes.NewReader(buf.Bytes())))
dec.SetRegistry(args.Registry)
unMarErr := dec.Decode(&doc)
if unMarErr != nil {
return nil, unMarErr
}
upload.metadata = doc
}
return upload, nil
}

View File

@@ -0,0 +1,299 @@
// 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"
"io"
"math"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
)
// ErrMissingChunk indicates that the number of chunks read from the server is
// less than expected. This error is specific to GridFS operations.
var ErrMissingChunk = errors.New("EOF missing one or more chunks")
// ErrWrongSize is used when the chunk retrieved from the server does not have
// the expected size. This error is specific to GridFS operations.
var ErrWrongSize = errors.New("chunk size does not match expected size")
var errNoMoreChunks = errors.New("no more chunks remaining")
// GridFSDownloadStream is a io.Reader that can be used to download a file from a GridFS bucket.
type GridFSDownloadStream struct {
numChunks int32
chunkSize int32
cursor *Cursor
done bool
closed bool
buffer []byte // store up to 1 chunk if the user provided buffer isn't big enough
bufferStart int
bufferEnd int
expectedChunk int32 // index of next expected chunk
fileLen int64
ctx context.Context
cancel context.CancelFunc
// The pointer returned by GetFile. This should not be used in the actual GridFSDownloadStream code outside of the
// newGridFSDownloadStream constructor because the values can be mutated by the user after calling GetFile. Instead,
// any values needed in the code should be stored separately and copied over in the constructor.
file *GridFSFile
}
// GridFSFile represents a file stored in GridFS. This type can be used to
// access file information when downloading using the
// GridFSDownloadStream.GetFile method.
type GridFSFile struct {
// ID is the file's ID. This will match the file ID specified when uploading the file. If an upload helper that
// does not require a file ID was used, this field will be a bson.ObjectID.
ID any
// Length is the length of this file in bytes.
Length int64
// ChunkSize is the maximum number of bytes for each chunk in this file.
ChunkSize int32
// UploadDate is the time this file was added to GridFS in UTC. This field is set by the driver and is not configurable.
// The Metadata field can be used to store a custom date.
UploadDate time.Time
// Name is the name of this file.
Name string
// Metadata is additional data that was specified when creating this file. This field can be unmarshalled into a
// custom type using the bson.Unmarshal family of functions.
Metadata bson.Raw
}
var _ bson.Unmarshaler = &GridFSFile{}
// findFileResponse is a temporary type used to unmarshal documents from the
// files collection and can be transformed into a File instance. This type
// exists to avoid adding BSON struct tags to the exported File type.
type findFileResponse struct {
ID any `bson:"_id"`
Length int64 `bson:"length"`
ChunkSize int32 `bson:"chunkSize"`
UploadDate time.Time `bson:"uploadDate"`
Name string `bson:"filename"`
Metadata bson.Raw `bson:"metadata"`
}
func newFileFromResponse(resp findFileResponse) *GridFSFile {
return &GridFSFile{
ID: resp.ID,
Length: resp.Length,
ChunkSize: resp.ChunkSize,
UploadDate: resp.UploadDate,
Name: resp.Name,
Metadata: resp.Metadata,
}
}
// UnmarshalBSON implements the bson.Unmarshaler interface.
func (f *GridFSFile) UnmarshalBSON(data []byte) error {
var temp findFileResponse
if err := bson.Unmarshal(data, &temp); err != nil {
return err
}
f.ID = temp.ID
f.Length = temp.Length
f.ChunkSize = temp.ChunkSize
f.UploadDate = temp.UploadDate
f.Name = temp.Name
f.Metadata = temp.Metadata
return nil
}
func newGridFSDownloadStream(
ctx context.Context,
cancel context.CancelFunc,
cursor *Cursor,
chunkSize int32,
file *GridFSFile,
) *GridFSDownloadStream {
numChunks := int32(math.Ceil(float64(file.Length) / float64(chunkSize)))
return &GridFSDownloadStream{
numChunks: numChunks,
chunkSize: chunkSize,
cursor: cursor,
buffer: make([]byte, chunkSize),
done: cursor == nil,
fileLen: file.Length,
file: file,
ctx: ctx,
cancel: cancel,
}
}
// Close closes this download stream.
func (ds *GridFSDownloadStream) Close() error {
defer func() {
if ds.cancel != nil {
ds.cancel()
}
}()
if ds.closed {
return ErrStreamClosed
}
ds.closed = true
if ds.cursor != nil {
return ds.cursor.Close(context.Background())
}
return nil
}
// Read reads the file from the server and writes it to a destination byte slice.
func (ds *GridFSDownloadStream) Read(p []byte) (int, error) {
if ds.closed {
return 0, ErrStreamClosed
}
if ds.done {
return 0, io.EOF
}
bytesCopied := 0
var err error
for bytesCopied < len(p) {
if ds.bufferStart >= ds.bufferEnd {
// Buffer is empty and can load in data from new chunk.
err = ds.fillBuffer(ds.ctx)
if err != nil {
if errors.Is(err, errNoMoreChunks) {
if bytesCopied == 0 {
ds.done = true
return 0, io.EOF
}
return bytesCopied, nil
}
return bytesCopied, err
}
}
copied := copy(p[bytesCopied:], ds.buffer[ds.bufferStart:ds.bufferEnd])
bytesCopied += copied
ds.bufferStart += copied
}
return len(p), nil
}
// Skip skips a given number of bytes in the file.
func (ds *GridFSDownloadStream) Skip(skip int64) (int64, error) {
if ds.closed {
return 0, ErrStreamClosed
}
if ds.done {
return 0, nil
}
var skipped int64
var err error
for skipped < skip {
if ds.bufferStart >= ds.bufferEnd {
// Buffer is empty and can load in data from new chunk.
err = ds.fillBuffer(ds.ctx)
if err != nil {
if errors.Is(err, errNoMoreChunks) {
return skipped, nil
}
return skipped, err
}
}
toSkip := skip - skipped
// Cap the amount to skip to the remaining bytes in the buffer to be consumed.
bufferRemaining := ds.bufferEnd - ds.bufferStart
if toSkip > int64(bufferRemaining) {
toSkip = int64(bufferRemaining)
}
skipped += toSkip
ds.bufferStart += int(toSkip)
}
return skip, nil
}
// GetFile returns a File object representing the file being downloaded.
func (ds *GridFSDownloadStream) GetFile() *GridFSFile {
return ds.file
}
func (ds *GridFSDownloadStream) fillBuffer(ctx context.Context) error {
if !ds.cursor.Next(ctx) {
ds.done = true
// Check for cursor error, otherwise there are no more chunks.
if ds.cursor.Err() != nil {
_ = ds.cursor.Close(ctx)
return ds.cursor.Err()
}
// If there are no more chunks, but we didn't read the expected number of chunks, return an
// ErrMissingChunk error to indicate that we're missing chunks at the end of the file.
if ds.expectedChunk != ds.numChunks {
return ErrMissingChunk
}
return errNoMoreChunks
}
chunkIndex, err := ds.cursor.Current.LookupErr("n")
if err != nil {
return err
}
var chunkIndexInt32 int32
if chunkIndexInt64, ok := chunkIndex.Int64OK(); ok {
chunkIndexInt32 = int32(chunkIndexInt64)
} else {
chunkIndexInt32 = chunkIndex.Int32()
}
if chunkIndexInt32 != ds.expectedChunk {
return ErrMissingChunk
}
ds.expectedChunk++
data, err := ds.cursor.Current.LookupErr("data")
if err != nil {
return err
}
_, dataBytes := data.Binary()
copied := copy(ds.buffer, dataBytes)
bytesLen := int32(len(dataBytes))
if ds.expectedChunk == ds.numChunks {
// final chunk can be fewer than ds.chunkSize bytes
bytesDownloaded := int64(ds.chunkSize) * (int64(ds.expectedChunk) - int64(1))
bytesRemaining := ds.fileLen - bytesDownloaded
if int64(bytesLen) != bytesRemaining {
return ErrWrongSize
}
} else if bytesLen != ds.chunkSize {
// all intermediate chunks must have size ds.chunkSize
return ErrWrongSize
}
ds.bufferStart = 0
ds.bufferEnd = copied
return nil
}

View File

@@ -0,0 +1,204 @@
// 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"
"math"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
)
// uploadBufferSize is the size in bytes of one stream batch. Chunks will be written to the db after the sum of chunk
// lengths is equal to the batch size.
const uploadBufferSize = 16 * 1024 * 1024 // 16 MiB
// ErrStreamClosed is an error returned if an operation is attempted on a closed/aborted stream.
var ErrStreamClosed = errors.New("stream is closed or aborted")
// GridFSUploadStream is used to upload a file in chunks. This type implements the io.Writer interface and a file can be
// uploaded using the Write method. After an upload is complete, the Close method must be called to write file
// metadata.
type GridFSUploadStream struct {
*upload // chunk size and metadata
FileID any
chunkIndex int
chunksColl *Collection // collection to store file chunks
filename string
filesColl *Collection // collection to store file metadata
closed bool
buffer []byte
bufferIndex int
fileLen int64
ctx context.Context
cancel context.CancelFunc
}
// NewUploadStream creates a new upload stream.
func newUploadStream(
ctx context.Context,
cancel context.CancelFunc,
up *upload,
fileID any,
filename string,
chunks, files *Collection,
) *GridFSUploadStream {
return &GridFSUploadStream{
upload: up,
FileID: fileID,
chunksColl: chunks,
filename: filename,
filesColl: files,
buffer: make([]byte, uploadBufferSize),
ctx: ctx,
cancel: cancel,
}
}
// Close writes file metadata to the files collection and cleans up any resources associated with the UploadStream.
func (us *GridFSUploadStream) Close() error {
defer func() {
if us.cancel != nil {
us.cancel()
}
}()
if us.closed {
return ErrStreamClosed
}
if us.bufferIndex != 0 {
if err := us.uploadChunks(us.ctx, true); err != nil {
return err
}
}
if err := us.createFilesCollDoc(us.ctx); err != nil {
return err
}
us.closed = true
return nil
}
// Write transfers the contents of a byte slice into this upload stream. If the stream's underlying buffer fills up,
// the buffer will be uploaded as chunks to the server. Implements the io.Writer interface.
func (us *GridFSUploadStream) Write(p []byte) (int, error) {
if us.closed {
return 0, ErrStreamClosed
}
origLen := len(p)
for len(p) != 0 {
n := copy(us.buffer[us.bufferIndex:], p) // copy as much as possible
p = p[n:]
us.bufferIndex += n
if us.bufferIndex == uploadBufferSize {
err := us.uploadChunks(us.ctx, false)
if err != nil {
return 0, err
}
}
}
return origLen, nil
}
// Abort closes the stream and deletes all file chunks that have already been written.
func (us *GridFSUploadStream) Abort() error {
defer func() {
if us.cancel != nil {
us.cancel()
}
}()
if us.closed {
return ErrStreamClosed
}
_, err := us.chunksColl.DeleteMany(us.ctx, bson.D{{"files_id", us.FileID}})
if err != nil {
return err
}
us.closed = true
return nil
}
// uploadChunks uploads the current buffer as a series of chunks to the bucket
// if uploadPartial is true, any data at the end of the buffer that is smaller than a chunk will be uploaded as a partial
// chunk. if it is false, the data will be moved to the front of the buffer.
// uploadChunks sets us.bufferIndex to the next available index in the buffer after uploading
func (us *GridFSUploadStream) uploadChunks(ctx context.Context, uploadPartial bool) error {
chunks := float64(us.bufferIndex) / float64(us.chunkSize)
numChunks := int(math.Ceil(chunks))
if !uploadPartial {
numChunks = int(math.Floor(chunks))
}
docs := make([]any, numChunks)
begChunkIndex := us.chunkIndex
for i := 0; i < us.bufferIndex; i += int(us.chunkSize) {
endIndex := i + int(us.chunkSize)
if us.bufferIndex-i < int(us.chunkSize) {
// partial chunk
if !uploadPartial {
break
}
endIndex = us.bufferIndex
}
chunkData := us.buffer[i:endIndex]
docs[us.chunkIndex-begChunkIndex] = bson.D{
{"_id", bson.NewObjectID()},
{"files_id", us.FileID},
{"n", int32(us.chunkIndex)},
{"data", bson.Binary{Subtype: 0x00, Data: chunkData}},
}
us.chunkIndex++
us.fileLen += int64(len(chunkData))
}
_, err := us.chunksColl.InsertMany(ctx, docs)
if err != nil {
return err
}
// copy any remaining bytes to beginning of buffer and set buffer index
bytesUploaded := numChunks * int(us.chunkSize)
if bytesUploaded != uploadBufferSize && !uploadPartial {
copy(us.buffer[0:], us.buffer[bytesUploaded:us.bufferIndex])
}
us.bufferIndex = uploadBufferSize - bytesUploaded
return nil
}
func (us *GridFSUploadStream) createFilesCollDoc(ctx context.Context) error {
doc := bson.D{
{"_id", us.FileID},
{"length", us.fileLen},
{"chunkSize", us.chunkSize},
{"uploadDate", bson.DateTime(time.Now().UnixNano() / int64(time.Millisecond))},
{"filename", us.filename},
}
if us.metadata != nil {
doc = append(doc, bson.E{"metadata", us.metadata})
}
_, err := us.filesColl.InsertOne(ctx, doc)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,548 @@
// 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"
"strconv"
"go.mongodb.org/mongo-driver/v2/internal/mongoutil"
"go.mongodb.org/mongo-driver/v2/internal/optionsutil"
"go.mongodb.org/mongo-driver/v2/internal/serverselector"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
)
// ErrInvalidIndexValue is returned if an index is created with a keys document that has a value that is not a number
// or string.
var ErrInvalidIndexValue = errors.New("invalid index value")
// ErrNonStringIndexName is returned if an index is created with a name that is not a string.
//
// Deprecated: it will be removed in the next major release
var ErrNonStringIndexName = errors.New("index name must be a string")
// IndexView is a type that can be used to create, drop, and list indexes on a collection. An IndexView for a collection
// can be created by a call to Collection.Indexes().
type IndexView struct {
coll *Collection
}
// IndexModel represents a new index to be created.
type IndexModel struct {
// A document describing which keys should be used for the index. It cannot be nil. This must be an order-preserving
// type such as bson.D. Map types such as bson.M are not valid. See https://www.mongodb.com/docs/manual/indexes/#indexes
// for examples of valid documents.
Keys any
// The options to use to create the index.
Options *options.IndexOptionsBuilder
}
// List executes a listIndexes command and returns a cursor over the indexes in the collection.
//
// The opts parameter can be used to specify options for this operation (see the options.ListIndexesOptions
// documentation).
//
// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/listIndexes/.
func (iv IndexView) List(ctx context.Context, opts ...options.Lister[options.ListIndexesOptions]) (*Cursor, error) {
if ctx == nil {
ctx = context.Background()
}
sess := sessionFromContext(ctx)
if sess == nil && iv.coll.client.sessionPool != nil {
sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
}
err := iv.coll.client.validSession(sess)
if err != nil {
closeImplicitSession(sess)
return nil, err
}
var selector description.ServerSelector
selector = &serverselector.Composite{
Selectors: []description.ServerSelector{
&serverselector.ReadPref{ReadPref: readpref.Primary()},
&serverselector.Latency{Latency: iv.coll.client.localThreshold},
},
}
selector = makeReadPrefSelector(sess, selector, iv.coll.client.localThreshold)
op := operation.NewListIndexes().
Session(sess).CommandMonitor(iv.coll.client.monitor).
ServerSelector(selector).ClusterClock(iv.coll.client.clock).
Database(iv.coll.db.name).Collection(iv.coll.name).
Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
Timeout(iv.coll.client.timeout).Crypt(iv.coll.client.cryptFLE).Authenticator(iv.coll.client.authenticator)
cursorOpts := iv.coll.client.createBaseCursorOptions()
cursorOpts.MarshalValueEncoderFn = newEncoderFn(iv.coll.bsonOpts, iv.coll.registry)
args, err := mongoutil.NewOptions[options.ListIndexesOptions](opts...)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
if args.BatchSize != nil {
op = op.BatchSize(*args.BatchSize)
cursorOpts.BatchSize = *args.BatchSize
}
if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
op = op.RawData(rawData)
}
retry := driver.RetryNone
if iv.coll.client.retryReads {
retry = driver.RetryOncePerCommand
}
op.Retry(retry)
err = op.Execute(ctx)
if err != nil {
// for namespaceNotFound errors, return an empty cursor and do not throw an error
closeImplicitSession(sess)
var de driver.Error
if errors.As(err, &de) && de.NamespaceNotFound() {
return newEmptyCursor(), nil
}
return nil, wrapErrors(err)
}
bc, err := op.Result(cursorOpts)
if err != nil {
closeImplicitSession(sess)
return nil, wrapErrors(err)
}
cursor, err := newCursorWithSession(bc, iv.coll.bsonOpts, iv.coll.registry, sess,
// This value is included for completeness, but a server will never return
// a tailable awaitData cursor from a listIndexes operation.
withCursorOptionClientTimeout(iv.coll.client.timeout))
return cursor, wrapErrors(err)
}
// ListSpecifications executes a List command and returns a slice of returned IndexSpecifications
func (iv IndexView) ListSpecifications(
ctx context.Context,
opts ...options.Lister[options.ListIndexesOptions],
) ([]IndexSpecification, error) {
cursor, err := iv.List(ctx, opts...)
if err != nil {
return nil, err
}
var resp []indexListSpecificationResponse
if err := cursor.All(ctx, &resp); err != nil {
return nil, err
}
namespace := iv.coll.db.Name() + "." + iv.coll.Name()
specs := make([]IndexSpecification, len(resp))
for idx, spec := range resp {
specs[idx] = IndexSpecification(spec)
specs[idx].Namespace = namespace
}
return specs, nil
}
// CreateOne executes a createIndexes command to create an index on the collection and returns the name of the new
// index. See the IndexView.CreateMany documentation for more information and an example.
func (iv IndexView) CreateOne(
ctx context.Context,
model IndexModel,
opts ...options.Lister[options.CreateIndexesOptions],
) (string, error) {
names, err := iv.CreateMany(ctx, []IndexModel{model}, opts...)
if err != nil {
return "", err
}
return names[0], nil
}
// CreateMany executes a createIndexes command to create multiple indexes on the collection and returns the names of
// the new indexes.
//
// For each IndexModel in the models parameter, the index name can be specified via the Options field. If a name is not
// given, it will be generated from the Keys document.
//
// The opts parameter can be used to specify options for this operation (see the options.CreateIndexesOptions
// documentation).
//
// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/createIndexes/.
func (iv IndexView) CreateMany(
ctx context.Context,
models []IndexModel,
opts ...options.Lister[options.CreateIndexesOptions],
) ([]string, error) {
names := make([]string, 0, len(models))
var indexes bsoncore.Document
aidx, indexes := bsoncore.AppendArrayStart(indexes)
for i, model := range models {
if model.Keys == nil {
return nil, fmt.Errorf("index model keys cannot be nil")
}
if isUnorderedMap(model.Keys) {
return nil, ErrMapForOrderedArgument{"keys"}
}
keys, err := marshal(model.Keys, iv.coll.bsonOpts, iv.coll.registry)
if err != nil {
return nil, err
}
name, err := getOrGenerateIndexName(keys, model)
if err != nil {
return nil, err
}
names = append(names, name)
var iidx int32
iidx, indexes = bsoncore.AppendDocumentElementStart(indexes, strconv.Itoa(i))
indexes = bsoncore.AppendDocumentElement(indexes, "key", keys)
if model.Options == nil {
model.Options = options.Index()
}
model.Options.SetName(name)
optsDoc, err := iv.createOptionsDoc(model.Options)
if err != nil {
return nil, err
}
indexes = bsoncore.AppendDocument(indexes, optsDoc)
indexes, err = bsoncore.AppendDocumentEnd(indexes, iidx)
if err != nil {
return nil, err
}
}
indexes, err := bsoncore.AppendArrayEnd(indexes, aidx)
if err != nil {
return nil, err
}
sess := sessionFromContext(ctx)
if sess == nil && iv.coll.client.sessionPool != nil {
sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
defer sess.EndSession()
}
err = iv.coll.client.validSession(sess)
if err != nil {
return nil, err
}
wc := iv.coll.writeConcern
if sess.TransactionRunning() {
wc = nil
}
if !wc.Acknowledged() {
sess = nil
}
selector := makePinnedSelector(sess, iv.coll.writeSelector)
args, err := mongoutil.NewOptions[options.CreateIndexesOptions](opts...)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
op := operation.NewCreateIndexes(indexes).
Session(sess).WriteConcern(wc).ClusterClock(iv.coll.client.clock).
Database(iv.coll.db.name).Collection(iv.coll.name).CommandMonitor(iv.coll.client.monitor).
Deployment(iv.coll.client.deployment).ServerSelector(selector).ServerAPI(iv.coll.client.serverAPI).
Timeout(iv.coll.client.timeout).Crypt(iv.coll.client.cryptFLE).Authenticator(iv.coll.client.authenticator)
if args.CommitQuorum != nil {
commitQuorum, err := marshalValue(args.CommitQuorum, iv.coll.bsonOpts, iv.coll.registry)
if err != nil {
return nil, err
}
op.CommitQuorum(commitQuorum)
}
if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
op = op.RawData(rawData)
}
_, err = processWriteError(op.Execute(ctx))
if err != nil {
return nil, err
}
return names, nil
}
func (iv IndexView) createOptionsDoc(opts options.Lister[options.IndexOptions]) (bsoncore.Document, error) {
args, err := mongoutil.NewOptions[options.IndexOptions](opts)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
optsDoc := bsoncore.Document{}
if args.ExpireAfterSeconds != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "expireAfterSeconds", *args.ExpireAfterSeconds)
}
if args.Name != nil {
optsDoc = bsoncore.AppendStringElement(optsDoc, "name", *args.Name)
}
if args.Sparse != nil {
optsDoc = bsoncore.AppendBooleanElement(optsDoc, "sparse", *args.Sparse)
}
if args.StorageEngine != nil {
doc, err := marshal(args.StorageEngine, iv.coll.bsonOpts, iv.coll.registry)
if err != nil {
return nil, err
}
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "storageEngine", doc)
}
if args.Unique != nil {
optsDoc = bsoncore.AppendBooleanElement(optsDoc, "unique", *args.Unique)
}
if args.Version != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "v", *args.Version)
}
if args.DefaultLanguage != nil {
optsDoc = bsoncore.AppendStringElement(optsDoc, "default_language", *args.DefaultLanguage)
}
if args.LanguageOverride != nil {
optsDoc = bsoncore.AppendStringElement(optsDoc, "language_override", *args.LanguageOverride)
}
if args.TextVersion != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "textIndexVersion", *args.TextVersion)
}
if args.Weights != nil {
doc, err := marshal(args.Weights, iv.coll.bsonOpts, iv.coll.registry)
if err != nil {
return nil, err
}
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "weights", doc)
}
if args.SphereVersion != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "2dsphereIndexVersion", *args.SphereVersion)
}
if args.Bits != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "bits", *args.Bits)
}
if args.Max != nil {
optsDoc = bsoncore.AppendDoubleElement(optsDoc, "max", *args.Max)
}
if args.Min != nil {
optsDoc = bsoncore.AppendDoubleElement(optsDoc, "min", *args.Min)
}
if args.BucketSize != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "bucketSize", *args.BucketSize)
}
if args.PartialFilterExpression != nil {
doc, err := marshal(args.PartialFilterExpression, iv.coll.bsonOpts, iv.coll.registry)
if err != nil {
return nil, err
}
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "partialFilterExpression", doc)
}
if args.Collation != nil {
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "collation", bsoncore.Document(toDocument(args.Collation)))
}
if args.WildcardProjection != nil {
doc, err := marshal(args.WildcardProjection, iv.coll.bsonOpts, iv.coll.registry)
if err != nil {
return nil, err
}
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "wildcardProjection", doc)
}
if args.Hidden != nil {
optsDoc = bsoncore.AppendBooleanElement(optsDoc, "hidden", *args.Hidden)
}
return optsDoc, nil
}
func (iv IndexView) drop(ctx context.Context, index any, opts ...options.Lister[options.DropIndexesOptions]) error {
args, err := mongoutil.NewOptions[options.DropIndexesOptions](opts...)
if err != nil {
return fmt.Errorf("failed to construct options from builder: %w", err)
}
if ctx == nil {
ctx = context.Background()
}
sess := sessionFromContext(ctx)
if sess == nil && iv.coll.client.sessionPool != nil {
sess = session.NewImplicitClientSession(iv.coll.client.sessionPool, iv.coll.client.id)
defer sess.EndSession()
}
err = iv.coll.client.validSession(sess)
if err != nil {
return err
}
wc := iv.coll.writeConcern
if sess.TransactionRunning() {
wc = nil
}
if !wc.Acknowledged() {
sess = nil
}
selector := makePinnedSelector(sess, iv.coll.writeSelector)
op := operation.NewDropIndexes(index).Session(sess).WriteConcern(wc).CommandMonitor(iv.coll.client.monitor).
ServerSelector(selector).ClusterClock(iv.coll.client.clock).
Database(iv.coll.db.name).Collection(iv.coll.name).
Deployment(iv.coll.client.deployment).ServerAPI(iv.coll.client.serverAPI).
Timeout(iv.coll.client.timeout).Crypt(iv.coll.client.cryptFLE).Authenticator(iv.coll.client.authenticator)
if rawData, ok := optionsutil.Value(args.Internal, "rawData").(bool); ok {
op = op.RawData(rawData)
}
err = op.Execute(ctx)
if err != nil {
return wrapErrors(err)
}
return nil
}
// DropOne executes a dropIndexes operation to drop an index on the collection.
//
// The name parameter should be the name of the index to drop. If the name is
// "*", ErrMultipleIndexDrop will be returned without running the command
// because doing so would drop all indexes.
//
// The opts parameter can be used to specify options for this operation (see the
// options.DropIndexesOptions documentation).
//
// For more information about the command, see https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
func (iv IndexView) DropOne(
ctx context.Context,
name string,
opts ...options.Lister[options.DropIndexesOptions],
) error {
// For more information about the command, see
// https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
if name == "*" {
return ErrMultipleIndexDrop
}
return iv.drop(ctx, name, opts...)
}
// DropWithKey drops a collection index by key using the dropIndexes operation.
//
// This function is useful to drop an index using its key specification instead of its name.
func (iv IndexView) DropWithKey(ctx context.Context, keySpecDocument any, opts ...options.Lister[options.DropIndexesOptions]) error {
doc, err := marshal(keySpecDocument, iv.coll.bsonOpts, iv.coll.registry)
if err != nil {
return err
}
return iv.drop(ctx, doc, opts...)
}
// DropAll executes a dropIndexes operation to drop all indexes on the collection.
//
// The opts parameter can be used to specify options for this operation (see the
// options.DropIndexesOptions documentation).
//
// For more information about the command, see
// https://www.mongodb.com/docs/manual/reference/command/dropIndexes/.
func (iv IndexView) DropAll(
ctx context.Context,
opts ...options.Lister[options.DropIndexesOptions],
) error {
return iv.drop(ctx, "*", opts...)
}
func getOrGenerateIndexName(keySpecDocument bsoncore.Document, model IndexModel) (string, error) {
args, err := mongoutil.NewOptions[options.IndexOptions](model.Options)
if err != nil {
return "", fmt.Errorf("failed to construct options from builder: %w", err)
}
if args != nil && args.Name != nil {
return *args.Name, nil
}
name := bytes.NewBufferString("")
first := true
elems, err := keySpecDocument.Elements()
if err != nil {
return "", err
}
for _, elem := range elems {
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
bsonValue := elem.Value()
switch bsonValue.Type {
case bsoncore.TypeInt32:
value = fmt.Sprintf("%d", bsonValue.Int32())
case bsoncore.TypeInt64:
value = fmt.Sprintf("%d", bsonValue.Int64())
case bsoncore.TypeString:
value = bsonValue.StringValue()
default:
return "", ErrInvalidIndexValue
}
_, err = name.WriteString(value)
if err != nil {
return "", err
}
first = false
}
return name.String(), nil
}

View File

@@ -0,0 +1,142 @@
// Copyright (C) MongoDB, Inc. 2019-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"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/event"
"go.mongodb.org/mongo-driver/v2/internal/driverutil"
"go.mongodb.org/mongo-driver/v2/internal/logger"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
)
// insert performs an insert operation.
type insert struct {
authenticator driver.Authenticator
bypassDocumentValidation *bool
comment bsoncore.Value
documents []bsoncore.Document
ordered *bool
session *session.Client
clock *session.ClusterClock
collection string
monitor *event.CommandMonitor
crypt driver.Crypt
database string
deployment driver.Deployment
selector description.ServerSelector
writeConcern *writeconcern.WriteConcern
retry *driver.RetryMode
result insertResult
serverAPI *driver.ServerAPIOptions
timeout *time.Duration
rawData *bool
additionalCmd bson.D
logger *logger.Logger
}
// insertResult represents an insert result returned by the server.
type insertResult struct {
// Number of documents successfully inserted.
N int64
}
func buildInsertResult(response bsoncore.Document) (insertResult, error) {
elements, err := response.Elements()
if err != nil {
return insertResult{}, err
}
ir := insertResult{}
for _, element := range elements {
if element.Key() == "n" {
var ok bool
ir.N, ok = element.Value().AsInt64OK()
if !ok {
return ir, fmt.Errorf("response field 'n' is type int32 or int64, but received BSON type %s", element.Value().Type)
}
}
}
return ir, nil
}
// Result returns the result of executing this operation.
func (i *insert) Result() insertResult { return i.result }
func (i *insert) processResponse(_ context.Context, resp bsoncore.Document, _ driver.ResponseInfo) error {
ir, err := buildInsertResult(resp)
i.result.N += ir.N
return err
}
// Execute runs this operations and returns an error if the operation did not execute successfully.
func (i *insert) Execute(ctx context.Context) error {
if i.deployment == nil {
return errors.New("the Insert operation must have a Deployment set before Execute can be called")
}
batches := &driver.Batches{
Identifier: "documents",
Documents: i.documents,
Ordered: i.ordered,
}
return driver.Operation{
CommandFn: i.command,
ProcessResponseFn: i.processResponse,
Batches: batches,
RetryMode: i.retry,
Type: driver.Write,
Client: i.session,
Clock: i.clock,
CommandMonitor: i.monitor,
Crypt: i.crypt,
Database: i.database,
Deployment: i.deployment,
Selector: i.selector,
WriteConcern: i.writeConcern,
ServerAPI: i.serverAPI,
Timeout: i.timeout,
Logger: i.logger,
Name: driverutil.InsertOp,
Authenticator: i.authenticator,
}.Execute(ctx)
}
func (i *insert) command(dst []byte, desc description.SelectedServer) ([]byte, error) {
dst = bsoncore.AppendStringElement(dst, "insert", i.collection)
if i.bypassDocumentValidation != nil && (desc.WireVersion != nil &&
driverutil.VersionRangeIncludes(*desc.WireVersion, 4)) {
dst = bsoncore.AppendBooleanElement(dst, "bypassDocumentValidation", *i.bypassDocumentValidation)
}
if i.comment.Type != bsoncore.Type(0) {
dst = bsoncore.AppendValueElement(dst, "comment", i.comment)
}
if i.ordered != nil {
dst = bsoncore.AppendBooleanElement(dst, "ordered", *i.ordered)
}
// Set rawData for 8.2+ servers.
if i.rawData != nil && desc.WireVersion != nil && driverutil.VersionRangeIncludes(*desc.WireVersion, 27) {
dst = bsoncore.AppendBooleanElement(dst, "rawData", *i.rawData)
}
if len(i.additionalCmd) > 0 {
doc, err := bson.Marshal(i.additionalCmd)
if err != nil {
return nil, err
}
dst = append(dst, doc[4:len(doc)-1]...)
}
return dst, nil
}

View File

@@ -0,0 +1,436 @@
// 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"
"io"
"net"
"reflect"
"strconv"
"strings"
"go.mongodb.org/mongo-driver/v2/internal/codecutil"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/bson"
)
var defaultRegistry = bson.NewRegistry()
// Dialer is used to make network connections.
type Dialer interface {
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
// Pipeline is a type that makes creating aggregation pipelines easier. It is a
// helper and is intended for serializing to BSON.
//
// Example usage:
//
// mongo.Pipeline{
// {{"$group", bson.D{{"_id", "$state"}, {"totalPop", bson.D{{"$sum", "$pop"}}}}}},
// {{"$match", bson.D{{"totalPop", bson.D{{"$gte", 10*1000*1000}}}}}},
// }
type Pipeline []bson.D
// getEncoder takes a writer, BSON options, and a BSON registry and returns a properly configured
// bson.Encoder that writes to the given writer.
func getEncoder(
w io.Writer,
opts *options.BSONOptions,
reg *bson.Registry,
) *bson.Encoder {
vw := bson.NewDocumentWriter(w)
enc := bson.NewEncoder(vw)
if opts != nil {
if opts.ErrorOnInlineDuplicates {
enc.ErrorOnInlineDuplicates()
}
if opts.IntMinSize {
enc.IntMinSize()
}
if opts.NilByteSliceAsEmpty {
enc.NilByteSliceAsEmpty()
}
if opts.NilMapAsEmpty {
enc.NilMapAsEmpty()
}
if opts.NilSliceAsEmpty {
enc.NilSliceAsEmpty()
}
if opts.OmitZeroStruct {
enc.OmitZeroStruct()
}
if opts.OmitEmpty {
enc.OmitEmpty()
}
if opts.StringifyMapKeysWithFmt {
enc.StringifyMapKeysWithFmt()
}
if opts.UseJSONStructTags {
enc.UseJSONStructTags()
}
}
if reg != nil {
enc.SetRegistry(reg)
}
return enc
}
// newEncoderFn will return a function for constructing an encoder based on the
// provided codec options.
func newEncoderFn(opts *options.BSONOptions, registry *bson.Registry) codecutil.EncoderFn {
return func(w io.Writer) *bson.Encoder {
return getEncoder(w, opts, registry)
}
}
// marshal marshals the given value as a BSON document. Byte slices are always converted to a
// bson.Raw before marshaling.
//
// If bsonOpts and registry are specified, the encoder is configured with the requested behaviors.
// If they are nil, the default behaviors are used.
func marshal(
val any,
bsonOpts *options.BSONOptions,
registry *bson.Registry,
) (bsoncore.Document, error) {
if registry == nil {
registry = defaultRegistry
}
if val == nil {
return nil, ErrNilDocument
}
if bs, ok := val.([]byte); ok {
// Slight optimization so we'll just use MarshalBSON and not go through the codec machinery.
val = bson.Raw(bs)
}
buf := new(bytes.Buffer)
enc := getEncoder(buf, bsonOpts, registry)
err := enc.Encode(val)
if err != nil {
return nil, MarshalError{Value: val, Err: err}
}
return buf.Bytes(), nil
}
// ensureID inserts the given ObjectID as an element named "_id" at the
// beginning of the given BSON document if there is not an "_id" already.
// If the given ObjectID is bson.NilObjectID, a new object ID will be
// generated with time.Now().
//
// If there is already an element named "_id", the document is not modified. It
// returns the resulting document and the decoded Go value of the "_id" element.
func ensureID(
doc bsoncore.Document,
oid bson.ObjectID,
bsonOpts *options.BSONOptions,
reg *bson.Registry,
) (bsoncore.Document, any, error) {
if reg == nil {
reg = defaultRegistry
}
// Try to find the "_id" element. If it exists, try to unmarshal just the
// "_id" field as an any and return it along with the unmodified
// BSON document.
if _, err := doc.LookupErr("_id"); err == nil {
var id struct {
ID any `bson:"_id"`
}
dec := getDecoder(doc, bsonOpts, reg)
err = dec.Decode(&id)
if err != nil {
return nil, nil, fmt.Errorf("error unmarshaling BSON document: %w", err)
}
return doc, id.ID, nil
}
// We couldn't find an "_id" element, so add one with the value of the
// provided ObjectID.
olddoc := doc
// Reserve an extra 17 bytes for the "_id" field we're about to add:
// type (1) + "_id" (3) + terminator (1) + object ID (12)
const extraSpace = 17
doc = make(bsoncore.Document, 0, len(olddoc)+extraSpace)
_, doc = bsoncore.ReserveLength(doc)
if oid.IsZero() {
oid = bson.NewObjectID()
}
doc = bsoncore.AppendObjectIDElement(doc, "_id", oid)
// Remove and re-write the BSON document length header.
const int32Len = 4
doc = append(doc, olddoc[int32Len:]...)
doc = bsoncore.UpdateLength(doc, 0, int32(len(doc)))
return doc, oid, nil
}
func ensureDollarKey(doc bsoncore.Document) error {
firstElem, err := doc.IndexErr(0)
if err != nil {
return errors.New("update document must have at least one element")
}
if !strings.HasPrefix(firstElem.Key(), "$") {
return errors.New("update document must contain key beginning with '$'")
}
return nil
}
func ensureNoDollarKey(doc bsoncore.Document) error {
if elem, err := doc.IndexErr(0); err == nil && strings.HasPrefix(elem.Key(), "$") {
return errors.New("replacement document cannot contain keys beginning with '$'")
}
return nil
}
func marshalAggregatePipeline(
pipeline any,
bsonOpts *options.BSONOptions,
registry *bson.Registry,
) (bsoncore.Document, bool, error) {
switch t := pipeline.(type) {
case bson.ValueMarshaler:
btype, val, err := t.MarshalBSONValue()
if err != nil {
return nil, false, err
}
if typ := bson.Type(btype); typ != bson.TypeArray {
return nil, false, fmt.Errorf("ValueMarshaler returned a %v, but was expecting %v", typ, bson.TypeArray)
}
var hasOutputStage bool
pipelineDoc := bsoncore.Document(val)
values, _ := pipelineDoc.Values()
if pipelineLen := len(values); pipelineLen > 0 {
if finalDoc, ok := values[pipelineLen-1].DocumentOK(); ok {
if elem, err := finalDoc.IndexErr(0); err == nil && (elem.Key() == "$out" || elem.Key() == "$merge") {
hasOutputStage = true
}
}
}
return pipelineDoc, hasOutputStage, nil
default:
val := reflect.ValueOf(t)
if !val.IsValid() || (val.Kind() != reflect.Slice && val.Kind() != reflect.Array) {
return nil, false, fmt.Errorf("can only marshal slices and arrays into aggregation pipelines, but got %v", val.Kind())
}
var hasOutputStage bool
valLen := val.Len()
switch t := pipeline.(type) {
// Explicitly forbid non-empty pipelines that are semantically single documents
// and are implemented as slices.
case bson.D, bson.Raw, bsoncore.Document:
if valLen > 0 {
return nil, false,
fmt.Errorf("%T is not an allowed pipeline type as it represents a single document. Use bson.A or mongo.Pipeline instead", t)
}
// bsoncore.Arrays do not need to be marshaled. Only check validity and presence of output stage.
case bsoncore.Array:
if err := t.Validate(); err != nil {
return nil, false, err
}
values, err := t.Values()
if err != nil {
return nil, false, err
}
numVals := len(values)
if numVals == 0 {
return bsoncore.Document(t), false, nil
}
// If not empty, check if first value of the last stage is $out or $merge.
if lastStage, ok := values[numVals-1].DocumentOK(); ok {
if elem, err := lastStage.IndexErr(0); err == nil && (elem.Key() == "$out" || elem.Key() == "$merge") {
hasOutputStage = true
}
}
return bsoncore.Document(t), hasOutputStage, nil
}
aidx, arr := bsoncore.AppendArrayStart(nil)
for idx := 0; idx < valLen; idx++ {
doc, err := marshal(val.Index(idx).Interface(), bsonOpts, registry)
if err != nil {
return nil, false, err
}
if idx == valLen-1 {
if elem, err := doc.IndexErr(0); err == nil && (elem.Key() == "$out" || elem.Key() == "$merge") {
hasOutputStage = true
}
}
arr = bsoncore.AppendDocumentElement(arr, strconv.Itoa(idx), doc)
}
arr, _ = bsoncore.AppendArrayEnd(arr, aidx)
return arr, hasOutputStage, nil
}
}
func marshalUpdateValue(
update any,
bsonOpts *options.BSONOptions,
registry *bson.Registry,
dollarKeysAllowed bool,
) (bsoncore.Value, error) {
documentCheckerFunc := ensureDollarKey
if !dollarKeysAllowed {
documentCheckerFunc = ensureNoDollarKey
}
var u bsoncore.Value
var err error
switch t := update.(type) {
case nil:
return u, ErrNilDocument
case bson.D:
u.Type = bsoncore.TypeEmbeddedDocument
u.Data, err = marshal(update, bsonOpts, registry)
if err != nil {
return u, err
}
return u, documentCheckerFunc(u.Data)
case bson.Raw:
u.Type = bsoncore.TypeEmbeddedDocument
u.Data = t
return u, documentCheckerFunc(u.Data)
case bsoncore.Document:
u.Type = bsoncore.TypeEmbeddedDocument
u.Data = t
return u, documentCheckerFunc(u.Data)
case []byte:
u.Type = bsoncore.TypeEmbeddedDocument
u.Data = t
return u, documentCheckerFunc(u.Data)
case bson.Marshaler:
u.Type = bsoncore.TypeEmbeddedDocument
u.Data, err = t.MarshalBSON()
if err != nil {
return u, err
}
return u, documentCheckerFunc(u.Data)
case bson.ValueMarshaler:
tt, data, err := t.MarshalBSONValue()
u.Type = bsoncore.Type(tt)
u.Data = data
if err != nil {
return u, err
}
if u.Type != bsoncore.TypeArray && u.Type != bsoncore.TypeEmbeddedDocument {
return u, fmt.Errorf("ValueMarshaler returned a %v, but was expecting %v or %v", u.Type, bsoncore.TypeArray, bsoncore.TypeEmbeddedDocument)
}
return u, err
default:
val := reflect.ValueOf(t)
if !val.IsValid() {
return u, fmt.Errorf("can only marshal slices and arrays into update pipelines, but got %v", val.Kind())
}
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
u.Type = bsoncore.TypeEmbeddedDocument
u.Data, err = marshal(update, bsonOpts, registry)
if err != nil {
return u, err
}
return u, documentCheckerFunc(u.Data)
}
u.Type = bsoncore.TypeArray
aidx, arr := bsoncore.AppendArrayStart(nil)
valLen := val.Len()
for idx := 0; idx < valLen; idx++ {
doc, err := marshal(val.Index(idx).Interface(), bsonOpts, registry)
if err != nil {
return u, err
}
if err := documentCheckerFunc(doc); err != nil {
return u, err
}
arr = bsoncore.AppendDocumentElement(arr, strconv.Itoa(idx), doc)
}
u.Data, _ = bsoncore.AppendArrayEnd(arr, aidx)
return u, err
}
}
func marshalValue(
val any,
bsonOpts *options.BSONOptions,
registry *bson.Registry,
) (bsoncore.Value, error) {
return codecutil.MarshalValue(val, newEncoderFn(bsonOpts, registry))
}
// Build the aggregation pipeline for the CountDocument command.
func countDocumentsAggregatePipeline(
filter any,
encOpts *options.BSONOptions,
registry *bson.Registry,
args *options.CountOptions,
) (bsoncore.Document, error) {
filterDoc, err := marshal(filter, encOpts, registry)
if err != nil {
return nil, err
}
aidx, arr := bsoncore.AppendArrayStart(nil)
didx, arr := bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(0))
arr = bsoncore.AppendDocumentElement(arr, "$match", filterDoc)
arr, _ = bsoncore.AppendDocumentEnd(arr, didx)
index := 1
if args != nil {
if args.Skip != nil {
didx, arr = bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(index))
arr = bsoncore.AppendInt64Element(arr, "$skip", *args.Skip)
arr, _ = bsoncore.AppendDocumentEnd(arr, didx)
index++
}
if args.Limit != nil {
didx, arr = bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(index))
arr = bsoncore.AppendInt64Element(arr, "$limit", *args.Limit)
arr, _ = bsoncore.AppendDocumentEnd(arr, didx)
index++
}
}
didx, arr = bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(index))
iidx, arr := bsoncore.AppendDocumentElementStart(arr, "$group")
arr = bsoncore.AppendInt32Element(arr, "_id", 1)
iiidx, arr := bsoncore.AppendDocumentElementStart(arr, "n")
arr = bsoncore.AppendInt32Element(arr, "$sum", 1)
arr, _ = bsoncore.AppendDocumentEnd(arr, iiidx)
arr, _ = bsoncore.AppendDocumentEnd(arr, iidx)
arr, _ = bsoncore.AppendDocumentEnd(arr, didx)
return bsoncore.AppendArrayEnd(arr, aidx)
}

View File

@@ -0,0 +1,166 @@
// 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"
"os/exec"
"strings"
"time"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/mongo/readconcern"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
)
const (
defaultServerSelectionTimeout = 10 * time.Second
defaultURI = "mongodb://localhost:27020"
defaultPath = "mongocryptd"
serverSelectionTimeoutStr = "server selection error"
)
var (
defaultTimeoutArgs = []string{"--idleShutdownTimeoutSecs=60"}
databaseOpts = options.Database().SetReadConcern(&readconcern.ReadConcern{}).SetReadPreference(readpref.Primary())
)
type mongocryptdClient struct {
bypassSpawn bool
client *Client
path string
spawnArgs []string
}
// newMongocryptdClient creates a client to mongocryptd.
// newMongocryptdClient is expected to not be called if the crypt shared library is available.
// The crypt shared library replaces all mongocryptd functionality.
func newMongocryptdClient(opts *options.AutoEncryptionOptions) (*mongocryptdClient, error) {
// create mcryptClient instance and spawn process if necessary
var bypassSpawn bool
var bypassAutoEncryption bool
if bypass, ok := opts.ExtraOptions["mongocryptdBypassSpawn"]; ok {
bypassSpawn = bypass.(bool)
}
if opts.BypassAutoEncryption != nil {
bypassAutoEncryption = *opts.BypassAutoEncryption
}
bypassQueryAnalysis := opts.BypassQueryAnalysis != nil && *opts.BypassQueryAnalysis
mc := &mongocryptdClient{
// mongocryptd should not be spawned if any of these conditions are true:
// - mongocryptdBypassSpawn is passed
// - bypassAutoEncryption is true because mongocryptd is not used during decryption
// - bypassQueryAnalysis is true because mongocryptd is not used during decryption
bypassSpawn: bypassSpawn || bypassAutoEncryption || bypassQueryAnalysis,
}
if !mc.bypassSpawn {
mc.path, mc.spawnArgs = createSpawnArgs(opts.ExtraOptions)
if err := mc.spawnProcess(); err != nil {
return nil, err
}
}
// get connection string
uri := defaultURI
if u, ok := opts.ExtraOptions["mongocryptdURI"]; ok {
uri = u.(string)
}
// create client
client, err := newClient(options.Client().ApplyURI(uri).SetServerSelectionTimeout(defaultServerSelectionTimeout))
if err != nil {
return nil, err
}
mc.client = client
return mc, nil
}
// markCommand executes the given command on mongocryptd.
func (mc *mongocryptdClient) markCommand(ctx context.Context, dbName string, cmd bsoncore.Document) (bsoncore.Document, error) {
// Remove the explicit session from the context if one is set.
// The explicit session will be from a different client.
// If an explicit session is set, it is applied after automatic encryption.
ctx = NewSessionContext(ctx, nil)
db := mc.client.Database(dbName, databaseOpts)
res, err := db.RunCommand(ctx, cmd).Raw()
// propagate original result
if err == nil {
return bsoncore.Document(res), nil
}
// wrap original error
if mc.bypassSpawn || !strings.Contains(err.Error(), serverSelectionTimeoutStr) {
return nil, MongocryptdError{Wrapped: err}
}
// re-spawn and retry
if err = mc.spawnProcess(); err != nil {
return nil, err
}
res, err = db.RunCommand(ctx, cmd).Raw()
if err != nil {
return nil, MongocryptdError{Wrapped: err}
}
return bsoncore.Document(res), nil
}
// connect connects the underlying Client instance. This must be called before performing any mark operations.
func (mc *mongocryptdClient) connect() error {
return mc.client.connect()
}
// disconnect disconnects the underlying Client instance. This should be called after all operations have completed.
func (mc *mongocryptdClient) disconnect(ctx context.Context) error {
return mc.client.Disconnect(ctx)
}
func (mc *mongocryptdClient) spawnProcess() error {
// Ignore gosec warning about subprocess launched with externally-provided path variable.
/* #nosec G204 */
cmd := exec.Command(mc.path, mc.spawnArgs...)
cmd.Stdout = nil
cmd.Stderr = nil
return cmd.Start()
}
// createSpawnArgs creates arguments to spawn mcryptClient. It returns the path and a slice of arguments.
func createSpawnArgs(opts map[string]any) (string, []string) {
var spawnArgs []string
// get command path
path := defaultPath
if p, ok := opts["mongocryptdPath"]; ok {
path = p.(string)
}
// add specified options
if sa, ok := opts["mongocryptdSpawnArgs"]; ok {
spawnArgs = append(spawnArgs, sa.([]string)...)
}
// add timeout options if necessary
var foundTimeout bool
for _, arg := range spawnArgs {
// need to use HasPrefix instead of doing an exact equality check because both
// mongocryptd supports both [--idleShutdownTimeoutSecs, 0] and [--idleShutdownTimeoutSecs=0]
if strings.HasPrefix(arg, "--idleShutdownTimeoutSecs") {
foundTimeout = true
break
}
}
if !foundTimeout {
spawnArgs = append(spawnArgs, defaultTimeoutArgs...)
}
return path, spawnArgs
}

View File

@@ -0,0 +1,41 @@
// Copyright (C) MongoDB, Inc. 2025-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
//go:build mongointernal
package mongo
import (
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
)
// NewSessionWithLSID returns a Session with the given sessionID document. The
// sessionID is a BSON document with key "id" containing a 16-byte UUID (binary
// subtype 4).
//
// Sessions returned by NewSessionWithLSID are never added to the driver's
// session pool. Calling "EndSession" or "ClientSession.SetServer" on a Session
// returned by NewSessionWithLSID will panic.
//
// NewSessionWithLSID is intended only for internal use and may be changed or
// removed at any time.
func NewSessionWithLSID(client *Client, sessionID bson.Raw) *Session {
return &Session{
clientSession: &session.Client{
Server: &session.Server{
SessionID: bsoncore.Document(sessionID),
LastUsed: time.Now(),
},
ClientID: client.id,
},
client: client,
deployment: client.deployment,
}
}

View File

@@ -0,0 +1,170 @@
// 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 options
import (
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/optionsutil"
)
// AggregateOptions represents arguments that can be used to configure an
// Aggregate operation.
//
// See corresponding setter methods for documentation.
type AggregateOptions struct {
AllowDiskUse *bool
BatchSize *int32
BypassDocumentValidation *bool
Collation *Collation
MaxAwaitTime *time.Duration
Comment any
Hint any
Let any
Custom bson.M
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// AggregateOptionsBuilder contains options to configure aggregate operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type AggregateOptionsBuilder struct {
Opts []func(*AggregateOptions) error
}
// Aggregate creates a new AggregateOptions instance.
func Aggregate() *AggregateOptionsBuilder {
return &AggregateOptionsBuilder{}
}
// List returns a list of AggergateOptions setter functions.
func (ao *AggregateOptionsBuilder) List() []func(*AggregateOptions) error {
return ao.Opts
}
// SetAllowDiskUse sets the value for the AllowDiskUse field. If true, the operation can write to temporary
// files in the _tmp subdirectory of the database directory path on the server. The default value is false.
func (ao *AggregateOptionsBuilder) SetAllowDiskUse(b bool) *AggregateOptionsBuilder {
ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
opts.AllowDiskUse = &b
return nil
})
return ao
}
// SetBatchSize sets the value for the BatchSize field. Specifies the maximum number of documents
// to be included in each batch returned by the server.
func (ao *AggregateOptionsBuilder) SetBatchSize(i int32) *AggregateOptionsBuilder {
ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
opts.BatchSize = &i
return nil
})
return ao
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true, writes
// executed as part of the operation will opt out of document-level validation on the server. The default value
// is false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about
// document validation.
func (ao *AggregateOptionsBuilder) SetBypassDocumentValidation(b bool) *AggregateOptionsBuilder {
ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
opts.BypassDocumentValidation = &b
return nil
})
return ao
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (ao *AggregateOptionsBuilder) SetCollation(c *Collation) *AggregateOptionsBuilder {
ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
opts.Collation = c
return nil
})
return ao
}
// SetMaxAwaitTime sets the value for the MaxAwaitTime field. Specifies maximum amount of time
// that the server should wait for new documents to satisfy a tailable cursor query.
func (ao *AggregateOptionsBuilder) SetMaxAwaitTime(d time.Duration) *AggregateOptionsBuilder {
ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
opts.MaxAwaitTime = &d
return nil
})
return ao
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be included in
// server logs, profiling logs, and currentOp queries to help trace the operation. The default is nil,
// which means that no comment will be included in the logs.
func (ao *AggregateOptionsBuilder) SetComment(comment any) *AggregateOptionsBuilder {
ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
opts.Comment = comment
return nil
})
return ao
}
// SetHint sets the value for the Hint field. Specifies the index to use for the aggregation. This should
// either be the index name as a string or the index specification as a document. The hint does not apply to
// $lookup and $graphLookup aggregation stages. The driver will return an error if the hint parameter
// is a multi-key map. The default value is nil, which means that no hint will be sent.
func (ao *AggregateOptionsBuilder) SetHint(h any) *AggregateOptionsBuilder {
ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
opts.Hint = h
return nil
})
return ao
}
// SetLet sets the value for the Let field. Specifies parameters for the aggregate expression. This
// option is only valid for MongoDB versions >= 5.0. Older servers will report an error for using this
// option. This must be a document mapping parameter names to values. Values must be constant or closed
// expressions that do not reference document fields. Parameters can then be accessed as variables in
// an aggregate expression context (e.g. "$$var").
func (ao *AggregateOptionsBuilder) SetLet(let any) *AggregateOptionsBuilder {
ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
opts.Let = let
return nil
})
return ao
}
// SetCustom sets the value for the Custom field. Key-value pairs of the BSON map should correlate
// with desired option names and values. Values must be Marshalable. Custom options may conflict
// with non-custom options, and custom options bypass client-side validation. Prefer using non-custom
// options where possible.
func (ao *AggregateOptionsBuilder) SetCustom(c bson.M) *AggregateOptionsBuilder {
ao.Opts = append(ao.Opts, func(opts *AggregateOptions) error {
opts.Custom = c
return nil
})
return ao
}

View File

@@ -0,0 +1,176 @@
// 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 options
import (
"crypto/tls"
"net/http"
"time"
"go.mongodb.org/mongo-driver/v2/internal/httputil"
)
// AutoEncryptionOptions represents arguments used to configure auto encryption/decryption behavior for a mongo.Client
// instance.
//
// Automatic encryption is an enterprise only feature that only applies to operations on a collection. Automatic
// encryption is not supported for operations on a database or view, and operations that are not bypassed will result
// in error. Too bypass automatic encryption for all operations, set BypassAutoEncryption=true.
//
// Auto encryption requires the authenticated user to have the listCollections privilege action.
//
// If automatic encryption fails on an operation, use a MongoClient configured with bypassAutoEncryption=true and use
// ClientEncryption.encrypt() to manually encrypt values.
//
// Enabling In-Use Encryption reduces the maximum document and message size (using a maxBsonObjectSize of 2MiB and
// maxMessageSizeBytes of 6MB) and may have a negative performance impact.
//
// See corresponding setter methods for documentation.
type AutoEncryptionOptions struct {
KeyVaultClientOptions *ClientOptions
KeyVaultNamespace string
KmsProviders map[string]map[string]any
SchemaMap map[string]any
BypassAutoEncryption *bool
ExtraOptions map[string]any
TLSConfig map[string]*tls.Config
HTTPClient *http.Client
EncryptedFieldsMap map[string]any
BypassQueryAnalysis *bool
KeyExpiration *time.Duration
}
// AutoEncryption creates a new AutoEncryptionOptions configured with default values.
func AutoEncryption() *AutoEncryptionOptions {
return &AutoEncryptionOptions{
HTTPClient: httputil.DefaultHTTPClient,
}
}
// SetKeyVaultClientOptions specifies options for the client used to communicate with the key vault collection.
//
// If this is set, it is used to create an internal mongo.Client.
// Otherwise, if the target mongo.Client being configured has an unlimited connection pool size (i.e. maxPoolSize=0),
// it is reused to interact with the key vault collection.
// Otherwise, if the target mongo.Client has a limited connection pool size, a separate internal mongo.Client is used
// (and created if necessary). The internal mongo.Client may be shared during automatic encryption (if
// BypassAutomaticEncryption is false). The internal mongo.Client is configured with the same options as the target
// mongo.Client except minPoolSize is set to 0 and AutoEncryptionOptions is omitted.
func (a *AutoEncryptionOptions) SetKeyVaultClientOptions(opts *ClientOptions) *AutoEncryptionOptions {
a.KeyVaultClientOptions = opts
return a
}
// SetKeyVaultNamespace specifies the namespace of the key vault collection. This is required.
func (a *AutoEncryptionOptions) SetKeyVaultNamespace(ns string) *AutoEncryptionOptions {
a.KeyVaultNamespace = ns
return a
}
// SetKmsProviders specifies options for KMS providers. This is required.
func (a *AutoEncryptionOptions) SetKmsProviders(providers map[string]map[string]any) *AutoEncryptionOptions {
a.KmsProviders = providers
return a
}
// SetSchemaMap specifies a map from namespace to local schema document. Schemas supplied in the schemaMap only apply
// to configuring automatic encryption for Client-Side Field Level Encryption. Other validation rules in the JSON schema
// will not be enforced by the driver and will result in an error.
//
// Supplying a schemaMap provides more security than relying on JSON Schemas obtained from the server. It protects
// against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted
// data that should be encrypted.
func (a *AutoEncryptionOptions) SetSchemaMap(schemaMap map[string]any) *AutoEncryptionOptions {
a.SchemaMap = schemaMap
return a
}
// SetBypassAutoEncryption specifies whether or not auto encryption should be done.
//
// If this is unset or false and target mongo.Client being configured has an unlimited connection pool size
// (i.e. maxPoolSize=0), it is reused in the process of auto encryption.
// Otherwise, if the target mongo.Client has a limited connection pool size, a separate internal mongo.Client is used
// (and created if necessary). The internal mongo.Client may be shared for key vault operations (if KeyVaultClient is
// unset). The internal mongo.Client is configured with the same options as the target mongo.Client except minPoolSize
// is set to 0 and AutoEncryptionOptions is omitted.
func (a *AutoEncryptionOptions) SetBypassAutoEncryption(bypass bool) *AutoEncryptionOptions {
a.BypassAutoEncryption = &bypass
return a
}
// SetExtraOptions specifies a map of options to configure the mongocryptd process or mongo_crypt shared library.
//
// # Supported Extra Options
//
// "mongocryptdURI" - The mongocryptd URI. Allows setting a custom URI used to communicate with the
// mongocryptd process. The default is "mongodb://localhost:27020", which works with the default
// mongocryptd process spawned by the Client. Must be a string.
//
// "mongocryptdBypassSpawn" - If set to true, the Client will not attempt to spawn a mongocryptd
// process. Must be a bool.
//
// "mongocryptdSpawnPath" - The path used when spawning mongocryptd.
// Defaults to empty string and spawns mongocryptd from system path. Must be a string.
//
// "mongocryptdSpawnArgs" - Command line arguments passed when spawning mongocryptd.
// Defaults to ["--idleShutdownTimeoutSecs=60"]. Must be an array of strings.
//
// "cryptSharedLibRequired" - If set to true, Client creation will return an error if the
// crypt_shared library is not loaded. If unset or set to false, Client creation will not return an
// error if the crypt_shared library is not loaded. The default is unset. Must be a bool.
//
// "cryptSharedLibPath" - The crypt_shared library override path. This must be the path to the
// crypt_shared dynamic library file (for example, a .so, .dll, or .dylib file), not the directory
// that contains it. If the override path is a relative path, it will be resolved relative to the
// working directory of the process. If the override path is a relative path and the first path
// component is the literal string "$ORIGIN", the "$ORIGIN" component will be replaced by the
// absolute path to the directory containing the linked libmongocrypt library. Setting an override
// path disables the default system library search path. If an override path is specified but the
// crypt_shared library cannot be loaded, Client creation will return an error. Must be a string.
func (a *AutoEncryptionOptions) SetExtraOptions(extraOpts map[string]any) *AutoEncryptionOptions {
a.ExtraOptions = extraOpts
return a
}
// SetTLSConfig specifies tls.Config instances for each KMS provider to use to configure TLS on all connections created
// to the KMS provider.
func (a *AutoEncryptionOptions) SetTLSConfig(cfg map[string]*tls.Config) *AutoEncryptionOptions {
// This should only be used to set custom TLS configurations. By default, the connection will use an empty tls.Config{} with MinVersion set to tls.VersionTLS12.
a.TLSConfig = cfg
return a
}
// SetEncryptedFieldsMap specifies a map from namespace to local EncryptedFieldsMap document.
// EncryptedFieldsMap is used for Queryable Encryption.
func (a *AutoEncryptionOptions) SetEncryptedFieldsMap(ef map[string]any) *AutoEncryptionOptions {
a.EncryptedFieldsMap = ef
return a
}
// SetBypassQueryAnalysis specifies whether or not query analysis should be used for automatic encryption.
// Use this option when using explicit encryption with Queryable Encryption.
func (a *AutoEncryptionOptions) SetBypassQueryAnalysis(bypass bool) *AutoEncryptionOptions {
a.BypassQueryAnalysis = &bypass
return a
}
// SetKeyExpiration specifies duration for the key expiration. 0 or negative value means "never expire".
// The granularity is in milliseconds. Any sub-millisecond fraction will be rounded up.
func (a *AutoEncryptionOptions) SetKeyExpiration(expiration time.Duration) *AutoEncryptionOptions {
a.KeyExpiration = &expiration
return a
}

View File

@@ -0,0 +1,100 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// DefaultOrdered is the default value for the Ordered option in BulkWriteOptions.
var DefaultOrdered = true
// BulkWriteOptions represents arguments that can be used to configure a
// BulkWrite operation.
//
// See corresponding setter methods for documentation.
type BulkWriteOptions struct {
BypassDocumentValidation *bool
Comment any
Ordered *bool
Let any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// BulkWriteOptionsBuilder contains options to configure bulk write operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type BulkWriteOptionsBuilder struct {
Opts []func(*BulkWriteOptions) error
}
// BulkWrite creates a new *BulkWriteOptions instance.
func BulkWrite() *BulkWriteOptionsBuilder {
opts := &BulkWriteOptionsBuilder{}
opts = opts.SetOrdered(DefaultOrdered)
return opts
}
// List returns a list of BulkWriteOptions setter functions.
func (b *BulkWriteOptionsBuilder) List() []func(*BulkWriteOptions) error {
return b.Opts
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be included in
// server logs, profiling logs, and currentOp queries to help tracethe operation. The default value is nil,
// which means that no comment will be included in the logs.
func (b *BulkWriteOptionsBuilder) SetComment(comment any) *BulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *BulkWriteOptions) error {
opts.Comment = comment
return nil
})
return b
}
// SetOrdered sets the value for the Ordered field. If true, no writes will be executed after one fails.
// The default value is true.
func (b *BulkWriteOptionsBuilder) SetOrdered(ordered bool) *BulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *BulkWriteOptions) error {
opts.Ordered = &ordered
return nil
})
return b
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true, writes
// executed as part of the operation will opt out of document-level validation on the server. The default value is
// false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document
// validation.
func (b *BulkWriteOptionsBuilder) SetBypassDocumentValidation(bypass bool) *BulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *BulkWriteOptions) error {
opts.BypassDocumentValidation = &bypass
return nil
})
return b
}
// SetLet sets the value for the Let field. Let specifies parameters for all update and delete commands in the BulkWrite.
// This option is only valid for MongoDB versions >= 5.0. Older servers will report an error for using this option.
// This must be a document mapping parameter names to values. Values must be constant or closed expressions that do not
// reference document fields. Parameters can then be accessed as variables in an aggregate expression context (e.g. "$$var").
func (b *BulkWriteOptionsBuilder) SetLet(let any) *BulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *BulkWriteOptions) error {
opts.Let = &let
return nil
})
return b
}

View File

@@ -0,0 +1,183 @@
// 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 options
import (
"time"
"go.mongodb.org/mongo-driver/v2/bson"
)
// ChangeStreamOptions represents arguments that can be used to configure a Watch operation.
//
// See corresponding setter methods for documentation.
type ChangeStreamOptions struct {
BatchSize *int32
Collation *Collation
Comment any
FullDocument *FullDocument
FullDocumentBeforeChange *FullDocument
MaxAwaitTime *time.Duration
ResumeAfter any
ShowExpandedEvents *bool
StartAtOperationTime *bson.Timestamp
StartAfter any
Custom bson.M
CustomPipeline bson.M
}
// ChangeStreamOptionsBuilder contains options to configure change stream
// operations. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type ChangeStreamOptionsBuilder struct {
Opts []func(*ChangeStreamOptions) error
}
// ChangeStream creates a new ChangeStreamOptions instance.
func ChangeStream() *ChangeStreamOptionsBuilder {
return &ChangeStreamOptionsBuilder{}
}
// List returns a list of ChangeStreamOptions setter functions.
func (cso *ChangeStreamOptionsBuilder) List() []func(*ChangeStreamOptions) error {
return cso.Opts
}
// SetBatchSize sets the value for the BatchSize field. Specifies the maximum number of documents to
// be included in each batch returned by the server.
func (cso *ChangeStreamOptionsBuilder) SetBatchSize(i int32) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.BatchSize = &i
return nil
})
return cso
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (cso *ChangeStreamOptionsBuilder) SetCollation(c Collation) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.Collation = &c
return nil
})
return cso
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be included in
// server logs, profiling logs, and currentOp queries to help trace the operation. The default is nil,
// which means that no comment will be included in the logs.
func (cso *ChangeStreamOptionsBuilder) SetComment(comment any) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.Comment = comment
return nil
})
return cso
}
// SetFullDocument sets the value for the FullDocument field. Specifies how the updated document should be
// returned in change notifications for update operations. The default is options.Default, which means that
// only partial update deltas will be included in the change notification.
func (cso *ChangeStreamOptionsBuilder) SetFullDocument(fd FullDocument) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.FullDocument = &fd
return nil
})
return cso
}
// SetFullDocumentBeforeChange sets the value for the FullDocumentBeforeChange field. Specifies how the
// pre-update document should be returned in change notifications for update operations. The default
// is options.Off, which means that the pre-update document will not be included in the change notification.
func (cso *ChangeStreamOptionsBuilder) SetFullDocumentBeforeChange(fdbc FullDocument) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.FullDocumentBeforeChange = &fdbc
return nil
})
return cso
}
// SetMaxAwaitTime sets the value for the MaxAwaitTime field. The maximum amount of time that the server should
// wait for new documents to satisfy a tailable cursor query.
func (cso *ChangeStreamOptionsBuilder) SetMaxAwaitTime(d time.Duration) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.MaxAwaitTime = &d
return nil
})
return cso
}
// SetResumeAfter sets the value for the ResumeAfter field. Specifies a document specifying the logical starting
// point for the change stream. Only changes corresponding to an oplog entry immediately after the resume token
// will be returned. If this is specified, StartAtOperationTime and StartAfter must not be set.
func (cso *ChangeStreamOptionsBuilder) SetResumeAfter(rt any) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.ResumeAfter = rt
return nil
})
return cso
}
// SetShowExpandedEvents sets the value for the ShowExpandedEvents field. ShowExpandedEvents specifies whether
// the server will return an expanded list of change stream events. Additional events include: createIndexes,
// dropIndexes, modify, create, shardCollection, reshardCollection and refineCollectionShardKey. This option
// is only valid for MongoDB versions >= 6.0.
func (cso *ChangeStreamOptionsBuilder) SetShowExpandedEvents(see bool) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.ShowExpandedEvents = &see
return nil
})
return cso
}
// SetStartAtOperationTime sets the value for the StartAtOperationTime field. If specified, the change stream
// will only return changes that occurred at or after the given timestamp.
// If this is specified, ResumeAfter and StartAfter must not be set.
func (cso *ChangeStreamOptionsBuilder) SetStartAtOperationTime(t *bson.Timestamp) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.StartAtOperationTime = t
return nil
})
return cso
}
// SetStartAfter sets the value for the StartAfter field. Sets a document specifying the logical starting
// point for the change stream. This is similar to the ResumeAfter option, but allows a resume token from
// an "invalidate" notification to be used. This allows a change stream on a collection to be resumed after
// the collection has been dropped and recreated or renamed. Only changes corresponding to an oplog entry
// immediately after the specified token will be returned. If this is specified, ResumeAfter and
// StartAtOperationTime must not be set. This option is only valid for MongoDB versions >= 4.1.1.
func (cso *ChangeStreamOptionsBuilder) SetStartAfter(sa any) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.StartAfter = sa
return nil
})
return cso
}
// SetCustom sets the value for the Custom field. Key-value pairs of the BSON map should correlate
// with desired option names and values. Values must be Marshalable. Custom options may conflict
// with non-custom options, and custom options bypass client-side validation. Prefer using non-custom
// options where possible.
func (cso *ChangeStreamOptionsBuilder) SetCustom(c bson.M) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.Custom = c
return nil
})
return cso
}
// SetCustomPipeline sets the value for the CustomPipeline field. Key-value pairs of the BSON map
// should correlate with desired option names and values. Values must be Marshalable. Custom pipeline
// options bypass client-side validation. Prefer using non-custom options where possible.
func (cso *ChangeStreamOptionsBuilder) SetCustomPipeline(cp bson.M) *ChangeStreamOptionsBuilder {
cso.Opts = append(cso.Opts, func(opts *ChangeStreamOptions) error {
opts.CustomPipeline = cp
return nil
})
return cso
}

View File

@@ -0,0 +1,127 @@
// Copyright (C) MongoDB, Inc. 2024-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 options
import (
"go.mongodb.org/mongo-driver/v2/internal/optionsutil"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
)
// ClientBulkWriteOptions represents options that can be used to configure a client-level BulkWrite operation.
//
// See corresponding setter methods for documentation.
type ClientBulkWriteOptions struct {
BypassDocumentValidation *bool
Comment any
Ordered *bool
Let any
WriteConcern *writeconcern.WriteConcern
VerboseResults *bool
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// ClientBulkWriteOptionsBuilder contains options to configure client-level bulk
// write operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type ClientBulkWriteOptionsBuilder struct {
Opts []func(*ClientBulkWriteOptions) error
}
// ClientBulkWrite creates a new *ClientBulkWriteOptions instance.
func ClientBulkWrite() *ClientBulkWriteOptionsBuilder {
opts := &ClientBulkWriteOptionsBuilder{}
opts = opts.SetOrdered(DefaultOrdered)
return opts
}
// List returns a list of ClientBulkWriteOptions setter functions.
func (b *ClientBulkWriteOptionsBuilder) List() []func(*ClientBulkWriteOptions) error {
return b.Opts
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be included in
// server logs, profiling logs, and currentOp queries to help tracethe operation. The default value is nil,
// which means that no comment will be included in the logs.
func (b *ClientBulkWriteOptionsBuilder) SetComment(comment any) *ClientBulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
opts.Comment = comment
return nil
})
return b
}
// SetOrdered sets the value for the Ordered field. If true, no writes will be executed after one fails.
// The default value is true.
func (b *ClientBulkWriteOptionsBuilder) SetOrdered(ordered bool) *ClientBulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
opts.Ordered = &ordered
return nil
})
return b
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true, writes
// executed as part of the operation will opt out of document-level validation on the server. The default
// value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information
// about document validation.
func (b *ClientBulkWriteOptionsBuilder) SetBypassDocumentValidation(bypass bool) *ClientBulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
opts.BypassDocumentValidation = &bypass
return nil
})
return b
}
// SetLet sets the value for the Let field. Let specifies parameters for all update and delete commands in the BulkWrite.
// This must be a document mapping parameter names to values. Values must be constant or closed expressions that do not
// reference document fields. Parameters can then be accessed as variables in an aggregate expression context (e.g. "$$var").
func (b *ClientBulkWriteOptionsBuilder) SetLet(let any) *ClientBulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
opts.Let = &let
return nil
})
return b
}
// SetWriteConcern sets the value for the WriteConcern field. Specifies the write concern for
// operations in the transaction. The default value is nil, which means that the default
// write concern of the session used to start the transaction will be used.
func (b *ClientBulkWriteOptionsBuilder) SetWriteConcern(wc *writeconcern.WriteConcern) *ClientBulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
opts.WriteConcern = wc
return nil
})
return b
}
// SetVerboseResults sets the value for the VerboseResults field. Specifies whether detailed
// results for each successful operation should be included in the returned BulkWriteResult.
// The defaults value is false.
func (b *ClientBulkWriteOptionsBuilder) SetVerboseResults(verboseResults bool) *ClientBulkWriteOptionsBuilder {
b.Opts = append(b.Opts, func(opts *ClientBulkWriteOptions) error {
opts.VerboseResults = &verboseResults
return nil
})
return b
}

View File

@@ -0,0 +1,158 @@
// 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 options
import (
"crypto/tls"
"fmt"
"net/http"
"time"
"go.mongodb.org/mongo-driver/v2/internal/httputil"
)
// ClientEncryptionOptions represents all possible arguments used to configure a ClientEncryption instance.
//
// See corresponding setter methods for documentation.
type ClientEncryptionOptions struct {
KeyVaultNamespace string
KmsProviders map[string]map[string]any
TLSConfig map[string]*tls.Config
HTTPClient *http.Client
KeyExpiration *time.Duration
}
// ClientEncryptionOptionsBuilder contains options to configure client
// encryption operations. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type ClientEncryptionOptionsBuilder struct {
Opts []func(*ClientEncryptionOptions) error
}
// ClientEncryption creates a new ClientEncryptionOptions instance.
func ClientEncryption() *ClientEncryptionOptionsBuilder {
return &ClientEncryptionOptionsBuilder{
Opts: []func(*ClientEncryptionOptions) error{
func(arg *ClientEncryptionOptions) error {
arg.HTTPClient = httputil.DefaultHTTPClient
return nil
},
},
}
}
// List returns a list of ClientEncryptionOptions setter functions.
func (c *ClientEncryptionOptionsBuilder) List() []func(*ClientEncryptionOptions) error {
return c.Opts
}
// SetKeyVaultNamespace specifies the namespace of the key vault collection. This is required.
func (c *ClientEncryptionOptionsBuilder) SetKeyVaultNamespace(ns string) *ClientEncryptionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *ClientEncryptionOptions) error {
opts.KeyVaultNamespace = ns
return nil
})
return c
}
// SetKmsProviders specifies options for KMS providers. This is required.
func (c *ClientEncryptionOptionsBuilder) SetKmsProviders(providers map[string]map[string]any) *ClientEncryptionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *ClientEncryptionOptions) error {
opts.KmsProviders = providers
return nil
})
return c
}
// SetTLSConfig specifies tls.Config instances for each KMS provider to use to configure TLS on all connections created
// to the KMS provider.
//
// This should only be used to set custom TLS configurations. By default, the connection will use an empty tls.Config{} with MinVersion set to tls.VersionTLS12.
func (c *ClientEncryptionOptionsBuilder) SetTLSConfig(cfg map[string]*tls.Config) *ClientEncryptionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *ClientEncryptionOptions) error {
opts.TLSConfig = cfg
return nil
})
return c
}
// SetKeyExpiration specifies duration for the key expiration. 0 or negative value means "never expire".
// The granularity is in milliseconds. Any sub-millisecond fraction will be rounded up.
func (c *ClientEncryptionOptionsBuilder) SetKeyExpiration(expiration time.Duration) *ClientEncryptionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *ClientEncryptionOptions) error {
opts.KeyExpiration = &expiration
return nil
})
return c
}
// BuildTLSConfig specifies tls.Config options for each KMS provider to use to configure TLS on all connections created
// to the KMS provider. The input map should contain a mapping from each KMS provider to a document containing the necessary
// options, as follows:
//
// {
// "kmip": {
// "tlsCertificateKeyFile": "foo.pem",
// "tlsCAFile": "fooCA.pem"
// }
// }
//
// Currently, the following TLS options are supported:
//
// 1. "tlsCertificateKeyFile" (or "sslClientCertificateKeyFile"): The "tlsCertificateKeyFile" option specifies a path to
// the client certificate and private key, which must be concatenated into one file.
//
// 2. "tlsCertificateKeyFilePassword" (or "sslClientCertificateKeyPassword"): Specify the password to decrypt the client
// private key file (e.g. "tlsCertificateKeyFilePassword=password").
//
// 3. "tlsCaFile" (or "sslCertificateAuthorityFile"): Specify the path to a single or bundle of certificate authorities
// to be considered trusted when making a TLS connection (e.g. "tlsCaFile=/path/to/caFile").
//
// This should only be used to set custom TLS options. By default, the connection will use an empty tls.Config{} with MinVersion set to tls.VersionTLS12.
func BuildTLSConfig(tlsOpts map[string]any) (*tls.Config, error) {
// use TLS min version 1.2 to enforce more secure hash algorithms and advanced cipher suites
cfg := &tls.Config{MinVersion: tls.VersionTLS12}
for name := range tlsOpts {
var err error
switch name {
case "tlsCertificateKeyFile", "sslClientCertificateKeyFile":
clientCertPath, ok := tlsOpts[name].(string)
if !ok {
return nil, fmt.Errorf("expected %q value to be of type string, got %T", name, tlsOpts[name])
}
// apply custom key file password if found, otherwise use empty string
if keyPwd, found := tlsOpts["tlsCertificateKeyFilePassword"].(string); found {
_, err = addClientCertFromConcatenatedFile(cfg, clientCertPath, keyPwd)
} else if keyPwd, found := tlsOpts["sslClientCertificateKeyPassword"].(string); found {
_, err = addClientCertFromConcatenatedFile(cfg, clientCertPath, keyPwd)
} else {
_, err = addClientCertFromConcatenatedFile(cfg, clientCertPath, "")
}
case "tlsCertificateKeyFilePassword", "sslClientCertificateKeyPassword":
continue
case "tlsCAFile", "sslCertificateAuthorityFile":
caPath, ok := tlsOpts[name].(string)
if !ok {
return nil, fmt.Errorf("expected %q value to be of type string, got %T", name, tlsOpts[name])
}
err = addCACertFromFile(cfg, caPath)
default:
return nil, fmt.Errorf("unrecognized TLS option %v", name)
}
if err != nil {
return nil, err
}
}
return cfg, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
// 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 options
import (
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo/readconcern"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
)
// CollectionOptions represents arguments that can be used to configure a Collection.
//
// See corresponding setter methods for documentation.
type CollectionOptions struct {
ReadConcern *readconcern.ReadConcern
WriteConcern *writeconcern.WriteConcern
ReadPreference *readpref.ReadPref
BSONOptions *BSONOptions
Registry *bson.Registry
}
// CollectionOptionsBuilder contains options to configure a Collection instance.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type CollectionOptionsBuilder struct {
Opts []func(*CollectionOptions) error
}
// Collection creates a new CollectionOptions instance.
func Collection() *CollectionOptionsBuilder {
return &CollectionOptionsBuilder{}
}
// List returns a list of CollectionOptions setter functions.
func (c *CollectionOptionsBuilder) List() []func(*CollectionOptions) error {
return c.Opts
}
// SetReadConcern sets the value for the ReadConcern field. ReadConcern is the read concern to use for
// operations executed on the Collection. The default value is nil, which means that the read concern
// of the Database used to configure the Collection will be used.
func (c *CollectionOptionsBuilder) SetReadConcern(rc *readconcern.ReadConcern) *CollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
opts.ReadConcern = rc
return nil
})
return c
}
// SetWriteConcern sets the value for the WriteConcern field. WriteConcern is the write concern to
// use for operations executed on the Collection. The default value is nil, which means that the write
// concern of the Database used to configure the Collection will be used.
func (c *CollectionOptionsBuilder) SetWriteConcern(wc *writeconcern.WriteConcern) *CollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
opts.WriteConcern = wc
return nil
})
return c
}
// SetReadPreference sets the value for the ReadPreference field. ReadPreference is the read preference
// to use for operations executed on the Collection. The default value is nil, which means that the
// read preference of the Database used to configure the Collection will be used.
func (c *CollectionOptionsBuilder) SetReadPreference(rp *readpref.ReadPref) *CollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
opts.ReadPreference = rp
return nil
})
return c
}
// SetBSONOptions configures optional BSON marshaling and unmarshaling behavior. BSONOptions configures
// optional BSON marshaling and unmarshaling behavior.
func (c *CollectionOptionsBuilder) SetBSONOptions(bopts *BSONOptions) *CollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
opts.BSONOptions = bopts
return nil
})
return c
}
// SetRegistry sets the value for the Registry field. Registry is the BSON registry to marshal and
// unmarshal documents for operations executed on the Collection. The default value is nil, which
// means that the registry of the Database used to configure the Collection will be used.
func (c *CollectionOptionsBuilder) SetRegistry(r *bson.Registry) *CollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CollectionOptions) error {
opts.Registry = r
return nil
})
return c
}

View File

@@ -0,0 +1,107 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// CountOptions represents arguments that can be used to configure a
// CountDocuments operation.
//
// See corresponding setter methods for documentation.
type CountOptions struct {
Collation *Collation
Comment any
Hint any
Limit *int64
Skip *int64
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// CountOptionsBuilder contains options to configure count operations. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type CountOptionsBuilder struct {
Opts []func(*CountOptions) error
}
// Count creates a new CountOptions instance.
func Count() *CountOptionsBuilder {
return &CountOptionsBuilder{}
}
// List returns a list of CountOptions setter functions.
func (co *CountOptionsBuilder) List() []func(*CountOptions) error {
return co.Opts
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (co *CountOptionsBuilder) SetCollation(c *Collation) *CountOptionsBuilder {
co.Opts = append(co.Opts, func(opts *CountOptions) error {
opts.Collation = c
return nil
})
return co
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be included
// in server logs, profiling logs, and currentOp queries to help trace the operation. The default is nil,
// which means that no comment will be included in the logs.
func (co *CountOptionsBuilder) SetComment(comment any) *CountOptionsBuilder {
co.Opts = append(co.Opts, func(opts *CountOptions) error {
opts.Comment = comment
return nil
})
return co
}
// SetHint sets the value for the Hint field. Specifies the index to use for the aggregation. This should
// either be the index name as a string or the index specification as a document. The driver will return
// an error if the hint parameter is a multi-key map. The default value is nil, which means that no hint
// will be sent.
func (co *CountOptionsBuilder) SetHint(h any) *CountOptionsBuilder {
co.Opts = append(co.Opts, func(opts *CountOptions) error {
opts.Hint = h
return nil
})
return co
}
// SetLimit sets the value for the Limit field. Specifies the maximum number of documents to count. The
// default value is 0, which means that there is no limit and all documents matching the filter will be
// counted.
func (co *CountOptionsBuilder) SetLimit(i int64) *CountOptionsBuilder {
co.Opts = append(co.Opts, func(opts *CountOptions) error {
opts.Limit = &i
return nil
})
return co
}
// SetSkip sets the value for the Skip field. Specifies the number of documents to skip before counting.
// The default value is 0.
func (co *CountOptionsBuilder) SetSkip(i int64) *CountOptionsBuilder {
co.Opts = append(co.Opts, func(opts *CountOptions) error {
opts.Skip = &i
return nil
})
return co
}

View File

@@ -0,0 +1,413 @@
// 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 options
import (
"time"
)
// DefaultIndexOptions represents the default arguments for a collection to
// apply on new indexes. This type can be used when creating a new collection
// through the CreateCollectionOptions.SetDefaultIndexOptions method.
//
// See corresponding setter methods for documentation.
type DefaultIndexOptions struct {
StorageEngine any
}
// DefaultIndexOptionsBuilder contains options to configure default index
// operations. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type DefaultIndexOptionsBuilder struct {
Opts []func(*DefaultIndexOptions) error
}
// DefaultIndex creates a new DefaultIndexOptions instance.
func DefaultIndex() *DefaultIndexOptionsBuilder {
return &DefaultIndexOptionsBuilder{}
}
// List returns a list of DefaultIndexOptions setter functions.
func (d *DefaultIndexOptionsBuilder) List() []func(*DefaultIndexOptions) error {
return d.Opts
}
// SetStorageEngine sets the value for the StorageEngine field. Specifies the storage engine to use for
// the index. The value must be a document in the form {<storage engine name>: <options>}. The default
// value is nil, which means that the default storage engine will be used.
func (d *DefaultIndexOptionsBuilder) SetStorageEngine(storageEngine any) *DefaultIndexOptionsBuilder {
d.Opts = append(d.Opts, func(opts *DefaultIndexOptions) error {
opts.StorageEngine = storageEngine
return nil
})
return d
}
// TimeSeriesOptions specifies arguments on a time-series collection.
//
// See corresponding setter methods for documentation.
type TimeSeriesOptions struct {
TimeField string
MetaField *string
Granularity *string
BucketMaxSpan *time.Duration
BucketRounding *time.Duration
}
// TimeSeriesOptionsBuilder contains options to configure timeseries operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type TimeSeriesOptionsBuilder struct {
Opts []func(*TimeSeriesOptions) error
}
// TimeSeries creates a new TimeSeriesOptions instance.
func TimeSeries() *TimeSeriesOptionsBuilder {
return &TimeSeriesOptionsBuilder{}
}
// List returns a list of TimeSeriesOptions setter functions.
func (tso *TimeSeriesOptionsBuilder) List() []func(*TimeSeriesOptions) error {
return tso.Opts
}
// SetTimeField sets the value for the TimeField. TimeField is the top-level field to be used
// for time. Inserted documents must have this field, and the field must be of the BSON UTC
// datetime type (0x9).
func (tso *TimeSeriesOptionsBuilder) SetTimeField(timeField string) *TimeSeriesOptionsBuilder {
tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
opts.TimeField = timeField
return nil
})
return tso
}
// SetMetaField sets the value for the MetaField. MetaField is the name of the top-level field
// describing the series. This field is used to group related data and may be of any BSON type,
// except for array. This name may not be the same as the TimeField or _id. This field is optional.
func (tso *TimeSeriesOptionsBuilder) SetMetaField(metaField string) *TimeSeriesOptionsBuilder {
tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
opts.MetaField = &metaField
return nil
})
return tso
}
// SetGranularity sets the value for Granularity. Granularity is the granularity of time-series data.
// Allowed granularity options are "seconds", "minutes" and "hours". This field is optional.
func (tso *TimeSeriesOptionsBuilder) SetGranularity(granularity string) *TimeSeriesOptionsBuilder {
tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
opts.Granularity = &granularity
return nil
})
return tso
}
// SetBucketMaxSpan sets the value for BucketMaxSpan. BucketMaxSpan is the maximum range of time
// values for a bucket. The time.Duration is rounded down to the nearest second and applied as
// the command option: "bucketRoundingSeconds". This field is optional.
func (tso *TimeSeriesOptionsBuilder) SetBucketMaxSpan(dur time.Duration) *TimeSeriesOptionsBuilder {
tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
opts.BucketMaxSpan = &dur
return nil
})
return tso
}
// SetBucketRounding sets the value for BucketRounding. BucketRounding is used to determine the
// minimum time boundary when opening a new bucket by rounding the first timestamp down to the next
// multiple of this value. The time.Duration is rounded down to the nearest second and applied as
// the command option: "bucketRoundingSeconds". This field is optional.
func (tso *TimeSeriesOptionsBuilder) SetBucketRounding(dur time.Duration) *TimeSeriesOptionsBuilder {
tso.Opts = append(tso.Opts, func(opts *TimeSeriesOptions) error {
opts.BucketRounding = &dur
return nil
})
return tso
}
// CreateCollectionOptions represents arguments that can be used to configure a
// CreateCollection operation.
//
// See corresponding setter methods for documentation.
type CreateCollectionOptions struct {
Capped *bool
Collation *Collation
ChangeStreamPreAndPostImages any
DefaultIndexOptions *DefaultIndexOptionsBuilder
MaxDocuments *int64
SizeInBytes *int64
StorageEngine any
ValidationAction *string
ValidationLevel *string
Validator any
ExpireAfterSeconds *int64
TimeSeriesOptions *TimeSeriesOptionsBuilder
EncryptedFields any
ClusteredIndex any
}
// CreateCollectionOptionsBuilder contains options to configure a new
// collection. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type CreateCollectionOptionsBuilder struct {
Opts []func(*CreateCollectionOptions) error
}
// CreateCollection creates a new CreateCollectionOptions instance.
func CreateCollection() *CreateCollectionOptionsBuilder {
return &CreateCollectionOptionsBuilder{}
}
// List returns a list of CreateCollectionOptions setter functions.
func (c *CreateCollectionOptionsBuilder) List() []func(*CreateCollectionOptions) error {
return c.Opts
}
// SetCapped sets the value for the Capped field. Specifies if the collection is capped
// (see https://www.mongodb.com/docs/manual/core/capped-collections/). If true, the SizeInBytes
// option must also be specified. The default value is false.
func (c *CreateCollectionOptionsBuilder) SetCapped(capped bool) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.Capped = &capped
return nil
})
return c
}
// SetCollation sets the value for the Collation field. Specifies the default
// collation for the new collection. The default value is nil.
func (c *CreateCollectionOptionsBuilder) SetCollation(collation *Collation) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.Collation = collation
return nil
})
return c
}
// SetChangeStreamPreAndPostImages sets the value for the ChangeStreamPreAndPostImages field.
// Specifies how change streams opened against the collection can return pre- and post-images of
// updated documents. The value must be a document in the form {<option name>: <options>}. This
// option is only valid for MongoDB versions >= 6.0. The default value is nil, which means that
// change streams opened against the collection will not return pre- and post-images of updated
// documents in any way.
func (c *CreateCollectionOptionsBuilder) SetChangeStreamPreAndPostImages(csppi any) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.ChangeStreamPreAndPostImages = &csppi
return nil
})
return c
}
// SetDefaultIndexOptions sets the value for the DefaultIndexOptions field.
// Specifies a default configuration for indexes on the collection. The default
// value is nil, meaning indexes will be configured using server defaults.
func (c *CreateCollectionOptionsBuilder) SetDefaultIndexOptions(iopts *DefaultIndexOptionsBuilder) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.DefaultIndexOptions = iopts
return nil
})
return c
}
// SetMaxDocuments sets the value for the MaxDocuments field. Specifies the maximum number of documents
// allowed in a capped collection. The limit specified by the SizeInBytes option takes precedence over
// this option. If a capped collection reaches its size limit, old documents will be removed, regardless
// of the number of documents in the collection. The default value is 0, meaning the maximum number of
// documents is unbounded.
func (c *CreateCollectionOptionsBuilder) SetMaxDocuments(max int64) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.MaxDocuments = &max
return nil
})
return c
}
// SetSizeInBytes sets the value for the SizeInBytes field. Specifies the maximum size in bytes for a
// capped collection. The default value is 0.
func (c *CreateCollectionOptionsBuilder) SetSizeInBytes(size int64) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.SizeInBytes = &size
return nil
})
return c
}
// SetStorageEngine sets the value for the StorageEngine field. Specifies the storage engine to use for
// the index. The value must be a document in the form {<storage engine name>: <options>}. The default
// value is nil, which means that the default storage engine will be used.
func (c *CreateCollectionOptionsBuilder) SetStorageEngine(storageEngine any) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.StorageEngine = &storageEngine
return nil
})
return c
}
// SetValidationAction sets the value for the ValidationAction field. Specifies what should happen if a
// document being inserted does not pass validation. Valid values are "error" and "warn". See
// https://www.mongodb.com/docs/manual/core/schema-validation/#accept-or-reject-invalid-documents for more
// information. The default value is "error".
func (c *CreateCollectionOptionsBuilder) SetValidationAction(action string) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.ValidationAction = &action
return nil
})
return c
}
// SetValidationLevel sets the value for the ValidationLevel field. Specifies how strictly the server applies
// validation rules to existing documents in the collection during update operations. Valid values are "off",
// "strict", and "moderate". See https://www.mongodb.com/docs/manual/core/schema-validation/#existing-documents
// for more information. The default value is "strict".
func (c *CreateCollectionOptionsBuilder) SetValidationLevel(level string) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.ValidationLevel = &level
return nil
})
return c
}
// SetValidator sets the value for the Validator field. Sets a document specifying validation rules for the
// collection. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about
// schema validation. The default value is nil, meaning no validator will be used for the collection.
func (c *CreateCollectionOptionsBuilder) SetValidator(validator any) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.Validator = validator
return nil
})
return c
}
// SetExpireAfterSeconds sets the value for the ExpireAfterSeconds field. Specifies value
// indicating after how many seconds old time-series data should be deleted.
// See https://www.mongodb.com/docs/manual/reference/command/create/ for supported options,
// and https://www.mongodb.com/docs/manual/core/timeseries-collections/ for more information
// on time-series collections.
//
// This option is only valid for MongoDB versions >= 5.0
func (c *CreateCollectionOptionsBuilder) SetExpireAfterSeconds(eas int64) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.ExpireAfterSeconds = &eas
return nil
})
return c
}
// SetTimeSeriesOptions sets the options for time-series collections. Specifies options for specifying
// a time-series collection. See https://www.mongodb.com/docs/manual/reference/command/create/ for
// supported options, and https://www.mongodb.com/docs/manual/core/timeseries-collections/ for more
// information on time-series collections.
//
// This option is only valid for MongoDB versions >= 5.0
func (c *CreateCollectionOptionsBuilder) SetTimeSeriesOptions(timeSeriesOpts *TimeSeriesOptionsBuilder) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.TimeSeriesOptions = timeSeriesOpts
return nil
})
return c
}
// SetEncryptedFields sets the encrypted fields for encrypted collections.
//
// This option is only valid for MongoDB versions >= 6.0
func (c *CreateCollectionOptionsBuilder) SetEncryptedFields(encryptedFields any) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.EncryptedFields = encryptedFields
return nil
})
return c
}
// SetClusteredIndex sets the value for the ClusteredIndex field which is used
// to create a collection with a clustered index.
//
// This option is only valid for MongoDB versions >= 5.3
func (c *CreateCollectionOptionsBuilder) SetClusteredIndex(clusteredIndex any) *CreateCollectionOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateCollectionOptions) error {
opts.ClusteredIndex = clusteredIndex
return nil
})
return c
}
// CreateViewOptions represents arguments that can be used to configure a
// CreateView operation.
//
// See corresponding setter methods for documentation.
type CreateViewOptions struct {
Collation *Collation
}
// CreateViewOptionsBuilder contains options to configure a new view. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type CreateViewOptionsBuilder struct {
Opts []func(*CreateViewOptions) error
}
// CreateView creates an new CreateViewOptions instance.
func CreateView() *CreateViewOptionsBuilder {
return &CreateViewOptionsBuilder{}
}
// List returns a list of TimeSeriesOptions setter functions.
func (c *CreateViewOptionsBuilder) List() []func(*CreateViewOptions) error {
return c.Opts
}
// SetCollation sets the value for the Collation field. Specifies the default
// collation for the new collection. The default value is nil.
func (c *CreateViewOptionsBuilder) SetCollation(collation *Collation) *CreateViewOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateViewOptions) error {
opts.Collation = collation
return nil
})
return c
}

View File

@@ -0,0 +1,107 @@
// 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 options
// DataKeyOptions represents all possible options used to create a new data key.
//
// See corresponding setter methods for documentation.
type DataKeyOptions struct {
MasterKey any
KeyAltNames []string
KeyMaterial []byte
}
// DataKeyOptionsBuilder contains options to configure DataKey operations. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type DataKeyOptionsBuilder struct {
Opts []func(*DataKeyOptions) error
}
// DataKey creates a new DataKeyOptions instance.
func DataKey() *DataKeyOptionsBuilder {
return &DataKeyOptionsBuilder{}
}
// List returns a list of DataKey setter functions.
func (dk *DataKeyOptionsBuilder) List() []func(*DataKeyOptions) error {
return dk.Opts
}
// SetMasterKey specifies a KMS-specific key used to encrypt the new data key.
//
// If being used with a local KMS provider, this option is not applicable and should not be specified.
//
// For the AWS, Azure, and GCP KMS providers, this option is required and must be a document. For each, the value of the
// "endpoint" or "keyVaultEndpoint" must be a host name with an optional port number (e.g. "foo.com" or "foo.com:443").
//
// When using AWS, the document must have the format:
//
// {
// region: <string>,
// key: <string>, // The Amazon Resource Name (ARN) to the AWS customer master key (CMK).
// endpoint: Optional<string> // An alternate host identifier to send KMS requests to.
// }
//
// If unset, the "endpoint" defaults to "kms.<region>.amazonaws.com".
//
// When using Azure, the document must have the format:
//
// {
// keyVaultEndpoint: <string>, // A host identifier to send KMS requests to.
// keyName: <string>,
// keyVersion: Optional<string> // A specific version of the named key.
// }
//
// If unset, "keyVersion" defaults to the key's primary version.
//
// When using GCP, the document must have the format:
//
// {
// projectId: <string>,
// location: <string>,
// keyRing: <string>,
// keyName: <string>,
// keyVersion: Optional<string>, // A specific version of the named key.
// endpoint: Optional<string> // An alternate host identifier to send KMS requests to.
// }
//
// If unset, "keyVersion" defaults to the key's primary version and "endpoint" defaults to "cloudkms.googleapis.com".
func (dk *DataKeyOptionsBuilder) SetMasterKey(masterKey any) *DataKeyOptionsBuilder {
dk.Opts = append(dk.Opts, func(opts *DataKeyOptions) error {
opts.MasterKey = masterKey
return nil
})
return dk
}
// SetKeyAltNames specifies an optional list of string alternate names used to reference a key. If a key is created'
// with alternate names, encryption may refer to the key by a unique alternate name instead of by _id.
func (dk *DataKeyOptionsBuilder) SetKeyAltNames(keyAltNames []string) *DataKeyOptionsBuilder {
dk.Opts = append(dk.Opts, func(opts *DataKeyOptions) error {
opts.KeyAltNames = keyAltNames
return nil
})
return dk
}
// SetKeyMaterial will set a custom keyMaterial to DataKeyOptions which can be used to encrypt data. If omitted,
// keyMaterial is generated form a cryptographically secure random source. "Key Material" is used interchangeably
// with "dataKey" and "Data Encryption Key" (DEK).
func (dk *DataKeyOptionsBuilder) SetKeyMaterial(keyMaterial []byte) *DataKeyOptionsBuilder {
dk.Opts = append(dk.Opts, func(opts *DataKeyOptions) error {
opts.KeyMaterial = keyMaterial
return nil
})
return dk
}

View File

@@ -0,0 +1,106 @@
// 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 options
import (
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo/readconcern"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
)
// DatabaseOptions represents arguments that can be used to configure a
// database.
//
// See corresponding setter methods for documentation.
type DatabaseOptions struct {
ReadConcern *readconcern.ReadConcern
WriteConcern *writeconcern.WriteConcern
ReadPreference *readpref.ReadPref
BSONOptions *BSONOptions
Registry *bson.Registry
}
// DatabaseOptionsBuilder contains options to configure a database object. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type DatabaseOptionsBuilder struct {
Opts []func(*DatabaseOptions) error
}
// Database creates a new DatabaseOptions instance.
func Database() *DatabaseOptionsBuilder {
return &DatabaseOptionsBuilder{}
}
// List returns a list of DatabaseOptions setter functions.
func (d *DatabaseOptionsBuilder) List() []func(*DatabaseOptions) error {
return d.Opts
}
// SetReadConcern sets the value for the ReadConcern field. ReadConcern is the read concern
// to use for operations executed on the Database. The default value is nil, which means that
// the read concern of the Client used to configure the Database will be used.
func (d *DatabaseOptionsBuilder) SetReadConcern(rc *readconcern.ReadConcern) *DatabaseOptionsBuilder {
d.Opts = append(d.Opts, func(opts *DatabaseOptions) error {
opts.ReadConcern = rc
return nil
})
return d
}
// SetWriteConcern sets the value for the WriteConcern field. WriteConcern is the write concern
// to use for operations executed on the Database. The default value is nil, which means that
// the write concern of the Client used to configure the Database will be used.
func (d *DatabaseOptionsBuilder) SetWriteConcern(wc *writeconcern.WriteConcern) *DatabaseOptionsBuilder {
d.Opts = append(d.Opts, func(opts *DatabaseOptions) error {
opts.WriteConcern = wc
return nil
})
return d
}
// SetReadPreference sets the value for the ReadPreference field. ReadPreference is the read
// preference to use for operations executed on the Database. The default value is nil, which
// means that the read preference of the Client used to configure the Database will be used.
func (d *DatabaseOptionsBuilder) SetReadPreference(rp *readpref.ReadPref) *DatabaseOptionsBuilder {
d.Opts = append(d.Opts, func(opts *DatabaseOptions) error {
opts.ReadPreference = rp
return nil
})
return d
}
// SetBSONOptions configures optional BSON marshaling and unmarshaling behavior. BSONOptions
// configures optional BSON marshaling and unmarshaling behavior.
func (d *DatabaseOptionsBuilder) SetBSONOptions(bopts *BSONOptions) *DatabaseOptionsBuilder {
d.Opts = append(d.Opts, func(opts *DatabaseOptions) error {
opts.BSONOptions = bopts
return nil
})
return d
}
// SetRegistry sets the value for the Registry field. Registry is the BSON registry to marshal and
// unmarshal documents for operations executed on the Database. The default value is nil, which
// means that the registry of the Client used to configure the Database will be used.
func (d *DatabaseOptionsBuilder) SetRegistry(r *bson.Registry) *DatabaseOptionsBuilder {
d.Opts = append(d.Opts, func(opts *DatabaseOptions) error {
opts.Registry = r
return nil
})
return d
}

View File

@@ -0,0 +1,191 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// DeleteOneOptions represents arguments that can be used to configure DeleteOne
// operations.
//
// See corresponding setter methods for documentation.
type DeleteOneOptions struct {
Collation *Collation
Comment any
Hint any
Let any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// DeleteOneOptionsBuilder contains options to configure DeleteOne operations. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type DeleteOneOptionsBuilder struct {
Opts []func(*DeleteOneOptions) error
}
// DeleteOne creates a new DeleteOneOptions instance.
func DeleteOne() *DeleteOneOptionsBuilder {
return &DeleteOneOptionsBuilder{}
}
// List returns a list of DeleteOneOptions setter functions.
func (do *DeleteOneOptionsBuilder) List() []func(*DeleteOneOptions) error {
return do.Opts
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (do *DeleteOneOptionsBuilder) SetCollation(c *Collation) *DeleteOneOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DeleteOneOptions) error {
opts.Collation = c
return nil
})
return do
}
// SetComment sets the value for the Comment field. Specifies a string or document that will
// be included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default value is nil, which means that no comment will be included in the logs.
func (do *DeleteOneOptionsBuilder) SetComment(comment any) *DeleteOneOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DeleteOneOptions) error {
opts.Comment = comment
return nil
})
return do
}
// SetHint sets the value for the Hint field. Specifies the index to use for the
// operation. This should either be the index name as a string or the index
// specification as a document. This option is only valid for MongoDB versions
// >= 4.4. Server versions < 4.4 will return an error if this option is
// specified. The driver will return an error if this option is specified during
// an unacknowledged write operation. The driver will return an error if the
// hint parameter is a multi-key map. The default value is nil, which means that
// no hint will be sent.
func (do *DeleteOneOptionsBuilder) SetHint(hint any) *DeleteOneOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DeleteOneOptions) error {
opts.Hint = hint
return nil
})
return do
}
// SetLet sets the value for the Let field. Specifies parameters for the delete expression. This
// option is only valid for MongoDB versions >= 5.0. Older servers will report an error for using
// this option. This must be a document mapping parameter names to values. Values must be constant
// or closed expressions that do not reference document fields. Parameters can then be accessed as
// variables in an aggregate expression context (e.g. "$$var").
func (do *DeleteOneOptionsBuilder) SetLet(let any) *DeleteOneOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DeleteOneOptions) error {
opts.Let = let
return nil
})
return do
}
// DeleteManyOptions represents arguments that can be used to configure DeleteMany
// operations.
//
// See corresponding setter methods for documentation.
type DeleteManyOptions struct {
Collation *Collation
Comment any
Hint any
Let any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// DeleteManyOptionsBuilder contains options to configure DeleteMany operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type DeleteManyOptionsBuilder struct {
Opts []func(*DeleteManyOptions) error
}
// DeleteMany creates a new DeleteManyOptions instance.
func DeleteMany() *DeleteManyOptionsBuilder {
return &DeleteManyOptionsBuilder{}
}
// List returns a list of DeleteOneOptions setter functions.
func (do *DeleteManyOptionsBuilder) List() []func(*DeleteManyOptions) error {
return do.Opts
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (do *DeleteManyOptionsBuilder) SetCollation(c *Collation) *DeleteManyOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DeleteManyOptions) error {
opts.Collation = c
return nil
})
return do
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be
// included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default value is nil, which means that no comment will be included in the logs.
func (do *DeleteManyOptionsBuilder) SetComment(comment any) *DeleteManyOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DeleteManyOptions) error {
opts.Comment = comment
return nil
})
return do
}
// SetHint sets the value for the Hint field. Specifies the index to use for the
// operation. This should either be the index name as a string or the index
// specification as a document. This option is only valid for MongoDB versions
// >= 4.4. Server versions < 4.4 will return an error if this option is
// specified. The driver will return an error if this option is specified during
// an unacknowledged write operation. The driver will return an error if the
// hint parameter is a multi-key map. The default value is nil, which means that
// no hint will be sent.
func (do *DeleteManyOptionsBuilder) SetHint(hint any) *DeleteManyOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DeleteManyOptions) error {
opts.Hint = hint
return nil
})
return do
}
// SetLet sets the value for the Let field. Specifies parameters for the delete expression.
// This option is only valid for MongoDB versions >= 5.0. Older servers will report an error
// for using this option. This must be a document mapping parameter names to values. Values
// must be constant or closed expressions that do not reference document fields. Parameters
// can then be accessed as variables in an aggregate expression context (e.g. "$$var").
func (do *DeleteManyOptionsBuilder) SetLet(let any) *DeleteManyOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DeleteManyOptions) error {
opts.Let = let
return nil
})
return do
}

View File

@@ -0,0 +1,85 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// DistinctOptions represents arguments that can be used to configure a Distinct
// operation.
//
// See corresponding setter methods for documentation.
type DistinctOptions struct {
Collation *Collation
Comment any
Hint any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// DistinctOptionsBuilder contains options to configure distinct operations. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type DistinctOptionsBuilder struct {
Opts []func(*DistinctOptions) error
}
// Distinct creates a new DistinctOptions instance.
func Distinct() *DistinctOptionsBuilder {
return &DistinctOptionsBuilder{}
}
// List returns a list of DistinctArg setter functions.
func (do *DistinctOptionsBuilder) List() []func(*DistinctOptions) error {
return do.Opts
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (do *DistinctOptionsBuilder) SetCollation(c *Collation) *DistinctOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DistinctOptions) error {
opts.Collation = c
return nil
})
return do
}
// SetComment sets the value for the Comment field. Specifies a string or document that
// will be included in server logs, profiling logs, and currentOp queries to help trace
// the operation. The default value is nil, which means that no comment will be included
// in the logs.
func (do *DistinctOptionsBuilder) SetComment(comment any) *DistinctOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DistinctOptions) error {
opts.Comment = comment
return nil
})
return do
}
// SetHint specifies the index to use for the operation. This should either be
// the index name as a string or the index specification as a document. This
// option is only valid for MongoDB versions >= 7.1. Previous server versions
// will return an error if an index hint is specified. Distinct returns an error
// if the hint parameter is a multi-key map. The default value is nil, which
// means that no index hint will be sent.
//
// SetHint sets the Hint field.
func (do *DistinctOptionsBuilder) SetHint(hint any) *DistinctOptionsBuilder {
do.Opts = append(do.Opts, func(opts *DistinctOptions) error {
opts.Hint = hint
return nil
})
return do
}

View File

@@ -0,0 +1,8 @@
// Copyright (C) MongoDB, Inc. 2022-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 options defines the optional configurations for the MongoDB Go Driver.
package options

View File

@@ -0,0 +1,45 @@
// 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 options
// DropCollectionOptions represents arguments that can be used to configure a
// Drop operation.
//
// See corresponding setter methods for documentation.
type DropCollectionOptions struct {
EncryptedFields any
}
// DropCollectionOptionsBuilder contains options to configure collection drop
// operations. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type DropCollectionOptionsBuilder struct {
Opts []func(*DropCollectionOptions) error
}
// DropCollection creates a new DropCollectionOptions instance.
func DropCollection() *DropCollectionOptionsBuilder {
return &DropCollectionOptionsBuilder{}
}
// List returns a list of DropCollectionOptions setter functions.
func (d *DropCollectionOptionsBuilder) List() []func(*DropCollectionOptions) error {
return d.Opts
}
// SetEncryptedFields sets the encrypted fields for encrypted collections.
//
// This option is only valid for MongoDB versions >= 6.0
func (d *DropCollectionOptionsBuilder) SetEncryptedFields(encryptedFields any) *DropCollectionOptionsBuilder {
d.Opts = append(d.Opts, func(opts *DropCollectionOptions) error {
opts.EncryptedFields = encryptedFields
return nil
})
return d
}

View File

@@ -0,0 +1,355 @@
// 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 options
import (
"go.mongodb.org/mongo-driver/v2/bson"
)
// These constants specify valid values for QueryType
// QueryType is used for Queryable Encryption.
const (
QueryTypeEquality string = "equality"
)
// RangeOptions specifies index options for a Queryable Encryption field supporting "range" queries.
//
// See corresponding setter methods for documentation.
type RangeOptions struct {
Min *bson.RawValue
Max *bson.RawValue
Sparsity *int64
TrimFactor *int32
Precision *int32
}
// RangeOptionsBuilder contains options to configure RangeOptions for queryable
// encryption. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type RangeOptionsBuilder struct {
Opts []func(*RangeOptions) error
}
// Range creates a new RangeOptions instance.
func Range() *RangeOptionsBuilder {
return &RangeOptionsBuilder{}
}
// List returns a list of RangeOptions setter functions.
func (ro *RangeOptionsBuilder) List() []func(*RangeOptions) error {
return ro.Opts
}
// SetMin sets the range index minimum value.
func (ro *RangeOptionsBuilder) SetMin(min bson.RawValue) *RangeOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *RangeOptions) error {
opts.Min = &min
return nil
})
return ro
}
// SetMax sets the range index maximum value.
func (ro *RangeOptionsBuilder) SetMax(max bson.RawValue) *RangeOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *RangeOptions) error {
opts.Max = &max
return nil
})
return ro
}
// SetSparsity sets the range index sparsity.
func (ro *RangeOptionsBuilder) SetSparsity(sparsity int64) *RangeOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *RangeOptions) error {
opts.Sparsity = &sparsity
return nil
})
return ro
}
// SetTrimFactor sets the range index trim factor.
func (ro *RangeOptionsBuilder) SetTrimFactor(trimFactor int32) *RangeOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *RangeOptions) error {
opts.TrimFactor = &trimFactor
return nil
})
return ro
}
// SetPrecision sets the range index precision.
func (ro *RangeOptionsBuilder) SetPrecision(precision int32) *RangeOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *RangeOptions) error {
opts.Precision = &precision
return nil
})
return ro
}
// TextOptions specifies index options for a Queryable Encryption field supporting "text" queries.
//
// See corresponding setter methods for documentation.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
type TextOptions struct {
Substring *SubstringOptions
Prefix *PrefixOptions
Suffix *SuffixOptions
CaseSensitive bool
DiacriticSensitive bool
}
// SubstringOptions specifies options to support substring queries.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
type SubstringOptions struct {
StrMaxLength int32
StrMinQueryLength int32
StrMaxQueryLength int32
}
// PrefixOptions specifies options to support prefix queries.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
type PrefixOptions struct {
StrMinQueryLength int32
StrMaxQueryLength int32
}
// SuffixOptions specifies options to support suffix queries.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
type SuffixOptions struct {
StrMinQueryLength int32
StrMaxQueryLength int32
}
// TextOptionsBuilder contains options to configure TextOptions for queryable
// encryption. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
type TextOptionsBuilder struct {
Opts []func(*TextOptions) error
}
// Text creates a new TextOptions instance.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
func Text() *TextOptionsBuilder {
return &TextOptionsBuilder{}
}
// List returns a list of TextOptions setter functions.
func (to *TextOptionsBuilder) List() []func(*TextOptions) error {
return to.Opts
}
// SetSubstring sets the text index substring value.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
func (to *TextOptionsBuilder) SetSubstring(substring SubstringOptions) *TextOptionsBuilder {
to.Opts = append(to.Opts, func(opts *TextOptions) error {
opts.Substring = &substring
return nil
})
return to
}
// SetPrefix sets the text index prefix value.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
func (to *TextOptionsBuilder) SetPrefix(prefix PrefixOptions) *TextOptionsBuilder {
to.Opts = append(to.Opts, func(opts *TextOptions) error {
opts.Prefix = &prefix
return nil
})
return to
}
// SetSuffix sets the text index suffix value.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
func (to *TextOptionsBuilder) SetSuffix(suffix SuffixOptions) *TextOptionsBuilder {
to.Opts = append(to.Opts, func(opts *TextOptions) error {
opts.Suffix = &suffix
return nil
})
return to
}
// SetCaseSensitive sets the text index caseSensitive value.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
func (to *TextOptionsBuilder) SetCaseSensitive(caseSensitive bool) *TextOptionsBuilder {
to.Opts = append(to.Opts, func(opts *TextOptions) error {
opts.CaseSensitive = caseSensitive
return nil
})
return to
}
// SetDiacriticSensitive sets the text index diacriticSensitive value.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
func (to *TextOptionsBuilder) SetDiacriticSensitive(diacriticSensitive bool) *TextOptionsBuilder {
to.Opts = append(to.Opts, func(opts *TextOptions) error {
opts.DiacriticSensitive = diacriticSensitive
return nil
})
return to
}
// EncryptOptions represents arguments to explicitly encrypt a value.
//
// See corresponding setter methods for documentation.
type EncryptOptions struct {
KeyID *bson.Binary
KeyAltName *string
Algorithm string
QueryType string
ContentionFactor *int64
RangeOptions *RangeOptionsBuilder
TextOptions *TextOptionsBuilder
}
// EncryptOptionsBuilder contains options to configure Encryptopts for
// queryeable encryption. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type EncryptOptionsBuilder struct {
Opts []func(*EncryptOptions) error
}
// List returns a list of EncryptOptions setter functions.
func (e *EncryptOptionsBuilder) List() []func(*EncryptOptions) error {
return e.Opts
}
// Encrypt creates a new EncryptOptions instance.
func Encrypt() *EncryptOptionsBuilder {
return &EncryptOptionsBuilder{}
}
// SetKeyID specifies an _id of a data key. This should be a UUID (a bson.Binary with subtype 4).
func (e *EncryptOptionsBuilder) SetKeyID(keyID bson.Binary) *EncryptOptionsBuilder {
e.Opts = append(e.Opts, func(opts *EncryptOptions) error {
opts.KeyID = &keyID
return nil
})
return e
}
// SetKeyAltName identifies a key vault document by 'keyAltName'.
func (e *EncryptOptionsBuilder) SetKeyAltName(keyAltName string) *EncryptOptionsBuilder {
e.Opts = append(e.Opts, func(opts *EncryptOptions) error {
opts.KeyAltName = &keyAltName
return nil
})
return e
}
// SetAlgorithm specifies an algorithm to use for encryption. This should be one of the following:
// - AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
// - AEAD_AES_256_CBC_HMAC_SHA_512-Random
// - Indexed
// - Unindexed
// - Range
// This is required.
// Indexed and Unindexed are used for Queryable Encryption.
func (e *EncryptOptionsBuilder) SetAlgorithm(algorithm string) *EncryptOptionsBuilder {
e.Opts = append(e.Opts, func(opts *EncryptOptions) error {
opts.Algorithm = algorithm
return nil
})
return e
}
// SetQueryType specifies the intended query type. It is only valid to set if algorithm is "Indexed".
// This should be one of the following:
// - equality
// QueryType is used for Queryable Encryption.
func (e *EncryptOptionsBuilder) SetQueryType(queryType string) *EncryptOptionsBuilder {
e.Opts = append(e.Opts, func(opts *EncryptOptions) error {
opts.QueryType = queryType
return nil
})
return e
}
// SetContentionFactor specifies the contention factor. It is only valid to set if algorithm is "Indexed".
// ContentionFactor is used for Queryable Encryption.
func (e *EncryptOptionsBuilder) SetContentionFactor(contentionFactor int64) *EncryptOptionsBuilder {
e.Opts = append(e.Opts, func(opts *EncryptOptions) error {
opts.ContentionFactor = &contentionFactor
return nil
})
return e
}
// SetRangeOptions specifies the options to use for explicit encryption with range. It is only valid to set if algorithm is "range".
func (e *EncryptOptionsBuilder) SetRangeOptions(ro *RangeOptionsBuilder) *EncryptOptionsBuilder {
e.Opts = append(e.Opts, func(opts *EncryptOptions) error {
opts.RangeOptions = ro
return nil
})
return e
}
// SetTextOptions specifies the options to use for text queries.
//
// Beta: This is a preview feature and should only be used for experimental workloads.
// It is not intended for public use. It is subject to breaking changes.
func (e *EncryptOptionsBuilder) SetTextOptions(to *TextOptionsBuilder) *EncryptOptionsBuilder {
e.Opts = append(e.Opts, func(opts *EncryptOptions) error {
opts.TextOptions = to
return nil
})
return e
}

View File

@@ -0,0 +1,52 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// EstimatedDocumentCountOptions represents arguments that can be used to configure
// an EstimatedDocumentCount operation.
//
// See corresponding setter methods for documentation.
type EstimatedDocumentCountOptions struct {
Comment any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// EstimatedDocumentCountOptionsBuilder contains options to estimate document
// count. Each option can be set through setter functions. See documentation for
// each setter function for an explanation of the option.
type EstimatedDocumentCountOptionsBuilder struct {
Opts []func(*EstimatedDocumentCountOptions) error
}
// EstimatedDocumentCount creates a new EstimatedDocumentCountOptions instance.
func EstimatedDocumentCount() *EstimatedDocumentCountOptionsBuilder {
return &EstimatedDocumentCountOptionsBuilder{}
}
// List returns a list of CountOptions setter functions.
func (eco *EstimatedDocumentCountOptionsBuilder) List() []func(*EstimatedDocumentCountOptions) error {
return eco.Opts
}
// SetComment sets the value for the Comment field. Specifies a string or document
// that will be included in server logs, profiling logs, and currentOp queries to help
// trace the operation. The default is nil, which means that no comment will be
// included in the logs.
func (eco *EstimatedDocumentCountOptionsBuilder) SetComment(comment any) *EstimatedDocumentCountOptionsBuilder {
eco.Opts = append(eco.Opts, func(opts *EstimatedDocumentCountOptions) error {
opts.Comment = comment
return nil
})
return eco
}

View File

@@ -0,0 +1,910 @@
// 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 options
import (
"time"
"go.mongodb.org/mongo-driver/v2/internal/optionsutil"
)
// FindOptions represents arguments that can be used to configure a Find
// operation.
//
// See corresponding setter methods for documentation.
type FindOptions struct {
AllowPartialResults *bool
Collation *Collation
Comment any
Hint any
Max any
MaxAwaitTime *time.Duration
Min any
OplogReplay *bool
Projection any
ReturnKey *bool
ShowRecordID *bool
Skip *int64
Sort any
// The above are in common with FindOneopts.
AllowDiskUse *bool
BatchSize *int32
CursorType *CursorType
Let any
Limit *int64
NoCursorTimeout *bool
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// FindOptionsBuilder represents functional options that configure an Findopts.
type FindOptionsBuilder struct {
Opts []func(*FindOptions) error
}
// Find creates a new FindOptions instance.
func Find() *FindOptionsBuilder {
return &FindOptionsBuilder{}
}
// List returns a list of FindOptions setter functions.
func (f *FindOptionsBuilder) List() []func(*FindOptions) error {
return f.Opts
}
// SetAllowDiskUse sets the value for the AllowDiskUse field. AllowDiskUse
// specifies whether the server can write temporary data to disk while executing
// the Find operation. This option is only valid for MongoDB versions >= 4.4.
// Server versions < 4.4 will return an error if this option is specified. The
// default value is false.
func (f *FindOptionsBuilder) SetAllowDiskUse(b bool) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.AllowDiskUse = &b
return nil
})
return f
}
// SetAllowPartialResults sets the value for the AllowPartialResults field. AllowPartial results
// specifies whether the Find operation on a sharded cluster can return partial results if some
// shards are down rather than returning an error. The default value is false.
func (f *FindOptionsBuilder) SetAllowPartialResults(b bool) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.AllowPartialResults = &b
return nil
})
return f
}
// SetBatchSize sets the value for the BatchSize field. BatchSize is the maximum number of documents
// to be included in each batch returned by the server.
func (f *FindOptionsBuilder) SetBatchSize(i int32) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.BatchSize = &i
return nil
})
return f
}
// SetCollation sets the value for the Collation field. Collation specifies a
// collation to use for string comparisons during the operation. The default
// value is nil, which means the default collation of the collection will be
// used.
func (f *FindOptionsBuilder) SetCollation(collation *Collation) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Collation = collation
return nil
})
return f
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be
// included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default is nil, which means that no comment will be included in the logs.
func (f *FindOptionsBuilder) SetComment(comment any) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Comment = &comment
return nil
})
return f
}
// SetCursorType sets the value for the CursorType field. CursorType specifies the type of cursor
// that should be created for the operation. The default is NonTailable, which means that the
// cursor will be closed by the server when the last batch of documents is retrieved.
func (f *FindOptionsBuilder) SetCursorType(ct CursorType) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.CursorType = &ct
return nil
})
return f
}
// SetHint sets the value for the Hint field. Hint is the index to use for the Find operation.
// This should either be the index name as a string or the index specification as a document.
// The driver will return an error if the hint parameter is a multi-key map. The default
// value is nil, which means that no hint will be sent.
func (f *FindOptionsBuilder) SetHint(hint any) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Hint = hint
return nil
})
return f
}
// SetLet sets the value for the Let field. Let specifies parameters for the find expression.
// This option is only valid for MongoDB versions >= 5.0. Older servers will report an error
// for using this option. This must be a document mapping parameter names to values. Values
// must be constant or closed expressions that do not reference document fields. Parameters
// can then be accessed as variables in an aggregate expression context (e.g. "$$var").
func (f *FindOptionsBuilder) SetLet(let any) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Let = let
return nil
})
return f
}
// SetLimit sets the value for the Limit field. Limit is the maximum number of documents to return.
// The default value is 0, which means that all documents matching the filter will be returned.
// A negative limit specifies that the resulting documents should be returned in a single batch.
// The default value is 0.
func (f *FindOptionsBuilder) SetLimit(i int64) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Limit = &i
return nil
})
return f
}
// SetMax sets the value for the Max field. Max is a document specifying the exclusive upper bound
// for a specific index. The default value is nil, which means that there is no maximum value.
func (f *FindOptionsBuilder) SetMax(max any) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Max = max
return nil
})
return f
}
// SetMaxAwaitTime sets the value for the MaxAwaitTime field. MaxAwaitTime is
// the maximum amount of time that the server should wait for new documents to
// satisfy a tailable cursor query. This option is only valid for tailable await
// cursors (see the CursorType option for more information). For other cursor
// types, this option is ignored.
func (f *FindOptionsBuilder) SetMaxAwaitTime(d time.Duration) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.MaxAwaitTime = &d
return nil
})
return f
}
// SetMin sets the value for the Min field. Min is a document specifying the inclusive lower bound
// for a specific index. The default value is 0, which means that there is no minimum value.
func (f *FindOptionsBuilder) SetMin(min any) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Min = min
return nil
})
return f
}
// SetNoCursorTimeout sets the value for the NoCursorTimeout field. NoCursorTimeout specifies
// whether the cursor created by the operation will not timeout after a period of inactivity.
// The default value is false.
func (f *FindOptionsBuilder) SetNoCursorTimeout(b bool) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.NoCursorTimeout = &b
return nil
})
return f
}
// SetOplogReplay sets the value for the OplogReplay field. OplogReplay is for internal
// replication use only and should not be set.
//
// Deprecated: This option has been deprecated in MongoDB version 4.4 and will be ignored by
// the server if it is set.
func (f *FindOptionsBuilder) SetOplogReplay(b bool) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.OplogReplay = &b
return nil
})
return f
}
// SetProjection sets the value for the Projection field. Projection is a document describing
// which fields will be included in the documents returned by the Find operation. The
// default value is nil, which means all fields will be included.
func (f *FindOptionsBuilder) SetProjection(projection any) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Projection = projection
return nil
})
return f
}
// SetReturnKey sets the value for the ReturnKey field. ReturnKey specifies whether the
// documents returned by the Find operation will only contain fields corresponding to the
// index used. The default value is false.
func (f *FindOptionsBuilder) SetReturnKey(b bool) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.ReturnKey = &b
return nil
})
return f
}
// SetShowRecordID sets the value for the ShowRecordID field. ShowRecordID specifies whether
// a $recordId field with a record identifier will be included in the documents returned by
// the Find operation. The default value is false.
func (f *FindOptionsBuilder) SetShowRecordID(b bool) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.ShowRecordID = &b
return nil
})
return f
}
// SetSkip sets the value for the Skip field. Skip is the number of documents to skip before
// adding documents to the result. The default value is 0.
func (f *FindOptionsBuilder) SetSkip(i int64) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Skip = &i
return nil
})
return f
}
// SetSort sets the value for the Sort field. Sort is a document specifying the order in which
// documents should be returned. The sort parameter is evaluated sequentially, so the driver will
// return an error if it is a multi-key map (which is unordeded). The default value is nil.
func (f *FindOptionsBuilder) SetSort(sort any) *FindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOptions) error {
opts.Sort = sort
return nil
})
return f
}
// FindOneOptions represents arguments that can be used to configure a FindOne
// operation.
//
// See corresponding setter methods for documentation.
type FindOneOptions struct {
AllowPartialResults *bool
Collation *Collation
Comment any
Hint any
Max any
Min any
OplogReplay *bool
Projection any
ReturnKey *bool
ShowRecordID *bool
Skip *int64
Sort any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// FindOneOptionsBuilder represents functional options that configure an
// FindOneopts.
type FindOneOptionsBuilder struct {
Opts []func(*FindOneOptions) error
}
// FindOne creates a new FindOneOptions instance.
func FindOne() *FindOneOptionsBuilder {
return &FindOneOptionsBuilder{}
}
// List returns a list of FindOneOptions setter functions.
func (f *FindOneOptionsBuilder) List() []func(*FindOneOptions) error {
return f.Opts
}
// SetAllowPartialResults sets the value for the AllowPartialResults field. If true, an operation
// on a sharded cluster can return partial results if some shards are down rather than returning
// an error. The default value is false.
func (f *FindOneOptionsBuilder) SetAllowPartialResults(b bool) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.AllowPartialResults = &b
return nil
})
return f
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (f *FindOneOptionsBuilder) SetCollation(collation *Collation) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.Collation = collation
return nil
})
return f
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be
// included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default is nil, which means that no comment will be included in the logs.
func (f *FindOneOptionsBuilder) SetComment(comment any) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.Comment = &comment
return nil
})
return f
}
// SetHint sets the value for the Hint field. Specifies the index to use for the aggregation.
// This should either be the index name as a string or the index specification as a document.
// The driver will return an error if the hint parameter is a multi-key map. The default value
// is nil, which means that no hint will be sent.
func (f *FindOneOptionsBuilder) SetHint(hint any) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.Hint = hint
return nil
})
return f
}
// SetMax sets the value for the Max field. Sets a document specifying the exclusive upper bound
// for a specific index. The default value is nil, which means that there is no maximum value.
func (f *FindOneOptionsBuilder) SetMax(max any) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.Max = max
return nil
})
return f
}
// SetMin sets the value for the Min field. Sets a document specifying the inclusive lower bound
// for a specific index. The default value is 0, which means that there is no minimum value.
func (f *FindOneOptionsBuilder) SetMin(min any) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.Min = min
return nil
})
return f
}
// SetOplogReplay sets the value for the OplogReplay field. OplogReplay is for internal
// replication use only and should not be set.
//
// Deprecated: This option has been deprecated in MongoDB version 4.4 and will be ignored by
// the server if it is set.
func (f *FindOneOptionsBuilder) SetOplogReplay(b bool) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.OplogReplay = &b
return nil
})
return f
}
// SetProjection sets the value for the Projection field. Sets a document describing which fields
// will be included in the document returned by the operation. The default value is nil, which
// means all fields will be included.
func (f *FindOneOptionsBuilder) SetProjection(projection any) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.Projection = projection
return nil
})
return f
}
// SetReturnKey sets the value for the ReturnKey field. If true, the document returned by the
// operation will only contain fields corresponding to the index used. The default value
// is false.
func (f *FindOneOptionsBuilder) SetReturnKey(b bool) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.ReturnKey = &b
return nil
})
return f
}
// SetShowRecordID sets the value for the ShowRecordID field. If true, a $recordId field with
// a record identifier will be included in the document returned by the operation. The default
// value is false.
func (f *FindOneOptionsBuilder) SetShowRecordID(b bool) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.ShowRecordID = &b
return nil
})
return f
}
// SetSkip sets the value for the Skip field. Specifies the number of documents to skip before
// selecting the document to be returned. The default value is 0.
func (f *FindOneOptionsBuilder) SetSkip(i int64) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.Skip = &i
return nil
})
return f
}
// SetSort sets the value for the Sort field. Sets a document specifying the sort order to
// apply to the query. The first document in the sorted order will be returned. The sort
// parameter is evaluated sequentially, so the driver will return an error if it is a multi-
// key map (which is unordeded). The default value is nil.
func (f *FindOneOptionsBuilder) SetSort(sort any) *FindOneOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneOptions) error {
opts.Sort = sort
return nil
})
return f
}
// FindOneAndReplaceOptions represents arguments that can be used to configure a
// FindOneAndReplace instance.
//
// See corresponding setter methods for documentation.
type FindOneAndReplaceOptions struct {
BypassDocumentValidation *bool
Collation *Collation
Comment any
Projection any
ReturnDocument *ReturnDocument
Sort any
Upsert *bool
Hint any
Let any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// FindOneAndReplaceOptionsBuilder contains options to perform a findAndModify
// operation. Each option can be set through setter functions. See documentation
// for each setter function for an explanation of the option.
type FindOneAndReplaceOptionsBuilder struct {
Opts []func(*FindOneAndReplaceOptions) error
}
// FindOneAndReplace creates a new FindOneAndReplaceOptions instance.
func FindOneAndReplace() *FindOneAndReplaceOptionsBuilder {
return &FindOneAndReplaceOptionsBuilder{}
}
// List returns a list of FindOneAndReplaceOptions setter functions.
func (f *FindOneAndReplaceOptionsBuilder) List() []func(*FindOneAndReplaceOptions) error {
return f.Opts
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true, writes
// executed as part of the operation will opt out of document-level validation on the server. The
// default value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more
// information about document validation.
func (f *FindOneAndReplaceOptionsBuilder) SetBypassDocumentValidation(b bool) *FindOneAndReplaceOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error {
opts.BypassDocumentValidation = &b
return nil
})
return f
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (f *FindOneAndReplaceOptionsBuilder) SetCollation(collation *Collation) *FindOneAndReplaceOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error {
opts.Collation = collation
return nil
})
return f
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be
// included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default value is nil, which means that no comment will be included in the logs.
func (f *FindOneAndReplaceOptionsBuilder) SetComment(comment any) *FindOneAndReplaceOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error {
opts.Comment = comment
return nil
})
return f
}
// SetProjection sets the value for the Projection field. Sets a document describing which fields
// will be included in the document returned by the operation. The default value is nil, which
// means all fields will be included.
func (f *FindOneAndReplaceOptionsBuilder) SetProjection(projection any) *FindOneAndReplaceOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error {
opts.Projection = projection
return nil
})
return f
}
// SetReturnDocument sets the value for the ReturnDocument field. Specifies whether the original
// or replaced document should be returned by the operation. The default value is Before, which
// means the original document will be returned from before the replacement is performed.
func (f *FindOneAndReplaceOptionsBuilder) SetReturnDocument(rd ReturnDocument) *FindOneAndReplaceOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error {
opts.ReturnDocument = &rd
return nil
})
return f
}
// SetSort sets the value for the Sort field. Sets a document specifying which document should
// be replaced if the filter used by the operation matches multiple documents in the collection.
// If set, the first document in the sorted order will be replaced. The sort parameter is evaluated
// sequentially, so the driver will return an error if it is a multi-key map (which is unordeded).
// The default value is nil.
func (f *FindOneAndReplaceOptionsBuilder) SetSort(sort any) *FindOneAndReplaceOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error {
opts.Sort = sort
return nil
})
return f
}
// SetUpsert sets the value for the Upsert field. If true, a new document will be inserted if
// the filter does not match any documents in the collection. The default value is false.
func (f *FindOneAndReplaceOptionsBuilder) SetUpsert(b bool) *FindOneAndReplaceOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error {
opts.Upsert = &b
return nil
})
return f
}
// SetHint sets the value for the Hint field. Specifies the index to use for the operation.
// This should either be the index name as a string or the index specification as a document.
// This option is only valid for MongoDB versions >= 4.4. MongoDB version 4.2 will report an
// error if this option is specified. For server versions < 4.2, the driver will return an
// error if this option is specified. The driver will return an error if this option is used
// with during an unacknowledged write operation. The driver will return an error if the
// hint parameter is a multi-key map. The default value is nil, which means that no hint
// will be sent.
func (f *FindOneAndReplaceOptionsBuilder) SetHint(hint any) *FindOneAndReplaceOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error {
opts.Hint = hint
return nil
})
return f
}
// SetLet sets the value for the Let field. Specifies parameters for the find one and
// replace expression. This option is only valid for MongoDB versions >= 5.0. Older
// servers will report an error for using this option. This must be a document mapping
// parameter names to values. Values must be constant or closed expressions that do not
// reference document fields. Parameters can then be accessed as variables in an
// aggregate expression context (e.g. "$$var").
func (f *FindOneAndReplaceOptionsBuilder) SetLet(let any) *FindOneAndReplaceOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndReplaceOptions) error {
opts.Let = let
return nil
})
return f
}
// FindOneAndUpdateOptions represents arguments that can be used to configure a
// FindOneAndUpdate options.
//
// See corresponding setter methods for documentation.
type FindOneAndUpdateOptions struct {
ArrayFilters []any
BypassDocumentValidation *bool
Collation *Collation
Comment any
Projection any
ReturnDocument *ReturnDocument
Sort any
Upsert *bool
Hint any
Let any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// FindOneAndUpdateOptionsBuilder contains options to configure a
// findOneAndUpdate operation. Each option can be set through setter functions.
// See documentation for each setter function for an explanation of the option.
type FindOneAndUpdateOptionsBuilder struct {
Opts []func(*FindOneAndUpdateOptions) error
}
// FindOneAndUpdate creates a new FindOneAndUpdateOptions instance.
func FindOneAndUpdate() *FindOneAndUpdateOptionsBuilder {
return &FindOneAndUpdateOptionsBuilder{}
}
// List returns a list of FindOneAndUpdateOptions setter functions.
func (f *FindOneAndUpdateOptionsBuilder) List() []func(*FindOneAndUpdateOptions) error {
return f.Opts
}
// SetArrayFilters sets the value for the ArrayFilters field. ArrayFilters is a
// set of filters specifying to which array elements an update should apply. The
// default value is nil, which means the update will apply to all array
// elements.
func (f *FindOneAndUpdateOptionsBuilder) SetArrayFilters(filters []any) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.ArrayFilters = filters
return nil
})
return f
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true,
// writes executed as part of the operation will opt out of document-level validation on the server.
// The default value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/
// for more information about document validation.
func (f *FindOneAndUpdateOptionsBuilder) SetBypassDocumentValidation(b bool) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.BypassDocumentValidation = &b
return nil
})
return f
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (f *FindOneAndUpdateOptionsBuilder) SetCollation(collation *Collation) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.Collation = collation
return nil
})
return f
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be
// included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default value is nil, which means that no comment will be included in the logs.
func (f *FindOneAndUpdateOptionsBuilder) SetComment(comment any) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.Comment = comment
return nil
})
return f
}
// SetProjection sets the value for the Projection field. Sets a document describing which fields
// will be included in the document returned by the operation. The default value is nil, which
// means all fields will be included.
func (f *FindOneAndUpdateOptionsBuilder) SetProjection(projection any) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.Projection = projection
return nil
})
return f
}
// SetReturnDocument sets the value for the ReturnDocument field. Specifies whether the original
// or replaced document should be returned by the operation. The default value is Before, which
// means the original document will be returned before the replacement is performed.
func (f *FindOneAndUpdateOptionsBuilder) SetReturnDocument(rd ReturnDocument) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.ReturnDocument = &rd
return nil
})
return f
}
// SetSort sets the value for the Sort field. Sets a document specifying which document should
// be updated if the filter used by the operation matches multiple documents in the collection.
// If set, the first document in the sorted order will be updated. The sort parameter is evaluated
// sequentially, so the driver will return an error if it is a multi-key map (which is unordeded).
// The default value is nil.
func (f *FindOneAndUpdateOptionsBuilder) SetSort(sort any) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.Sort = sort
return nil
})
return f
}
// SetUpsert sets the value for the Upsert field. If true, a new document will be inserted if
// the filter does not match any documents in the collection. The default value is false.
func (f *FindOneAndUpdateOptionsBuilder) SetUpsert(b bool) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.Upsert = &b
return nil
})
return f
}
// SetHint sets the value for the Hint field. Specifies the index to use for the operation.
// This should either be the index name as a string or the index specification as a document.
// This option is only valid for MongoDB versions >= 4.4. MongoDB version 4.2 will report an
// error if this option is specified. For server versions < 4.2, the driver will return an
// error if this option is specified. The driver will return an error if this option is used
// with during an unacknowledged write operation. The driver will return an error if the
// hint parameter is a multi-key map. The default value is nil, which means that no hint
// will be sent.
func (f *FindOneAndUpdateOptionsBuilder) SetHint(hint any) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.Hint = hint
return nil
})
return f
}
// SetLet sets the value for the Let field. Specifies parameters for the find one and update
// expression. This option is only valid for MongoDB versions >= 5.0. Older servers will
// report an error for using this option. This must be a document mapping parameter names
// to values. Values must be constant or closed expressions that do not reference document
// fields. Parameters can then be accessed as variables in an aggregate expression context
// (e.g. "$$var").
func (f *FindOneAndUpdateOptionsBuilder) SetLet(let any) *FindOneAndUpdateOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndUpdateOptions) error {
opts.Let = let
return nil
})
return f
}
// FindOneAndDeleteOptions represents arguments that can be used to configure a
// FindOneAndDelete operation.
//
// See corresponding setter methods for documentation.
type FindOneAndDeleteOptions struct {
Collation *Collation
Comment any
Projection any
Sort any
Hint any
Let any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// FindOneAndDeleteOptionsBuilder contains options to configure delete
// operations. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type FindOneAndDeleteOptionsBuilder struct {
Opts []func(*FindOneAndDeleteOptions) error
}
// FindOneAndDelete creates a new FindOneAndDeleteOptions instance.
func FindOneAndDelete() *FindOneAndDeleteOptionsBuilder {
return &FindOneAndDeleteOptionsBuilder{}
}
// List returns a list of FindOneAndDeleteOptions setter functions.
func (f *FindOneAndDeleteOptionsBuilder) List() []func(*FindOneAndDeleteOptions) error {
return f.Opts
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (f *FindOneAndDeleteOptionsBuilder) SetCollation(collation *Collation) *FindOneAndDeleteOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndDeleteOptions) error {
opts.Collation = collation
return nil
})
return f
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be
// included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default value is nil, which means that no comment will be included in the logs.
func (f *FindOneAndDeleteOptionsBuilder) SetComment(comment any) *FindOneAndDeleteOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndDeleteOptions) error {
opts.Comment = comment
return nil
})
return f
}
// SetProjection sets the value for the Projection field. Sets a document describing which fields
// will be included in the document returned by the operation. The default value is nil, which
// means all fields will be included.
func (f *FindOneAndDeleteOptionsBuilder) SetProjection(projection any) *FindOneAndDeleteOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndDeleteOptions) error {
opts.Projection = projection
return nil
})
return f
}
// SetSort sets the value for the Sort field. Sets a document specifying which document should
// be replaced if the filter used by the operation matches multiple documents in the collection.
// If set, the first document in the sorted order will be deleted. The sort parameter is evaluated
// sequentially, so the driver will return an error if it is a multi-key map (which is unordeded).
// The default value is nil.
func (f *FindOneAndDeleteOptionsBuilder) SetSort(sort any) *FindOneAndDeleteOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndDeleteOptions) error {
opts.Sort = sort
return nil
})
return f
}
// SetHint sets the value for the Hint field. Specifies the index to use for the operation.
// This should either be the index name as a string or the index specification as a document.
// This option is only valid for MongoDB versions >= 4.4. MongoDB version 4.2 will report an
// error if this option is specified. For server versions < 4.2, the driver will return an
// error if this option is specified. The driver will return an error if this option is used
// with during an unacknowledged write operation. The driver will return an error if the hint
// parameter is a multi-key map. The default value is nil, which means that no hint will be sent.
func (f *FindOneAndDeleteOptionsBuilder) SetHint(hint any) *FindOneAndDeleteOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndDeleteOptions) error {
opts.Hint = hint
return nil
})
return f
}
// SetLet sets the value for the Let field. Specifies parameters for the find one and delete
// expression. This option is only valid for MongoDB versions >= 5.0. Older servers will
// report an error for using this option. This must be a document mapping parameter names to
// values. Values must be constant or closed expressions that do not reference document fields.
// Parameters can then be accessed as variables in an aggregate expression context (e.g. "$$var").
func (f *FindOneAndDeleteOptionsBuilder) SetLet(let any) *FindOneAndDeleteOptionsBuilder {
f.Opts = append(f.Opts, func(opts *FindOneAndDeleteOptions) error {
opts.Let = let
return nil
})
return f
}

View File

@@ -0,0 +1,342 @@
// 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 options
import (
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo/readconcern"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
)
// DefaultName is the default name for a GridFS bucket.
var DefaultName = "fs"
// DefaultChunkSize is the default size of each file chunk in bytes (255 KiB).
var DefaultChunkSize int32 = 255 * 1024
// DefaultRevision is the default revision number for a download by name operation.
var DefaultRevision int32 = -1
// BucketOptions represents arguments that can be used to configure GridFS
// bucket.
//
// See corresponding setter methods for documentation.
type BucketOptions struct {
Name *string
ChunkSizeBytes *int32
WriteConcern *writeconcern.WriteConcern
ReadConcern *readconcern.ReadConcern
ReadPreference *readpref.ReadPref
}
// BucketOptionsBuilder contains options to configure a gridfs bucket. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type BucketOptionsBuilder struct {
Opts []func(*BucketOptions) error
}
// GridFSBucket creates a new BucketOptions instance.
func GridFSBucket() *BucketOptionsBuilder {
bo := &BucketOptionsBuilder{}
bo.SetName(DefaultName).SetChunkSizeBytes(DefaultChunkSize)
return bo
}
// List returns a list of CountOptions setter functions.
func (b *BucketOptionsBuilder) List() []func(*BucketOptions) error {
return b.Opts
}
// SetName sets the value for the Name field. Specifies the name of the bucket.
// The default value is "fs".
func (b *BucketOptionsBuilder) SetName(name string) *BucketOptionsBuilder {
b.Opts = append(b.Opts, func(opts *BucketOptions) error {
opts.Name = &name
return nil
})
return b
}
// SetChunkSizeBytes sets the value for the ChunkSize field. Specifies the number
// of bytes in each chunk in the bucket. The default value is 255 KiB.
func (b *BucketOptionsBuilder) SetChunkSizeBytes(i int32) *BucketOptionsBuilder {
b.Opts = append(b.Opts, func(opts *BucketOptions) error {
opts.ChunkSizeBytes = &i
return nil
})
return b
}
// SetWriteConcern sets the value for the WriteConcern field. Specifies the write
// concern for the bucket. The default value is the write concern of the database
// from which the bucket is created.
func (b *BucketOptionsBuilder) SetWriteConcern(wc *writeconcern.WriteConcern) *BucketOptionsBuilder {
b.Opts = append(b.Opts, func(opts *BucketOptions) error {
opts.WriteConcern = wc
return nil
})
return b
}
// SetReadConcern sets the value for the ReadConcern field. Specifies the read
// concern for the bucket. The default value is the read concern of the database
// from which the bucket is created.
func (b *BucketOptionsBuilder) SetReadConcern(rc *readconcern.ReadConcern) *BucketOptionsBuilder {
b.Opts = append(b.Opts, func(opts *BucketOptions) error {
opts.ReadConcern = rc
return nil
})
return b
}
// SetReadPreference sets the value for the ReadPreference field. Specifies the
// read preference for the bucket. The default value is the read preference of
// the database from which the bucket is created.
func (b *BucketOptionsBuilder) SetReadPreference(rp *readpref.ReadPref) *BucketOptionsBuilder {
b.Opts = append(b.Opts, func(opts *BucketOptions) error {
opts.ReadPreference = rp
return nil
})
return b
}
// GridFSUploadOptions represents arguments that can be used to configure a GridFS
// upload operation.
//
// See corresponding setter methods for documentation.
type GridFSUploadOptions struct {
ChunkSizeBytes *int32
Metadata any
Registry *bson.Registry
}
// GridFSUploadOptionsBuilder contains options to configure a GridFS Upload.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type GridFSUploadOptionsBuilder struct {
Opts []func(*GridFSUploadOptions) error
}
// GridFSUpload creates a new GridFSUploadOptions instance.
func GridFSUpload() *GridFSUploadOptionsBuilder {
opts := &GridFSUploadOptionsBuilder{}
opts.SetRegistry(defaultRegistry)
return opts
}
// List returns a list of GridFSUploadOptions setter functions.
func (u *GridFSUploadOptionsBuilder) List() []func(*GridFSUploadOptions) error {
return u.Opts
}
// SetChunkSizeBytes sets the value for the ChunkSize field. Specifies the number of
// bytes in each chunk in the bucket. The default value is DefaultChunkSize (255 KiB).
func (u *GridFSUploadOptionsBuilder) SetChunkSizeBytes(i int32) *GridFSUploadOptionsBuilder {
u.Opts = append(u.Opts, func(opts *GridFSUploadOptions) error {
opts.ChunkSizeBytes = &i
return nil
})
return u
}
// SetMetadata sets the value for the Metadata field. Specifies additional application data
// that will be stored in the "metadata" field of the document in the files collection.
// The default value is nil, which means that the document in the files collection will
// not contain a "metadata" field.
func (u *GridFSUploadOptionsBuilder) SetMetadata(doc any) *GridFSUploadOptionsBuilder {
u.Opts = append(u.Opts, func(opts *GridFSUploadOptions) error {
opts.Metadata = doc
return nil
})
return u
}
// SetRegistry sets the bson codec registry for the Registry field. Specifies the BSON
// registry to use for converting filters to BSON documents. The default value is
// bson.NewRegistry().
func (u *GridFSUploadOptionsBuilder) SetRegistry(registry *bson.Registry) *GridFSUploadOptionsBuilder {
u.Opts = append(u.Opts, func(opts *GridFSUploadOptions) error {
opts.Registry = registry
return nil
})
return u
}
// GridFSNameOptions represents arguments that can be used to configure a GridFS
// DownloadByName operation.
//
// See corresponding setter methods for documentation.
type GridFSNameOptions struct {
Revision *int32
}
// GridFSNameOptionsBuilder contains options to configure a GridFS name. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type GridFSNameOptionsBuilder struct {
Opts []func(*GridFSNameOptions) error
}
// GridFSName creates a new GridFSNameOptions instance.
func GridFSName() *GridFSNameOptionsBuilder {
return &GridFSNameOptionsBuilder{}
}
// List returns a list of GridFSNameOptions setter functions.
func (n *GridFSNameOptionsBuilder) List() []func(*GridFSNameOptions) error {
return n.Opts
}
// SetRevision sets the value for the Revision field. Specifies the revision
// of the file to retrieve. Revision numbers are defined as follows:
//
// * 0 = the original stored file
// * 1 = the first revision
// * 2 = the second revision
// * etc..
// * -2 = the second most recent revision
// * -1 = the most recent revision.
//
// The default value is -1
func (n *GridFSNameOptionsBuilder) SetRevision(r int32) *GridFSNameOptionsBuilder {
n.Opts = append(n.Opts, func(opts *GridFSNameOptions) error {
opts.Revision = &r
return nil
})
return n
}
// GridFSFindOptions represents arguments that can be used to configure a GridFS
// Find operation.
//
// See corresponding setter methods for documentation.
type GridFSFindOptions struct {
AllowDiskUse *bool
BatchSize *int32
Limit *int32
NoCursorTimeout *bool
Skip *int32
Sort any
}
// GridFSFindOptionsBuilder contains options to configure find operations. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type GridFSFindOptionsBuilder struct {
Opts []func(*GridFSFindOptions) error
}
// GridFSFind creates a new GridFSFindOptions instance.
func GridFSFind() *GridFSFindOptionsBuilder {
return &GridFSFindOptionsBuilder{}
}
// List returns a list of GridFSFindOptions setter functions.
func (f *GridFSFindOptionsBuilder) List() []func(*GridFSFindOptions) error {
return f.Opts
}
// SetAllowDiskUse sets the value for the AllowDiskUse field. If true, the server can
// write temporary data to disk while executing the find operation. The default value
// is false. This option is only valid for MongoDB versions >= 4.4. For previous server
// versions, the server will return an error if this option is used.
func (f *GridFSFindOptionsBuilder) SetAllowDiskUse(b bool) *GridFSFindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *GridFSFindOptions) error {
opts.AllowDiskUse = &b
return nil
})
return f
}
// SetBatchSize sets the value for the BatchSize field. Specifies the maximum number
// of documents to be included in each batch returned by the server.
func (f *GridFSFindOptionsBuilder) SetBatchSize(i int32) *GridFSFindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *GridFSFindOptions) error {
opts.BatchSize = &i
return nil
})
return f
}
// SetLimit sets the value for the Limit field. Specifies the maximum number of
// documents to return. The default value is 0, which means that all documents
// matching the filter will be returned. A negative limit specifies that the
// resulting documents should be returned in a single batch. The default value is 0.
func (f *GridFSFindOptionsBuilder) SetLimit(i int32) *GridFSFindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *GridFSFindOptions) error {
opts.Limit = &i
return nil
})
return f
}
// SetNoCursorTimeout sets the value for the NoCursorTimeout field. If true, the
// cursor created by the operation will not timeout after a period of inactivity.
// The default value is false.
func (f *GridFSFindOptionsBuilder) SetNoCursorTimeout(b bool) *GridFSFindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *GridFSFindOptions) error {
opts.NoCursorTimeout = &b
return nil
})
return f
}
// SetSkip sets the value for the Skip field. Specifies the number of documents
// to skip before adding documents to the result. The default value is 0.
func (f *GridFSFindOptionsBuilder) SetSkip(i int32) *GridFSFindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *GridFSFindOptions) error {
opts.Skip = &i
return nil
})
return f
}
// SetSort sets the value for the Sort field. Sets a document specifying the order
// in which documents should be returned. The sort parameter is evaluated sequentially,
// so the driver will return an error if it is a multi-key map (which is unordeded).
// The default value is nil.
func (f *GridFSFindOptionsBuilder) SetSort(sort any) *GridFSFindOptionsBuilder {
f.Opts = append(f.Opts, func(opts *GridFSFindOptions) error {
opts.Sort = sort
return nil
})
return f
}

View File

@@ -0,0 +1,486 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// CreateIndexesOptions represents arguments that can be used to configure
// IndexView.CreateOne and IndexView.CreateMany operations.
//
// See corresponding setter methods for documentation.
type CreateIndexesOptions struct {
CommitQuorum any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// CreateIndexesOptionsBuilder contains options to create indexes. Each option
// can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
//
// See corresponding setter methods for documentation.
type CreateIndexesOptionsBuilder struct {
Opts []func(*CreateIndexesOptions) error
}
// CreateIndexes creates a new CreateIndexesOptions instance.
func CreateIndexes() *CreateIndexesOptionsBuilder {
return &CreateIndexesOptionsBuilder{}
}
// List returns a list of CreateIndexesOptions setter functions.
func (c *CreateIndexesOptionsBuilder) List() []func(*CreateIndexesOptions) error {
return c.Opts
}
// SetCommitQuorumInt sets the value for the CommitQuorum field as an int32.
// Specifies the number of data-bearing members of a replica set, including the primary,
// that must complete the index builds successfully before the primary marks the indexes
// as ready.
//
// Semantics for int: the number of members that must complete the build.
//
// This option is only available on MongoDB versions >= 4.4. A client-side error will
// be returned if the option is specified for MongoDB versions <= 4.2. The default
// value is nil, meaning that the server-side default will be used. See
// dochub.mongodb.org/core/index-commit-quorum for more information.
func (c *CreateIndexesOptionsBuilder) SetCommitQuorumInt(quorum int32) *CreateIndexesOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateIndexesOptions) error {
opts.CommitQuorum = quorum
return nil
})
return c
}
// SetCommitQuorumString sets the value for the CommitQuorum field as a string.
// Specifies the number of data-bearing members of a replica set, including the primary,
// that must complete the index builds successfully before the primary marks the indexes
// as ready.
//
// Semantics for String: specifies a tag. All members with that tag must complete the build.
//
// This option is only available on MongoDB versions >= 4.4. A client-side error will
// be returned if the option is specified for MongoDB versions <= 4.2. The default
// value is nil, meaning that the server-side default will be used. See
// dochub.mongodb.org/core/index-commit-quorum for more information.
func (c *CreateIndexesOptionsBuilder) SetCommitQuorumString(quorum string) *CreateIndexesOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateIndexesOptions) error {
opts.CommitQuorum = quorum
return nil
})
return c
}
// SetCommitQuorumMajority sets the value for the CommitQuorum to special "majority" value.
// Specifies the number of data-bearing members of a replica set, including the primary,
// that must complete the index builds successfully before the primary marks the indexes
// as ready.
//
// Semantics for "majority": A special value to indicate that more than half the nodes
// must complete the build.
//
// This option is only available on MongoDB versions >= 4.4. A client-side error will
// be returned if the option is specified for MongoDB versions <= 4.2. The default
// value is nil, meaning that the server-side default will be used. See
// dochub.mongodb.org/core/index-commit-quorum for more information.
func (c *CreateIndexesOptionsBuilder) SetCommitQuorumMajority() *CreateIndexesOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateIndexesOptions) error {
opts.CommitQuorum = "majority"
return nil
})
return c
}
// SetCommitQuorumVotingMembers sets the value for the CommitQuorum to special "votingMembers" value.
// Specifies the number of data-bearing members of a replica set, including the primary,
// that must complete the index builds successfully before the primary marks the indexes
// as ready.
//
// Semantics for "votingMembers": A special value to indicate that all voting data-bearing
// nodes must complete.
//
// This option is only available on MongoDB versions >= 4.4. A client-side error will
// be returned if the option is specified for MongoDB versions <= 4.2. The default
// value is nil, meaning that the server-side default will be used. See
// dochub.mongodb.org/core/index-commit-quorum for more information.
func (c *CreateIndexesOptionsBuilder) SetCommitQuorumVotingMembers() *CreateIndexesOptionsBuilder {
c.Opts = append(c.Opts, func(opts *CreateIndexesOptions) error {
opts.CommitQuorum = "votingMembers"
return nil
})
return c
}
// DropIndexesOptions represents arguments that can be used to configure
// IndexView.DropOne and IndexView.DropAll operations.
type DropIndexesOptions struct {
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// DropIndexesOptionsBuilder contains options to configure dropping indexes.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type DropIndexesOptionsBuilder struct {
Opts []func(*DropIndexesOptions) error
}
// DropIndexes creates a new DropIndexesOptions instance.
func DropIndexes() *DropIndexesOptionsBuilder {
return &DropIndexesOptionsBuilder{}
}
// List returns a list of DropIndexesOptions setter functions.
func (d *DropIndexesOptionsBuilder) List() []func(*DropIndexesOptions) error {
return d.Opts
}
// ListIndexesOptions represents arguments that can be used to configure an
// IndexView.List operation.
//
// See corresponding setter methods for documentation.
type ListIndexesOptions struct {
BatchSize *int32
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// ListIndexesOptionsBuilder contains options to configure count operations. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type ListIndexesOptionsBuilder struct {
Opts []func(*ListIndexesOptions) error
}
// ListIndexes creates a new ListIndexesOptions instance.
func ListIndexes() *ListIndexesOptionsBuilder {
return &ListIndexesOptionsBuilder{}
}
// List returns a list of CountOptions setter functions.
func (l *ListIndexesOptionsBuilder) List() []func(*ListIndexesOptions) error {
return l.Opts
}
// SetBatchSize sets the value for the BatchSize field. Specifies the maximum number
// of documents to be included in each batch returned by the server.
func (l *ListIndexesOptionsBuilder) SetBatchSize(i int32) *ListIndexesOptionsBuilder {
l.Opts = append(l.Opts, func(opts *ListIndexesOptions) error {
opts.BatchSize = &i
return nil
})
return l
}
// IndexOptions represents arguments that can be used to configure a new index
// created through the IndexView.CreateOne or IndexView.CreateMany operations.
//
// See corresponding setter methods for documentation.
type IndexOptions struct {
ExpireAfterSeconds *int32
Name *string
Sparse *bool
StorageEngine any
Unique *bool
Version *int32
DefaultLanguage *string
LanguageOverride *string
TextVersion *int32
Weights any
SphereVersion *int32
Bits *int32
Max *float64
Min *float64
BucketSize *int32
PartialFilterExpression any
Collation *Collation
WildcardProjection any
Hidden *bool
}
// IndexOptionsBuilder contains options to configure index operations. Each option
// can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type IndexOptionsBuilder struct {
Opts []func(*IndexOptions) error
}
// Index creates a new IndexOptions instance.
func Index() *IndexOptionsBuilder {
return &IndexOptionsBuilder{}
}
// List returns a list of IndexOptions setter functions.
func (i *IndexOptionsBuilder) List() []func(*IndexOptions) error {
return i.Opts
}
// SetExpireAfterSeconds sets value for the ExpireAfterSeconds field. Specifies the length
// of time, in seconds, for documents to remain in the collection. The default value is 0,
// which means that documents will remain in the collection until they're explicitly
// deleted or the collection is dropped.
func (i *IndexOptionsBuilder) SetExpireAfterSeconds(seconds int32) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.ExpireAfterSeconds = &seconds
return nil
})
return i
}
// SetName sets the value for the Name field. Specifies the name of the index. The default
// value is "[field1]_[direction1]_[field2]_[direction2]...". For example, an index with
// the specification {name: 1, age: -1} will be named "name_1_age_-1".
func (i *IndexOptionsBuilder) SetName(name string) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Name = &name
return nil
})
return i
}
// SetSparse sets the value of the Sparse field. If true, the index will only reference
// documents that contain the fields specified in the index. The default is false.
func (i *IndexOptionsBuilder) SetSparse(sparse bool) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Sparse = &sparse
return nil
})
return i
}
// SetStorageEngine sets the value for the StorageEngine field. Specifies the
// storage engine to use for the index. The value must be a document in the form
// {<storage engine name>: <options>}. The default value is nil, which means that
// the default storage engine will be used.
func (i *IndexOptionsBuilder) SetStorageEngine(engine any) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.StorageEngine = engine
return nil
})
return i
}
// SetUnique sets the value for the Unique field. If true, the collection will not
// accept insertion or update of documents where the index key value matches an
// existing value in the index. The default is false.
func (i *IndexOptionsBuilder) SetUnique(unique bool) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Unique = &unique
return nil
})
return i
}
// SetVersion sets the value for the Version field. Specifies the index version
// number, either 0 or 1.
func (i *IndexOptionsBuilder) SetVersion(version int32) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Version = &version
return nil
})
return i
}
// SetDefaultLanguage sets the value for the DefaultLanguage field. Specifies the
// language that determines the list of stop words and the rules for the stemmer
// and tokenizer. This option is only applicable for text indexes and is ignored for
// other index types. The default value is "english".
func (i *IndexOptionsBuilder) SetDefaultLanguage(language string) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.DefaultLanguage = &language
return nil
})
return i
}
// SetLanguageOverride sets the value of the LanguageOverride field. Specifies the name
// of the field in the collection's documents that contains the override language for the
// document. This option is only applicable for text indexes and is ignored for other index
// types. The default value is the value of the DefaultLanguage option.
func (i *IndexOptionsBuilder) SetLanguageOverride(override string) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.LanguageOverride = &override
return nil
})
return i
}
// SetTextVersion sets the value for the TextVersion field. Specifies the index version number
// for a text index. See https://www.mongodb.com/docs/manual/core/index-text/#text-versions
// for information about different version numbers.
func (i *IndexOptionsBuilder) SetTextVersion(version int32) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.TextVersion = &version
return nil
})
return i
}
// SetWeights sets the value for the Weights field. Sets a document that contains field
// and weight pairs. The weight is an integer ranging from 1 to 99,999, inclusive,
// indicating the significance of the field relative to the other indexed fields in
// terms of the score. This option is only applicable for text indexes and is ignored
// for other index types. The default value is nil, which means that every field will
// have a weight of 1.
func (i *IndexOptionsBuilder) SetWeights(weights any) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Weights = weights
return nil
})
return i
}
// SetSphereVersion sets the value for the SphereVersion field. Specifies the index version number
// for a 2D sphere index. See https://www.mongodb.com/docs/manual/core/2dsphere/#dsphere-v2 for
// information about different version numbers.
func (i *IndexOptionsBuilder) SetSphereVersion(version int32) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.SphereVersion = &version
return nil
})
return i
}
// SetBits sets the value for the Bits field. Specifies the precision of the stored geohash
// value of the location data. This option only applies to 2D indexes and is ignored for
// other index types. The value must be between 1 and 32, inclusive. The default value is 26.
func (i *IndexOptionsBuilder) SetBits(bits int32) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Bits = &bits
return nil
})
return i
}
// SetMax sets the value for the Max field. Specifies the upper inclusive boundary for
// longitude and latitude values. This option is only applicable to 2D indexes and
// is ignored for other index types. The default value is 180.0.
func (i *IndexOptionsBuilder) SetMax(max float64) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Max = &max
return nil
})
return i
}
// SetMin sets the value for the Min field. Specifies the lower inclusive boundary for
// longitude and latitude values. This option is only applicable to 2D indexes and
// is ignored for other index types. The default value is -180.0.
func (i *IndexOptionsBuilder) SetMin(min float64) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Min = &min
return nil
})
return i
}
// SetBucketSize sets the value for the BucketSize field. Specifies the number of units
// within which to group location values. Location values that are within BucketSize
// units of each other will be grouped in the same bucket. This option is only applicable
// to geoHaystack indexes and is ignored for other index types. The value must be greater
// than 0.
func (i *IndexOptionsBuilder) SetBucketSize(bucketSize int32) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.BucketSize = &bucketSize
return nil
})
return i
}
// SetPartialFilterExpression sets the value for the PartialFilterExpression field. Sets
// a document that defines which collection documents the index should reference.
func (i *IndexOptionsBuilder) SetPartialFilterExpression(expression any) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.PartialFilterExpression = expression
return nil
})
return i
}
// SetCollation sets the value for the Collation field. Specifies the collation to use for
// string comparisons for the index.
func (i *IndexOptionsBuilder) SetCollation(collation *Collation) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Collation = collation
return nil
})
return i
}
// SetWildcardProjection sets the value for the WildcardProjection field. Sets a document
// that defines the wildcard projection for the index.
func (i *IndexOptionsBuilder) SetWildcardProjection(wildcardProjection any) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.WildcardProjection = wildcardProjection
return nil
})
return i
}
// SetHidden sets the value for the Hidden field. If true, the index will exist on the
// target collection but will not be used by the query planner when executing operations.
// This option is only valid for MongoDB versions >= 4.4. The default value is false.
func (i *IndexOptionsBuilder) SetHidden(hidden bool) *IndexOptionsBuilder {
i.Opts = append(i.Opts, func(opts *IndexOptions) error {
opts.Hidden = &hidden
return nil
})
return i
}

View File

@@ -0,0 +1,133 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// InsertOneOptions represents arguments that can be used to configure an InsertOne
// operation.
//
// See corresponding setter methods for documentation.
type InsertOneOptions struct {
BypassDocumentValidation *bool
Comment any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// InsertOneOptionsBuilder represents functional options that configure an
// InsertOneopts.
type InsertOneOptionsBuilder struct {
Opts []func(*InsertOneOptions) error
}
// InsertOne creates a new InsertOneOptions instance.
func InsertOne() *InsertOneOptionsBuilder {
return &InsertOneOptionsBuilder{}
}
// List returns a list of InsertOneOptions setter functions.
func (ioo *InsertOneOptionsBuilder) List() []func(*InsertOneOptions) error {
return ioo.Opts
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true,
// writes executed as part of the operation will opt out of document-level validation on the
// server. The default value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/
// for more information about document validation.
func (ioo *InsertOneOptionsBuilder) SetBypassDocumentValidation(b bool) *InsertOneOptionsBuilder {
ioo.Opts = append(ioo.Opts, func(opts *InsertOneOptions) error {
opts.BypassDocumentValidation = &b
return nil
})
return ioo
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be included in server logs, profiling logs, and currentOp queries to help trace
// the operation. The default value is nil, which means that no comment will be included in the logs.
func (ioo *InsertOneOptionsBuilder) SetComment(comment any) *InsertOneOptionsBuilder {
ioo.Opts = append(ioo.Opts, func(opts *InsertOneOptions) error {
opts.Comment = &comment
return nil
})
return ioo
}
// InsertManyOptions represents arguments that can be used to configure an
// InsertMany operation.
//
// See corresponding setter methods for documentation.
type InsertManyOptions struct {
BypassDocumentValidation *bool
Comment any
Ordered *bool
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// InsertManyOptionsBuilder contains options to configure insert operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type InsertManyOptionsBuilder struct {
Opts []func(*InsertManyOptions) error
}
// InsertMany creates a new InsertManyOptions instance.
func InsertMany() *InsertManyOptionsBuilder {
opts := &InsertManyOptionsBuilder{}
opts.SetOrdered(DefaultOrdered)
return opts
}
// List returns a list of InsertManyOptions setter functions.
func (imo *InsertManyOptionsBuilder) List() []func(*InsertManyOptions) error {
return imo.Opts
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true,
// writes executed as part of the operation will opt out of document-level validation on the
// server. The default value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/
// for more information about document validation.
func (imo *InsertManyOptionsBuilder) SetBypassDocumentValidation(b bool) *InsertManyOptionsBuilder {
imo.Opts = append(imo.Opts, func(opts *InsertManyOptions) error {
opts.BypassDocumentValidation = &b
return nil
})
return imo
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be
// included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default value is nil, which means that no comment will be included in the logs.
func (imo *InsertManyOptionsBuilder) SetComment(comment any) *InsertManyOptionsBuilder {
imo.Opts = append(imo.Opts, func(opts *InsertManyOptions) error {
opts.Comment = comment
return nil
})
return imo
}
// SetOrdered sets the value for the Ordered field. If true, no writes will be executed after
// one fails. The default value is true.
func (imo *InsertManyOptionsBuilder) SetOrdered(b bool) *InsertManyOptionsBuilder {
imo.Opts = append(imo.Opts, func(opts *InsertManyOptions) error {
opts.Ordered = &b
return nil
})
return imo
}

View File

@@ -0,0 +1,77 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// ListCollectionsOptions represents arguments that can be used to configure a
// ListCollections operation.
//
// See corresponding setter methods for documentation.
type ListCollectionsOptions struct {
NameOnly *bool
BatchSize *int32
AuthorizedCollections *bool
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// ListCollectionsOptionsBuilder contains options to configure list collection
// operations. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type ListCollectionsOptionsBuilder struct {
Opts []func(*ListCollectionsOptions) error
}
// ListCollections creates a new ListCollectionsOptions instance.
func ListCollections() *ListCollectionsOptionsBuilder {
return &ListCollectionsOptionsBuilder{}
}
// List returns a list of CountOptions setter functions.
func (lc *ListCollectionsOptionsBuilder) List() []func(*ListCollectionsOptions) error {
return lc.Opts
}
// SetNameOnly sets the value for the NameOnly field. If true, each collection document will only
// contain a field for the collection name. The default value is false.
func (lc *ListCollectionsOptionsBuilder) SetNameOnly(b bool) *ListCollectionsOptionsBuilder {
lc.Opts = append(lc.Opts, func(opts *ListCollectionsOptions) error {
opts.NameOnly = &b
return nil
})
return lc
}
// SetBatchSize sets the value for the BatchSize field. Specifies the maximum number of documents
// to be included in each batch returned by the server.
func (lc *ListCollectionsOptionsBuilder) SetBatchSize(size int32) *ListCollectionsOptionsBuilder {
lc.Opts = append(lc.Opts, func(opts *ListCollectionsOptions) error {
opts.BatchSize = &size
return nil
})
return lc
}
// SetAuthorizedCollections sets the value for the AuthorizedCollections field. If true, and
// NameOnly is true, limits the documents returned to only contain collections the user is
// authorized to use. The default value is false.
func (lc *ListCollectionsOptionsBuilder) SetAuthorizedCollections(b bool) *ListCollectionsOptionsBuilder {
lc.Opts = append(lc.Opts, func(opts *ListCollectionsOptions) error {
opts.AuthorizedCollections = &b
return nil
})
return lc
}

View File

@@ -0,0 +1,54 @@
// 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 options
// ListDatabasesOptions represents arguments that can be used to configure a
// ListDatabases operation.
//
// See corresponding setter methods for documentation.
type ListDatabasesOptions struct {
NameOnly *bool
AuthorizedDatabases *bool
}
// ListDatabasesOptionsBuilder represents functional options that configure a
// ListDatabasesopts.
type ListDatabasesOptionsBuilder struct {
Opts []func(*ListDatabasesOptions) error
}
// ListDatabases creates a new ListDatabasesOptions instance.
func ListDatabases() *ListDatabasesOptionsBuilder {
return &ListDatabasesOptionsBuilder{}
}
// List returns a list of ListDatabasesOptions setter functions.
func (ld *ListDatabasesOptionsBuilder) List() []func(*ListDatabasesOptions) error {
return ld.Opts
}
// SetNameOnly sets the value for the NameOnly field. If true, only the Name field of the returned
// DatabaseSpecification objects will be populated. The default value is false.
func (ld *ListDatabasesOptionsBuilder) SetNameOnly(b bool) *ListDatabasesOptionsBuilder {
ld.Opts = append(ld.Opts, func(opts *ListDatabasesOptions) error {
opts.NameOnly = &b
return nil
})
return ld
}
// SetAuthorizedDatabases sets the value for the AuthorizedDatabases field. If true, only the
// databases which the user is authorized to see will be returned. For more information about the
// behavior of this option, see https://www.mongodb.com/docs/manual/reference/privilege-actions/#find.
// The default value is true.
func (ld *ListDatabasesOptionsBuilder) SetAuthorizedDatabases(b bool) *ListDatabasesOptionsBuilder {
ld.Opts = append(ld.Opts, func(opts *ListDatabasesOptions) error {
opts.AuthorizedDatabases = &b
return nil
})
return ld
}

View File

@@ -0,0 +1,13 @@
// Copyright (C) MongoDB, Inc. 2024-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 options
// Lister is an interface that wraps a List method to return a
// slice of option setters.
type Lister[T any] interface {
List() []func(*T) error
}

View File

@@ -0,0 +1,115 @@
// Copyright (C) MongoDB, Inc. 2023-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 options
import (
"go.mongodb.org/mongo-driver/v2/internal/logger"
)
// LogLevel is an enumeration representing the supported log severity levels.
type LogLevel int
const (
// LogLevelInfo enables logging of informational messages. These logs
// are high-level information about normal driver behavior.
LogLevelInfo LogLevel = LogLevel(logger.LevelInfo)
// LogLevelDebug enables logging of debug messages. These logs can be
// voluminous and are intended for detailed information that may be
// helpful when debugging an application.
LogLevelDebug LogLevel = LogLevel(logger.LevelDebug)
)
// LogComponent is an enumeration representing the "components" which can be
// logged against. A LogLevel can be configured on a per-component basis.
type LogComponent int
const (
// LogComponentAll enables logging for all components.
LogComponentAll LogComponent = LogComponent(logger.ComponentAll)
// LogComponentCommand enables command monitor logging.
LogComponentCommand LogComponent = LogComponent(logger.ComponentCommand)
// LogComponentTopology enables topology logging.
LogComponentTopology LogComponent = LogComponent(logger.ComponentTopology)
// LogComponentServerSelection enables server selection logging.
LogComponentServerSelection LogComponent = LogComponent(logger.ComponentServerSelection)
// LogComponentConnection enables connection services logging.
LogComponentConnection LogComponent = LogComponent(logger.ComponentConnection)
)
// LogSink is an interface that can be implemented to provide a custom sink for
// the driver's logs.
type LogSink interface {
// Info logs a non-error message with the given key/value pairs. This
// method will only be called if the provided level has been defined
// for a component in the LoggerOptions.
//
// Here are the following level mappings for V = "Verbosity":
//
// - V(0): off
// - V(1): informational
// - V(2): debugging
//
// This level mapping is taken from the go-logr/logr library
// specifications, specifically:
//
// "Level V(0) is the default, and logger.V(0).Info() has the same
// meaning as logger.Info()."
Info(level int, message string, keysAndValues ...any)
// Error logs an error message with the given key/value pairs
Error(err error, message string, keysAndValues ...any)
}
// LoggerOptions represent arguments used to configure Logging in the Go Driver.
//
// See corresponding setter methods for documentation.
type LoggerOptions struct {
ComponentLevels map[LogComponent]LogLevel
Sink LogSink
MaxDocumentLength uint
}
// Logger creates a new LoggerOptions instance.
func Logger() *LoggerOptions {
return &LoggerOptions{}
}
// SetComponentLevel sets the LogLevel value for a LogComponent. ComponentLevels is a map of
// LogComponent to LogLevel. The LogLevel for a given LogComponent will be used to determine
// if a log message should be logged.
func (opts *LoggerOptions) SetComponentLevel(component LogComponent, level LogLevel) *LoggerOptions {
if opts.ComponentLevels == nil {
opts.ComponentLevels = map[LogComponent]LogLevel{}
}
opts.ComponentLevels[component] = level
return opts
}
// SetMaxDocumentLength sets the maximum length of a document to be logged. Sink is the
// LogSink that will be used to log messages. If this is nil, the driver will use the
// standard logging library.
func (opts *LoggerOptions) SetMaxDocumentLength(maxDocumentLength uint) *LoggerOptions {
opts.MaxDocumentLength = maxDocumentLength
return opts
}
// SetSink sets the LogSink to use for logging. MaxDocumentLength is the maximum length
// of a document to be logged. If the underlying document is larger than this value, it
// will be truncated and appended with an ellipses "...".
func (opts *LoggerOptions) SetSink(sink LogSink) *LoggerOptions {
opts.Sink = sink
return opts
}

View File

@@ -0,0 +1,70 @@
// 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 options
import (
"go.mongodb.org/mongo-driver/v2/bson"
)
var defaultRegistry = bson.NewRegistry()
// Collation allows users to specify language-specific rules for string comparison, such as
// rules for lettercase and accent marks.
type Collation struct {
Locale string `bson:",omitempty"` // The locale
CaseLevel bool `bson:",omitempty"` // The case level
CaseFirst string `bson:",omitempty"` // The case ordering
Strength int `bson:",omitempty"` // The number of comparison levels to use
NumericOrdering bool `bson:",omitempty"` // Whether to order numbers based on numerical order and not collation order
Alternate string `bson:",omitempty"` // Whether spaces and punctuation are considered base characters
MaxVariable string `bson:",omitempty"` // Which characters are affected by alternate: "shifted"
Normalization bool `bson:",omitempty"` // Causes text to be normalized into Unicode NFD
Backwards bool `bson:",omitempty"` // Causes secondary differences to be considered in reverse order, as it is done in the French language
}
// CursorType specifies whether a cursor should close when the last data is retrieved. See
// NonTailable, Tailable, and TailableAwait.
type CursorType int8
const (
// NonTailable specifies that a cursor should close after retrieving the last data.
NonTailable CursorType = iota
// Tailable specifies that a cursor should not close when the last data is retrieved and can be resumed later.
Tailable
// TailableAwait specifies that a cursor should not close when the last data is retrieved and
// that it should block for a certain amount of time for new data before returning no data.
TailableAwait
)
// ReturnDocument specifies whether a findAndUpdate operation should return the document as it was
// before the update or as it is after the update.
type ReturnDocument int8
const (
// Before specifies that findAndUpdate should return the document as it was before the update.
Before ReturnDocument = iota
// After specifies that findAndUpdate should return the document as it is after the update.
After
)
// FullDocument specifies how a change stream should return the modified document.
type FullDocument string
const (
// Default does not include a document copy.
Default FullDocument = "default"
// Off is the same as sending no value for fullDocumentBeforeChange.
Off FullDocument = "off"
// Required is the same as WhenAvailable but raises a server-side error if the post-image is not available.
Required FullDocument = "required"
// UpdateLookup includes a delta describing the changes to the document and a copy of the entire document that
// was changed.
UpdateLookup FullDocument = "updateLookup"
// WhenAvailable includes a post-image of the modified document for replace and update change events
// if the post-image for this event is available.
WhenAvailable FullDocument = "whenAvailable"
)

View File

@@ -0,0 +1,144 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// ReplaceOptions represents arguments that can be used to configure a ReplaceOne
// operation.
//
// See corresponding setter methods for documentation.
type ReplaceOptions struct {
BypassDocumentValidation *bool
Collation *Collation
Comment any
Hint any
Upsert *bool
Let any
Sort any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// ReplaceOptionsBuilder contains options to configure replace operations. Each
// option can be set through setter functions. See documentation for each setter
// function for an explanation of the option.
type ReplaceOptionsBuilder struct {
Opts []func(*ReplaceOptions) error
}
// Replace creates a new ReplaceOptions instance.
func Replace() *ReplaceOptionsBuilder {
return &ReplaceOptionsBuilder{}
}
// List returns a list of CountOptions setter functions.
func (ro *ReplaceOptionsBuilder) List() []func(*ReplaceOptions) error {
return ro.Opts
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true,
// writes executed as part of the operation will opt out of document-level validation on the server.
// The default value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for
// more information about document validation.
func (ro *ReplaceOptionsBuilder) SetBypassDocumentValidation(b bool) *ReplaceOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *ReplaceOptions) error {
opts.BypassDocumentValidation = &b
return nil
})
return ro
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (ro *ReplaceOptionsBuilder) SetCollation(c *Collation) *ReplaceOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *ReplaceOptions) error {
opts.Collation = c
return nil
})
return ro
}
// SetComment sets the value for the Comment field. Specifies a string or document that will
// be included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default value is nil, which means that no comment will be included in the logs.
func (ro *ReplaceOptionsBuilder) SetComment(comment any) *ReplaceOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *ReplaceOptions) error {
opts.Comment = comment
return nil
})
return ro
}
// SetHint sets the value for the Hint field. Specifies the index to use for the
// operation. This should either be the index name as a string or the index
// specification as a document. This option is only valid for MongoDB versions
// >= 4.2. Server versions < 4.2 will return an error if this option is
// specified. The driver will return an error if this option is specified during
// an unacknowledged write operation. The driver will return an error if the
// hint parameter is a multi-key map. The default value is nil, which means that
// no hint will be sent.
func (ro *ReplaceOptionsBuilder) SetHint(h any) *ReplaceOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *ReplaceOptions) error {
opts.Hint = h
return nil
})
return ro
}
// SetUpsert sets the value for the Upsert field. If true, a new document will be inserted
// if the filter does not match any documents in the collection. The default value is false.
func (ro *ReplaceOptionsBuilder) SetUpsert(b bool) *ReplaceOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *ReplaceOptions) error {
opts.Upsert = &b
return nil
})
return ro
}
// SetLet sets the value for the Let field. Specifies parameters for the aggregate expression.
// This option is only valid for MongoDB versions >= 5.0. Older servers will report an error
// for using this option. This must be a document mapping parameter names to values. Values
// must be constant or closed expressions that do not reference document fields. Parameters
// can then be accessed as variables in an aggregate expression context (e.g. "$$var").
func (ro *ReplaceOptionsBuilder) SetLet(l any) *ReplaceOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *ReplaceOptions) error {
opts.Let = l
return nil
})
return ro
}
// SetSort sets the value for the Sort field. Specifies a document specifying which document should
// be replaced if the filter used by the operation matches multiple documents in the collection. If
// set, the first document in the sorted order will be replaced. This option is only valid for MongoDB
// versions >= 8.0. The sort parameter is evaluated sequentially, so the driver will return an error
// if it is a multi-key map (which is unordeded). The default value is nil.
func (ro *ReplaceOptionsBuilder) SetSort(s any) *ReplaceOptionsBuilder {
ro.Opts = append(ro.Opts, func(opts *ReplaceOptions) error {
opts.Sort = s
return nil
})
return ro
}

View File

@@ -0,0 +1,57 @@
// Copyright (C) MongoDB, Inc. 2022-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 options
// RewrapManyDataKeyOptions represents all possible options used to decrypt and
// encrypt all matching data keys with a possibly new masterKey.
//
// See corresponding setter methods for documentation.
type RewrapManyDataKeyOptions struct {
Provider *string
MasterKey any
}
// RewrapManyDataKeyOptionsBuilder contains options to configure rewraping a
// data key. Each option can be set through setter functions. See documentation
// for each setter function for an explanation of the option.
type RewrapManyDataKeyOptionsBuilder struct {
Opts []func(*RewrapManyDataKeyOptions) error
}
// RewrapManyDataKey creates a new RewrapManyDataKeyOptions instance.
func RewrapManyDataKey() *RewrapManyDataKeyOptionsBuilder {
return new(RewrapManyDataKeyOptionsBuilder)
}
// List returns a list of CountOptions setter functions.
func (rmdko *RewrapManyDataKeyOptionsBuilder) List() []func(*RewrapManyDataKeyOptions) error {
return rmdko.Opts
}
// SetProvider sets the value for the Provider field. Provider identifies the new KMS provider.
// If omitted, encrypting uses the current KMS provider.
func (rmdko *RewrapManyDataKeyOptionsBuilder) SetProvider(provider string) *RewrapManyDataKeyOptionsBuilder {
rmdko.Opts = append(rmdko.Opts, func(opts *RewrapManyDataKeyOptions) error {
opts.Provider = &provider
return nil
})
return rmdko
}
// SetMasterKey sets the value for the MasterKey field. MasterKey identifies the new masterKey.
// If omitted, rewraps with the current masterKey.
func (rmdko *RewrapManyDataKeyOptionsBuilder) SetMasterKey(masterKey any) *RewrapManyDataKeyOptionsBuilder {
rmdko.Opts = append(rmdko.Opts, func(opts *RewrapManyDataKeyOptions) error {
opts.MasterKey = masterKey
return nil
})
return rmdko
}

View File

@@ -0,0 +1,49 @@
// 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 options
import (
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
)
// RunCmdOptions represents arguments that can be used to configure a RunCommand
// operation.
//
// See corresponding setter methods for documentation.
type RunCmdOptions struct {
ReadPreference *readpref.ReadPref
}
// RunCmdOptionsBuilder contains options to configure runCommand operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type RunCmdOptionsBuilder struct {
Opts []func(*RunCmdOptions) error
}
// RunCmd creates a new RunCmdOptions instance.
func RunCmd() *RunCmdOptionsBuilder {
return &RunCmdOptionsBuilder{}
}
// List returns a list of CountOptions setter functions.
func (rc *RunCmdOptionsBuilder) List() []func(*RunCmdOptions) error {
return rc.Opts
}
// SetReadPreference sets value for the ReadPreference field. Specifies the read preference
// to use for the operation. The default value is nil, which means that the primary read
// preference will be used.
func (rc *RunCmdOptionsBuilder) SetReadPreference(rp *readpref.ReadPref) *RunCmdOptionsBuilder {
rc.Opts = append(rc.Opts, func(opts *RunCmdOptions) error {
opts.ReadPreference = rp
return nil
})
return rc
}

View File

@@ -0,0 +1,118 @@
// Copyright (C) MongoDB, Inc. 2023-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 options
// SearchIndexesOptions represents arguments that can be used to configure a
// SearchIndexView.
type SearchIndexesOptions struct {
Name *string
Type *string
}
// SearchIndexesOptionsBuilder contains options to configure search index
// operations. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type SearchIndexesOptionsBuilder struct {
Opts []func(*SearchIndexesOptions) error
}
// SearchIndexes creates a new SearchIndexesOptions instance.
func SearchIndexes() *SearchIndexesOptionsBuilder {
return &SearchIndexesOptionsBuilder{}
}
// List returns a list of CountOptions setter functions.
func (sio *SearchIndexesOptionsBuilder) List() []func(*SearchIndexesOptions) error {
return sio.Opts
}
// SetName sets the value for the Name field.
func (sio *SearchIndexesOptionsBuilder) SetName(name string) *SearchIndexesOptionsBuilder {
sio.Opts = append(sio.Opts, func(opts *SearchIndexesOptions) error {
opts.Name = &name
return nil
})
return sio
}
// SetType sets the value for the Type field.
func (sio *SearchIndexesOptionsBuilder) SetType(typ string) *SearchIndexesOptionsBuilder {
sio.Opts = append(sio.Opts, func(opts *SearchIndexesOptions) error {
opts.Type = &typ
return nil
})
return sio
}
// CreateSearchIndexesOptions represents arguments that can be used to configure
// a SearchIndexView.CreateOne or SearchIndexView.CreateMany operation.
type CreateSearchIndexesOptions struct{}
// CreateSearchIndexesOptionsBuilder contains options to configure creating
// search indexes. Each option can be set through setter functions. See
// documentation for each setter function for an explanation of the option.
type CreateSearchIndexesOptionsBuilder struct {
Opts []func(*CreateSearchIndexesOptions) error
}
// List returns a list of CreateSearchIndexesOptions setter functions.
func (csio *CreateSearchIndexesOptionsBuilder) List() []func(*CreateSearchIndexesOptions) error {
return csio.Opts
}
// ListSearchIndexesOptions represents arguments that can be used to configure a
// SearchIndexView.List operation.
type ListSearchIndexesOptions struct {
AggregateOptions *AggregateOptions
}
// ListSearchIndexesOptionsBuilder contains options that can be used to
// configure a SearchIndexView.List operation.
type ListSearchIndexesOptionsBuilder struct {
Opts []func(*ListSearchIndexesOptions) error
}
// List returns a list of ListSearchIndexesOptions setter functions.
func (lsi *ListSearchIndexesOptionsBuilder) List() []func(*ListSearchIndexesOptions) error {
return lsi.Opts
}
// DropSearchIndexOptions represents arguments that can be used to configure a
// SearchIndexView.DropOne operation.
type DropSearchIndexOptions struct{}
// DropSearchIndexOptionsBuilder contains options to configure dropping search
// indexes. Each option can be set through setter functions. See documentation
// for each setter function for an explanation of the option.
type DropSearchIndexOptionsBuilder struct {
Opts []func(*DropSearchIndexOptions) error
}
// List returns a list of DropSearchIndexOptions setter functions.
func (dsio *DropSearchIndexOptionsBuilder) List() []func(*DropSearchIndexOptions) error {
return dsio.Opts
}
// UpdateSearchIndexOptions represents arguments that can be used to configure a
// SearchIndexView.UpdateOne operation.
type UpdateSearchIndexOptions struct{}
// UpdateSearchIndexOptionsBuilder contains options to configure updating search
// indexes. Each option can be set through setter functions. See documentation
// for each setter function for an explanation of the option.
type UpdateSearchIndexOptionsBuilder struct {
Opts []func(*UpdateSearchIndexOptions) error
}
// List returns a list of UpdateSearchIndexOptions setter functions.
func (usio *UpdateSearchIndexOptionsBuilder) List() []func(*UpdateSearchIndexOptions) error {
return usio.Opts
}

View File

@@ -0,0 +1,66 @@
// 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 options
import (
"fmt"
)
// ServerAPIOptions represents arguments used to configure the API version sent to
// the server when running commands.
//
// Sending a specified server API version causes the server to behave in a
// manner compatible with that API version. It also causes the driver to behave
// in a manner compatible with the drivers behavior as of the release when the
// driver first started to support the specified server API version.
//
// The user must specify a ServerAPIVersion if including ServerAPIOptions in
// their client. That version must also be currently supported by the driver.
// This version of the driver supports API version "1".
//
// See corresponding setter methods for documentation.
type ServerAPIOptions struct {
ServerAPIVersion ServerAPIVersion
Strict *bool
DeprecationErrors *bool
}
// ServerAPI creates a new ServerAPIOptions configured with the provided
// serverAPIversion.
func ServerAPI(serverAPIVersion ServerAPIVersion) *ServerAPIOptions {
return &ServerAPIOptions{ServerAPIVersion: serverAPIVersion}
}
// SetStrict specifies whether the server should return errors for features that are not part of the API version.
func (s *ServerAPIOptions) SetStrict(strict bool) *ServerAPIOptions {
s.Strict = &strict
return s
}
// SetDeprecationErrors specifies whether the server should return errors for deprecated features.
func (s *ServerAPIOptions) SetDeprecationErrors(deprecationErrors bool) *ServerAPIOptions {
s.DeprecationErrors = &deprecationErrors
return s
}
// ServerAPIVersion represents an API version that can be used in ServerAPIOptions.
type ServerAPIVersion string
const (
// ServerAPIVersion1 is the first API version.
ServerAPIVersion1 ServerAPIVersion = "1"
)
// Validate determines if the provided ServerAPIVersion is currently supported by the driver.
func (sav ServerAPIVersion) Validate() error {
if sav == ServerAPIVersion1 {
return nil
}
return fmt.Errorf("api version %q not supported; this driver version only supports API version \"1\"", sav)
}

View File

@@ -0,0 +1,87 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/bson"
// DefaultCausalConsistency is the default value for the CausalConsistency option.
var DefaultCausalConsistency = true
// SessionOptions represents arguments that can be used to configure a Session.
//
// See corresponding setter methods for documentation.
type SessionOptions struct {
CausalConsistency *bool
DefaultTransactionOptions *TransactionOptionsBuilder
Snapshot *bool
SnapshotTime *bson.Timestamp
}
// SessionOptionsBuilder represents functional options that configure a Sessionopts.
type SessionOptionsBuilder struct {
Opts []func(*SessionOptions) error
}
// Session creates a new SessionOptions instance.
func Session() *SessionOptionsBuilder {
return &SessionOptionsBuilder{}
}
// List returns a list of SessionOptions setter functions.
func (s *SessionOptionsBuilder) List() []func(*SessionOptions) error {
return s.Opts
}
// SetCausalConsistency sets the value for the CausalConsistency field. If true, causal
// consistency will be enabled for the session. This option cannot be set to true if Snapshot
// is set to true. The default value is true unless Snapshot is set to true. See
// https://www.mongodb.com/docs/manual/core/read-isolation-consistency-recency/#sessions
// for more information.
func (s *SessionOptionsBuilder) SetCausalConsistency(b bool) *SessionOptionsBuilder {
s.Opts = append(s.Opts, func(opts *SessionOptions) error {
opts.CausalConsistency = &b
return nil
})
return s
}
// SetDefaultTransactionOptions sets the value for the DefaultTransactionOptions field.
// Specifies the default options for transactions started in the session. If this object
// or any value on the object is nil, the client-level read concern, write concern,
// and/or read preference will be used to start the session.
func (s *SessionOptionsBuilder) SetDefaultTransactionOptions(dt *TransactionOptionsBuilder) *SessionOptionsBuilder {
s.Opts = append(s.Opts, func(opts *SessionOptions) error {
opts.DefaultTransactionOptions = dt
return nil
})
return s
}
// SetSnapshot sets the value for the Snapshot field. If true, all read operations performed
// with this session will be read from the same snapshot. This option cannot be set to true
// if CausalConsistency is set to true. Transactions and write operations are not allowed on
// snapshot sessions and will error. The default value is false.
func (s *SessionOptionsBuilder) SetSnapshot(b bool) *SessionOptionsBuilder {
s.Opts = append(s.Opts, func(opts *SessionOptions) error {
opts.Snapshot = &b
return nil
})
return s
}
// SetSnapshotTime sets the value for the SnapshotTime field. Specifies the
// timestamp to use for snapshot reads within the session. This option can only
// be set if Snapshot is set to true. If not provided, the snapshot time will be
// determined automatically from the atClusterTime of the first read operation
// performed in the session. The default value is nil.
func (s *SessionOptionsBuilder) SetSnapshotTime(t bson.Timestamp) *SessionOptionsBuilder {
s.Opts = append(s.Opts, func(opts *SessionOptions) error {
opts.SnapshotTime = &t
return nil
})
return s
}

View File

@@ -0,0 +1,79 @@
// 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 options
import (
"go.mongodb.org/mongo-driver/v2/mongo/readconcern"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
"go.mongodb.org/mongo-driver/v2/mongo/writeconcern"
)
// TransactionOptions represents arguments that can be used to configure a
// transaction.
//
// See corresponding setter methods for documentation.
type TransactionOptions struct {
ReadConcern *readconcern.ReadConcern
ReadPreference *readpref.ReadPref
WriteConcern *writeconcern.WriteConcern
}
// TransactionOptionsBuilder contains arguments to configure count operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type TransactionOptionsBuilder struct {
Opts []func(*TransactionOptions) error
}
// Transaction creates a new TransactionOptions instance.
func Transaction() *TransactionOptionsBuilder {
return &TransactionOptionsBuilder{}
}
// List returns a list of TransactionOptions setter functions.
func (t *TransactionOptionsBuilder) List() []func(*TransactionOptions) error {
return t.Opts
}
// SetReadConcern sets the value for the ReadConcern field. Specifies the read concern for operations
// in the transaction. The default value is nil, which means that the default read concern of the
// session used to start the transaction will be used.
func (t *TransactionOptionsBuilder) SetReadConcern(rc *readconcern.ReadConcern) *TransactionOptionsBuilder {
t.Opts = append(t.Opts, func(opts *TransactionOptions) error {
opts.ReadConcern = rc
return nil
})
return t
}
// SetReadPreference sets the value for the ReadPreference field. Specifies the read preference for
// operations in the transaction. The default value is nil, which means that the default read
// preference of the session used to start the transaction will be used.
func (t *TransactionOptionsBuilder) SetReadPreference(rp *readpref.ReadPref) *TransactionOptionsBuilder {
t.Opts = append(t.Opts, func(opts *TransactionOptions) error {
opts.ReadPreference = rp
return nil
})
return t
}
// SetWriteConcern sets the value for the WriteConcern field. Specifies the write concern for
// operations in the transaction. The default value is nil, which means that the default
// write concern of the session used to start the transaction will be used.
func (t *TransactionOptionsBuilder) SetWriteConcern(wc *writeconcern.WriteConcern) *TransactionOptionsBuilder {
t.Opts = append(t.Opts, func(opts *TransactionOptions) error {
opts.WriteConcern = wc
return nil
})
return t
}

View File

@@ -0,0 +1,293 @@
// 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 options
import "go.mongodb.org/mongo-driver/v2/internal/optionsutil"
// UpdateOneOptions represents arguments that can be used to configure UpdateOne
// operations.
//
// See corresponding setter methods for documentation.
type UpdateOneOptions struct {
ArrayFilters []any
BypassDocumentValidation *bool
Collation *Collation
Comment any
Hint any
Upsert *bool
Let any
Sort any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// UpdateOneOptionsBuilder contains options to configure UpdateOne operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type UpdateOneOptionsBuilder struct {
Opts []func(*UpdateOneOptions) error
}
// UpdateOne creates a new UpdateOneOptions instance.
func UpdateOne() *UpdateOneOptionsBuilder {
return &UpdateOneOptionsBuilder{}
}
// List returns a list of UpdateOneOptions setter functions.
func (uo *UpdateOneOptionsBuilder) List() []func(*UpdateOneOptions) error {
return uo.Opts
}
// SetArrayFilters sets the value for the ArrayFilters field. ArrayFilters is a
// set of filters specifying to which array elements an update should apply. The
// default value is nil, which means the update will apply to all array
// elements.
func (uo *UpdateOneOptionsBuilder) SetArrayFilters(af []any) *UpdateOneOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error {
opts.ArrayFilters = af
return nil
})
return uo
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true,
// writes executed as part of the operation will opt out of document-level validation on the server.
// The default value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for
// more information about document validation.
func (uo *UpdateOneOptionsBuilder) SetBypassDocumentValidation(b bool) *UpdateOneOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error {
opts.BypassDocumentValidation = &b
return nil
})
return uo
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (uo *UpdateOneOptionsBuilder) SetCollation(c *Collation) *UpdateOneOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error {
opts.Collation = c
return nil
})
return uo
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be
// included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default value is nil, which means that no comment will be included in the logs.
func (uo *UpdateOneOptionsBuilder) SetComment(comment any) *UpdateOneOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error {
opts.Comment = comment
return nil
})
return uo
}
// SetHint sets the value for the Hint field. Specifies the index to use for the
// operation. This should either be the index name as a string or the index
// specification as a document. This option is only valid for MongoDB versions
// >= 4.2. Server versions < 4.2 will return an error if this option is
// specified. The driver will return an error if this option is specified during
// an unacknowledged write operation. The driver will return an error if the
// hint parameter is a multi-key map. The default value is nil, which means that
// no hint will be sent.
func (uo *UpdateOneOptionsBuilder) SetHint(h any) *UpdateOneOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error {
opts.Hint = h
return nil
})
return uo
}
// SetUpsert sets the value for the Upsert field. If true, a new document will be inserted if the
// filter does not match any documents in the collection. The default value is false.
func (uo *UpdateOneOptionsBuilder) SetUpsert(b bool) *UpdateOneOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error {
opts.Upsert = &b
return nil
})
return uo
}
// SetLet sets the value for the Let field. Specifies parameters for the update expression. This
// option is only valid for MongoDB versions >= 5.0. Older servers will report an error for using
// this option. This must be a document mapping parameter names to values. Values must be constant
// or closed expressions that do not reference document fields. Parameters can then be accessed
// as variables in an aggregate expression context (e.g. "$$var").
func (uo *UpdateOneOptionsBuilder) SetLet(l any) *UpdateOneOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error {
opts.Let = l
return nil
})
return uo
}
// SetSort sets the value for the Sort field. Specifies a document specifying which document should
// be updated if the filter used by the operation matches multiple documents in the collection. If
// set, the first document in the sorted order will be updated. This option is only valid for MongoDB
// versions >= 8.0. The sort parameter is evaluated sequentially, so the driver will return an error
// if it is a multi-key map (which is unordeded). The default value is nil.
func (uo *UpdateOneOptionsBuilder) SetSort(s any) *UpdateOneOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateOneOptions) error {
opts.Sort = s
return nil
})
return uo
}
// UpdateManyOptions represents arguments that can be used to configure UpdateMany
// operations.
//
// See corresponding setter methods for documentation.
type UpdateManyOptions struct {
ArrayFilters []any
BypassDocumentValidation *bool
Collation *Collation
Comment any
Hint any
Upsert *bool
Let any
// Deprecated: This option is for internal use only and should not be set. It may be changed or removed in any
// release.
Internal optionsutil.Options
}
// UpdateManyOptionsBuilder contains options to configure UpdateMany operations.
// Each option can be set through setter functions. See documentation for each
// setter function for an explanation of the option.
type UpdateManyOptionsBuilder struct {
Opts []func(*UpdateManyOptions) error
}
// UpdateMany creates a new UpdateManyOptions instance.
func UpdateMany() *UpdateManyOptionsBuilder {
return &UpdateManyOptionsBuilder{}
}
// List returns a list of UpdateManyOptions setter functions.
func (uo *UpdateManyOptionsBuilder) List() []func(*UpdateManyOptions) error {
return uo.Opts
}
// SetArrayFilters sets the value for the ArrayFilters field. ArrayFilters is a
// set of filters specifying to which array elements an update should apply. The
// default value is nil, which means the update will apply to all array
// elements.
func (uo *UpdateManyOptionsBuilder) SetArrayFilters(af []any) *UpdateManyOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error {
opts.ArrayFilters = af
return nil
})
return uo
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field. If true,
// writes executed as part of the operation will opt out of document-level validation on the server.
// The default value is false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for
// more information about document validation.
func (uo *UpdateManyOptionsBuilder) SetBypassDocumentValidation(b bool) *UpdateManyOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error {
opts.BypassDocumentValidation = &b
return nil
})
return uo
}
// SetCollation sets the value for the Collation field. Specifies a collation to
// use for string comparisons during the operation. The default value is nil,
// which means the default collation of the collection will be used.
func (uo *UpdateManyOptionsBuilder) SetCollation(c *Collation) *UpdateManyOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error {
opts.Collation = c
return nil
})
return uo
}
// SetComment sets the value for the Comment field. Specifies a string or document that will be
// included in server logs, profiling logs, and currentOp queries to help trace the operation.
// The default value is nil, which means that no comment will be included in the logs.
func (uo *UpdateManyOptionsBuilder) SetComment(comment any) *UpdateManyOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error {
opts.Comment = comment
return nil
})
return uo
}
// SetHint sets the value for the Hint field. Specifies the index to use for the
// operation. This should either be the index name as a string or the index
// specification as a document. This option is only valid for MongoDB versions
// >= 4.2. Server versions < 4.2 will return an error if this option is
// specified. The driver will return an error if this option is specified during
// an unacknowledged write operation. The driver will return an error if the
// hint parameter is a multi-key map. The default value is nil, which means that
// no hint will be sent.
func (uo *UpdateManyOptionsBuilder) SetHint(h any) *UpdateManyOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error {
opts.Hint = h
return nil
})
return uo
}
// SetUpsert sets the value for the Upsert field. If true, a new document will be inserted if the
// filter does not match any documents in the collection. The default value is false.
func (uo *UpdateManyOptionsBuilder) SetUpsert(b bool) *UpdateManyOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error {
opts.Upsert = &b
return nil
})
return uo
}
// SetLet sets the value for the Let field. Specifies parameters for the update expression. This
// option is only valid for MongoDB versions >= 5.0. Older servers will report an error for using
// this option. This must be a document mapping parameter names to values. Values must be constant
// or closed expressions that do not reference document fields. Parameters can then be accessed
// as variables in an aggregate expression context (e.g. "$$var").
func (uo *UpdateManyOptionsBuilder) SetLet(l any) *UpdateManyOptionsBuilder {
uo.Opts = append(uo.Opts, func(opts *UpdateManyOptions) error {
opts.Let = l
return nil
})
return uo
}

View File

@@ -0,0 +1,65 @@
// 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 readconcern defines read concerns for MongoDB operations.
//
// For more information about MongoDB read concerns, see
// https://www.mongodb.com/docs/manual/reference/read-concern/
package readconcern
// A ReadConcern defines a MongoDB read concern, which allows you to control the consistency and
// isolation properties of the data read from replica sets and replica set shards.
//
// For more information about MongoDB read concerns, see
// https://www.mongodb.com/docs/manual/reference/read-concern/
type ReadConcern struct {
Level string
}
// Local returns a ReadConcern that requests data from the instance with no guarantee that the data
// has been written to a majority of the replica set members (i.e. may be rolled back).
//
// For more information about read concern "local", see
// https://www.mongodb.com/docs/manual/reference/read-concern-local/
func Local() *ReadConcern {
return &ReadConcern{Level: "local"}
}
// Majority returns a ReadConcern that requests data that has been acknowledged by a majority of the
// replica set members (i.e. the documents read are durable and guaranteed not to roll back).
//
// For more information about read concern "majority", see
// https://www.mongodb.com/docs/manual/reference/read-concern-majority/
func Majority() *ReadConcern {
return &ReadConcern{Level: "majority"}
}
// Linearizable returns a ReadConcern that requests data that reflects all successful
// majority-acknowledged writes that completed prior to the start of the read operation.
//
// For more information about read concern "linearizable", see
// https://www.mongodb.com/docs/manual/reference/read-concern-linearizable/
func Linearizable() *ReadConcern {
return &ReadConcern{Level: "linearizable"}
}
// Available returns a ReadConcern that requests data from an instance with no guarantee that the
// data has been written to a majority of the replica set members (i.e. may be rolled back).
//
// For more information about read concern "available", see
// https://www.mongodb.com/docs/manual/reference/read-concern-available/
func Available() *ReadConcern {
return &ReadConcern{Level: "available"}
}
// Snapshot returns a ReadConcern that requests majority-committed data as it appears across shards
// from a specific single point in time in the recent past.
//
// For more information about read concern "snapshot", see
// https://www.mongodb.com/docs/manual/reference/read-concern-snapshot/
func Snapshot() *ReadConcern {
return &ReadConcern{Level: "snapshot"}
}

View File

@@ -0,0 +1,88 @@
// 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 readpref
import (
"fmt"
"strings"
)
// Mode indicates the user's preference on reads.
type Mode uint8
// Mode constants
const (
_ Mode = iota
// PrimaryMode indicates that only a primary is
// considered for reading. This is the default
// mode.
PrimaryMode
// PrimaryPreferredMode indicates that if a primary
// is available, use it; otherwise, eligible
// secondaries will be considered.
PrimaryPreferredMode
// SecondaryMode indicates that only secondaries
// should be considered.
SecondaryMode
// SecondaryPreferredMode indicates that only secondaries
// should be considered when one is available. If none
// are available, then a primary will be considered.
SecondaryPreferredMode
// NearestMode indicates that all primaries and secondaries
// will be considered.
NearestMode
)
// ModeFromString returns a mode corresponding to
// mode.
func ModeFromString(mode string) (Mode, error) {
switch strings.ToLower(mode) {
case "primary":
return PrimaryMode, nil
case "primarypreferred":
return PrimaryPreferredMode, nil
case "secondary":
return SecondaryMode, nil
case "secondarypreferred":
return SecondaryPreferredMode, nil
case "nearest":
return NearestMode, nil
}
return Mode(0), fmt.Errorf("unknown read preference %v", mode)
}
// String returns the string representation of mode.
func (mode Mode) String() string {
switch mode {
case PrimaryMode:
return "primary"
case PrimaryPreferredMode:
return "primaryPreferred"
case SecondaryMode:
return "secondary"
case SecondaryPreferredMode:
return "secondaryPreferred"
case NearestMode:
return "nearest"
default:
return "unknown"
}
}
// IsValid checks whether the mode is valid.
func (mode Mode) IsValid() bool {
switch mode {
case PrimaryMode,
PrimaryPreferredMode,
SecondaryMode,
SecondaryPreferredMode,
NearestMode:
return true
default:
return false
}
}

View File

@@ -0,0 +1,88 @@
// 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 readpref
import (
"errors"
"time"
"go.mongodb.org/mongo-driver/v2/tag"
)
// ErrInvalidTagSet indicates that an invalid set of tags was specified.
var ErrInvalidTagSet = errors.New("an even number of tags must be specified")
// Option configures a read preference
type Option func(*ReadPref) error
// WithMaxStaleness sets the maximum staleness a
// server is allowed.
func WithMaxStaleness(ms time.Duration) Option {
return func(rp *ReadPref) error {
rp.maxStaleness = ms
rp.maxStalenessSet = true
return nil
}
}
// WithTags specifies a single tag set used to match replica set members. If no members match the
// tag set, read operations will return an error. To avoid errors if no members match the tag set, use
// [WithTagSets] and include an empty tag set as the last tag set in the list.
//
// The last call to [WithTags] or [WithTagSets] overrides all previous calls to either method.
//
// For more information about read preference tags, see
// https://www.mongodb.com/docs/manual/core/read-preference-tags/
func WithTags(tags ...string) Option {
return func(rp *ReadPref) error {
length := len(tags)
if length < 2 || length%2 != 0 {
return ErrInvalidTagSet
}
tagset := make(tag.Set, 0, length/2)
for i := 1; i < length; i += 2 {
tagset = append(tagset, tag.Tag{Name: tags[i-1], Value: tags[i]})
}
return WithTagSets(tagset)(rp)
}
}
// WithTagSets specifies a list of tag sets used to match replica set members. If the list contains
// multiple tag sets, members are matched against each tag set in succession until a match is found.
// Once a match is found, the remaining tag sets are ignored. If no members match any of the tag
// sets, the read operation returns with an error. To avoid an error if no members match any of the
// tag sets, include an empty tag set as the last tag set in the list.
//
// The last call to [WithTags] or [WithTagSets] overrides all previous calls to either method.
//
// For more information about read preference tags, see
// https://www.mongodb.com/docs/manual/core/read-preference-tags/
func WithTagSets(tagSets ...tag.Set) Option {
return func(rp *ReadPref) error {
rp.tagSets = tagSets
return nil
}
}
// WithHedgeEnabled specifies whether or not hedged reads should be enabled in
// the server. This feature requires MongoDB server version 4.4 or higher. For
// more information about hedged reads, see
// https://www.mongodb.com/docs/manual/core/sharded-cluster-query-router/#mongos-hedged-reads.
// If not specified, the default is to not send a value to the server, which
// will result in the server defaults being used.
//
// Deprecated: Hedged reads are deprecated in MongoDB 8.0 and may be removed in
// a future MongoDB version.
func WithHedgeEnabled(hedgeEnabled bool) Option {
return func(rp *ReadPref) error {
rp.hedgeEnabled = &hedgeEnabled
return nil
}
}

View File

@@ -0,0 +1,135 @@
// 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 readpref defines read preferences for MongoDB queries.
package readpref
import (
"bytes"
"errors"
"fmt"
"time"
"go.mongodb.org/mongo-driver/v2/tag"
)
var errInvalidReadPreference = errors.New("can not specify tags, max staleness, or hedge with mode primary")
// Primary constructs a read preference with a PrimaryMode.
func Primary() *ReadPref {
return &ReadPref{mode: PrimaryMode}
}
// PrimaryPreferred constructs a read preference with a PrimaryPreferredMode.
func PrimaryPreferred(opts ...Option) *ReadPref {
// New only returns an error with a mode of Primary
rp, _ := New(PrimaryPreferredMode, opts...)
return rp
}
// SecondaryPreferred constructs a read preference with a SecondaryPreferredMode.
func SecondaryPreferred(opts ...Option) *ReadPref {
// New only returns an error with a mode of Primary
rp, _ := New(SecondaryPreferredMode, opts...)
return rp
}
// Secondary constructs a read preference with a SecondaryMode.
func Secondary(opts ...Option) *ReadPref {
// New only returns an error with a mode of Primary
rp, _ := New(SecondaryMode, opts...)
return rp
}
// Nearest constructs a read preference with a NearestMode.
func Nearest(opts ...Option) *ReadPref {
// New only returns an error with a mode of Primary
rp, _ := New(NearestMode, opts...)
return rp
}
// New creates a new ReadPref.
func New(mode Mode, opts ...Option) (*ReadPref, error) {
rp := &ReadPref{
mode: mode,
}
if mode == PrimaryMode && len(opts) != 0 {
return nil, errInvalidReadPreference
}
for _, opt := range opts {
if opt == nil {
continue
}
err := opt(rp)
if err != nil {
return nil, err
}
}
return rp, nil
}
// ReadPref determines which servers are considered suitable for read operations.
type ReadPref struct {
maxStaleness time.Duration
maxStalenessSet bool
mode Mode
tagSets []tag.Set
hedgeEnabled *bool
}
// MaxStaleness is the maximum amount of time to allow
// a server to be considered eligible for selection. The
// second return value indicates if this value has been set.
func (r *ReadPref) MaxStaleness() (time.Duration, bool) {
return r.maxStaleness, r.maxStalenessSet
}
// Mode indicates the mode of the read preference.
func (r *ReadPref) Mode() Mode {
return r.mode
}
// TagSets are multiple tag sets indicating
// which servers should be considered.
func (r *ReadPref) TagSets() []tag.Set {
return r.tagSets
}
// HedgeEnabled returns whether or not hedged reads are enabled for this read
// preference. If this option was not specified during read preference
// construction, nil is returned.
//
// Deprecated: Hedged reads are deprecated in MongoDB 8.0 and may be removed in
// a future MongoDB version.
func (r *ReadPref) HedgeEnabled() *bool {
return r.hedgeEnabled
}
// String returns a human-readable description of the read preference.
func (r *ReadPref) String() string {
var b bytes.Buffer
b.WriteString(r.mode.String())
delim := "("
if r.maxStalenessSet {
fmt.Fprintf(&b, "%smaxStaleness=%v", delim, r.maxStaleness)
delim = " "
}
for _, tagSet := range r.tagSets {
fmt.Fprintf(&b, "%stagSet=%s", delim, tagSet.String())
delim = " "
}
if r.hedgeEnabled != nil {
fmt.Fprintf(&b, "%shedgeEnabled=%v", delim, *r.hedgeEnabled)
delim = " "
}
if delim != "(" {
b.WriteString(")")
}
return b.String()
}

View File

@@ -0,0 +1,286 @@
// 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 (
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
)
// ClientBulkWriteResult is the result type returned by a client-level BulkWrite operation.
type ClientBulkWriteResult struct {
// The number of documents inserted.
InsertedCount int64
// The number of documents matched by filters in update and replace operations.
MatchedCount int64
// The number of documents modified by update and replace operations.
ModifiedCount int64
// The number of documents deleted.
DeletedCount int64
// The number of documents upserted by update and replace operations.
UpsertedCount int64
// A map of operation index to the _id of each inserted document.
InsertResults map[int]ClientBulkWriteInsertResult
// A map of operation index to the _id of each updated document.
UpdateResults map[int]ClientBulkWriteUpdateResult
// A map of operation index to the _id of each deleted document.
DeleteResults map[int]ClientBulkWriteDeleteResult
// Operation performed with an acknowledged write. Values for other fields may
// not be deterministic if the write operation was unacknowledged.
Acknowledged bool
// HasVerboseResults indicates whether this result contains verbose results.
HasVerboseResults bool
}
// ClientBulkWriteInsertResult is the result type returned by a client-level bulk write of InsertOne operation.
type ClientBulkWriteInsertResult struct {
// The _id of the inserted document. A value generated by the driver will be of type primitive.ObjectID.
InsertedID any
}
// ClientBulkWriteUpdateResult is the result type returned from a client-level bulk write of UpdateOne, UpdateMany, and ReplaceOne operation.
type ClientBulkWriteUpdateResult struct {
MatchedCount int64 // The number of documents matched by the filter.
ModifiedCount int64 // The number of documents modified by the operation.
UpsertedID any // The _id field of the upserted document, or nil if no upsert was done.
}
// ClientBulkWriteDeleteResult is the result type returned by a client-level bulk write DeleteOne and DeleteMany operation.
type ClientBulkWriteDeleteResult struct {
DeletedCount int64 // The number of documents deleted.
}
// BulkWriteResult is the result type returned by a BulkWrite operation.
type BulkWriteResult struct {
// The number of documents inserted.
InsertedCount int64
// The number of documents matched by filters in update and replace operations.
MatchedCount int64
// The number of documents modified by update and replace operations.
ModifiedCount int64
// The number of documents deleted.
DeletedCount int64
// The number of documents upserted by update and replace operations.
UpsertedCount int64
// A map of operation index to the _id of each upserted document.
UpsertedIDs map[int64]any
// Operation performed with an acknowledged write. Values for other fields may
// not be deterministic if the write operation was unacknowledged.
Acknowledged bool
}
// InsertOneResult is the result type returned by an InsertOne operation.
type InsertOneResult struct {
// The _id of the inserted document. A value generated by the driver will be of type bson.ObjectID.
InsertedID any
// Operation performed with an acknowledged write. Values for other fields may
// not be deterministic if the write operation was unacknowledged.
Acknowledged bool
}
// InsertManyResult is a result type returned by an InsertMany operation.
type InsertManyResult struct {
// The _id values of the inserted documents. Values generated by the driver will be of type bson.ObjectID.
InsertedIDs []any
// Operation performed with an acknowledged write. Values for other fields may
// not be deterministic if the write operation was unacknowledged.
Acknowledged bool
}
// TODO(GODRIVER-2367): Remove the BSON struct tags on DeleteResult.
// DeleteResult is the result type returned by DeleteOne and DeleteMany operations.
type DeleteResult struct {
DeletedCount int64 // The number of documents deleted.
// Operation performed with an acknowledged write. Values for other fields may
// not be deterministic if the write operation was unacknowledged.
Acknowledged bool
}
// RewrapManyDataKeyResult is the result of the bulk write operation used to update the key vault collection with
// rewrapped data keys.
type RewrapManyDataKeyResult struct {
*BulkWriteResult
}
// ListDatabasesResult is a result of a ListDatabases operation.
type ListDatabasesResult struct {
// A slice containing one DatabaseSpecification for each database matched by the operation's filter.
Databases []DatabaseSpecification
// The total size of the database files of the returned databases in bytes.
// This will be the sum of the SizeOnDisk field for each specification in Databases.
TotalSize int64
}
func newListDatabasesResultFromOperation(res operation.ListDatabasesResult) ListDatabasesResult {
var ldr ListDatabasesResult
ldr.Databases = make([]DatabaseSpecification, 0, len(res.Databases))
for _, spec := range res.Databases {
ldr.Databases = append(
ldr.Databases,
DatabaseSpecification{Name: spec.Name, SizeOnDisk: spec.SizeOnDisk, Empty: spec.Empty},
)
}
ldr.TotalSize = res.TotalSize
return ldr
}
// DatabaseSpecification contains information for a database. This type is returned as part of ListDatabasesResult.
type DatabaseSpecification struct {
Name string // The name of the database.
SizeOnDisk int64 // The total size of the database files on disk in bytes.
Empty bool // Specifies whether or not the database is empty.
}
// UpdateResult is the result type returned from UpdateOne, UpdateMany, and ReplaceOne operations.
type UpdateResult struct {
MatchedCount int64 // The number of documents matched by the filter.
ModifiedCount int64 // The number of documents modified by the operation.
UpsertedCount int64 // The number of documents upserted by the operation.
UpsertedID any // The _id field of the upserted document, or nil if no upsert was done.
// Operation performed with an acknowledged write. Values for other fields may
// not be deterministic if the write operation was unacknowledged.
Acknowledged bool
}
// IndexSpecification represents an index in a database. This type is returned by the IndexView.ListSpecifications
// function and is also used in the CollectionSpecification type.
type IndexSpecification struct {
// The index name.
Name string
// The namespace for the index. This is a string in the format "databaseName.collectionName".
Namespace string
// The keys specification document for the index.
KeysDocument bson.Raw
// The index version.
Version int32
// The length of time, in seconds, for documents to remain in the collection. The default value is 0, which means
// that documents will remain in the collection until they're explicitly deleted or the collection is dropped.
ExpireAfterSeconds *int32
// If true, the index will only reference documents that contain the fields specified in the index. The default is
// false.
Sparse *bool
// If true, the collection will not accept insertion or update of documents where the index key value matches an
// existing value in the index. The default is false.
Unique *bool
// The clustered index.
Clustered *bool
}
type indexListSpecificationResponse struct {
Name string `bson:"name"`
Namespace string `bson:"ns"`
KeysDocument bson.Raw `bson:"key"`
Version int32 `bson:"v"`
ExpireAfterSeconds *int32 `bson:"expireAfterSeconds"`
Sparse *bool `bson:"sparse"`
Unique *bool `bson:"unique"`
Clustered *bool `bson:"clustered"`
}
// CollectionSpecification represents a collection in a database. This type is returned by the
// Database.ListCollectionSpecifications function.
type CollectionSpecification struct {
// The collection name.
Name string
// The type of the collection. This will either be "collection" or "view".
Type string
// Whether or not the collection is readOnly.
ReadOnly bool
// The collection UUID as a bson.Binary with subtype 4.
UUID *bson.Binary
// A document containing the options used to construct the collection.
Options bson.Raw
// An IndexSpecification instance with details about the collection's _id index.
IDIndex IndexSpecification
}
// DistinctResult represents an array of BSON data returned from an operation.
// If the operation resulted in an error, all DistinctResult methods will return
// that error. If the operation did not return any data, all DistinctResult
// methods will return ErrNoDocuments.
type DistinctResult struct {
err error
arr bson.RawArray
reg *bson.Registry
bsonOpts *options.BSONOptions
}
// Decode will unmarshal the array represented by this DistinctResult into v. If
// there was an error from the operation that created this DistinctReuslt, that
// error will be returned. If the operation returned no array, Decode will
// return ErrNoDocuments.
//
// If the operation was successful and returned an array, Decode will return any
// errors from the unmarshalling process without any modification. If v is nil
// or is a typed nil, an error will be returned.
func (dr *DistinctResult) Decode(v any) error {
doc := bsoncore.NewDocumentBuilder().
AppendValue("arr", bsoncore.Value{
Type: bsoncore.TypeArray,
Data: dr.arr,
}).Build()
dec := getDecoder(doc, dr.bsonOpts, dr.reg)
return dec.Decode(&struct{ Arr any }{Arr: v})
}
// Err provides a way to check for query errors without calling Decode. Err
// returns the error, if any, that was encountered while running the operation.
// If the operation was successful but did not return any documents, Err returns
// ErrNoDocuments. If this error is not nil, this error will also be returned
// from Decode.
func (dr *DistinctResult) Err() error {
return dr.err
}
// Raw returns the document represented by this DistinctResult as a bson.Raw. If
// there was an error from the operation that created this DistinctResult, both
// the result and that error will be returned. If the operation returned no
// documents, this will return (nil, ErrNoDocuments).
func (dr *DistinctResult) Raw() (bson.RawArray, error) {
if dr.err != nil {
return nil, dr.err
}
return dr.arr, nil
}

View File

@@ -0,0 +1,290 @@
// Copyright (C) MongoDB, Inc. 2023-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"
"strconv"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/mongoutil"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
)
// SearchIndexView is a type that can be used to create, drop, list and update
// search indexes on a collection. A SearchIndexView for a collection can be
// created by a call to Collection.SearchIndexes().
//
// Search index commands are asynchronous and return from the server before
// the index is successfully updated, created or dropped. In order to determine
// when an index has been created / updated, users are expected to run the
// listSearchIndexes repeatedly until index changes appear.
type SearchIndexView struct {
coll *Collection
}
// SearchIndexModel represents a new search index to be created.
type SearchIndexModel struct {
// A document describing the definition for the search index. It cannot be nil.
// See https://www.mongodb.com/docs/atlas/atlas-search/create-index/ for reference.
Definition any
// The search index options.
Options *options.SearchIndexesOptionsBuilder
}
// List executes a listSearchIndexes command and returns a cursor over the search indexes in the collection.
//
// The name parameter specifies the index name. A nil pointer matches all indexes.
//
// The opts parameter can be used to specify options for this operation (see the options.ListSearchIndexesOptions
// documentation).
func (siv SearchIndexView) List(
ctx context.Context,
searchIdxOpts options.Lister[options.SearchIndexesOptions],
opts ...options.Lister[options.ListSearchIndexesOptions],
) (*Cursor, error) {
if ctx == nil {
ctx = context.Background()
}
searchIdxArgs, err := mongoutil.NewOptions[options.SearchIndexesOptions](searchIdxOpts)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
index := bson.D{}
if searchIdxArgs != nil && searchIdxArgs.Name != nil {
index = bson.D{{"name", *searchIdxArgs.Name}}
}
args, err := mongoutil.NewOptions[options.ListSearchIndexesOptions](opts...)
if err != nil {
return nil, err
}
aggregateOpts := mongoutil.NewOptionsLister(args.AggregateOptions, nil)
return siv.coll.Aggregate(ctx, Pipeline{{{"$listSearchIndexes", index}}}, aggregateOpts)
}
// CreateOne executes a createSearchIndexes command to create a search index on the collection and returns the name of the new
// search index. See the SearchIndexView.CreateMany documentation for more information and an example.
//
// This is an asynchronous operation.
func (siv SearchIndexView) CreateOne(
ctx context.Context,
model SearchIndexModel,
opts ...options.Lister[options.CreateSearchIndexesOptions],
) (string, error) {
names, err := siv.CreateMany(ctx, []SearchIndexModel{model}, opts...)
if err != nil {
return "", err
}
return names[0], nil
}
// CreateMany executes a createSearchIndexes command to create multiple search indexes on the collection and returns
// the names of the new search indexes.
//
// For each SearchIndexModel in the models parameter, the index name can be specified.
//
// The opts parameter can be used to specify options for this operation (see the options.CreateSearchIndexesOptions
// documentation).
//
// This is an asynchronous operation.
func (siv SearchIndexView) CreateMany(
ctx context.Context,
models []SearchIndexModel,
_ ...options.Lister[options.CreateSearchIndexesOptions],
) ([]string, error) {
var indexes bsoncore.Document
aidx, indexes := bsoncore.AppendArrayStart(indexes)
for i, model := range models {
if model.Definition == nil {
return nil, fmt.Errorf("search index model definition cannot be nil")
}
definition, err := marshal(model.Definition, siv.coll.bsonOpts, siv.coll.registry)
if err != nil {
return nil, err
}
var iidx int32
iidx, indexes = bsoncore.AppendDocumentElementStart(indexes, strconv.Itoa(i))
if model.Options != nil {
searchIndexArgs, err := mongoutil.NewOptions[options.SearchIndexesOptions](model.Options)
if err != nil {
return nil, fmt.Errorf("failed to construct options from builder: %w", err)
}
if searchIndexArgs.Name != nil {
indexes = bsoncore.AppendStringElement(indexes, "name", *searchIndexArgs.Name)
}
if searchIndexArgs.Type != nil {
indexes = bsoncore.AppendStringElement(indexes, "type", *searchIndexArgs.Type)
}
}
indexes = bsoncore.AppendDocumentElement(indexes, "definition", definition)
indexes, err = bsoncore.AppendDocumentEnd(indexes, iidx)
if err != nil {
return nil, err
}
}
indexes, err := bsoncore.AppendArrayEnd(indexes, aidx)
if err != nil {
return nil, err
}
sess := sessionFromContext(ctx)
if sess == nil && siv.coll.client.sessionPool != nil {
sess = session.NewImplicitClientSession(siv.coll.client.sessionPool, siv.coll.client.id)
defer sess.EndSession()
}
err = siv.coll.client.validSession(sess)
if err != nil {
return nil, err
}
selector := makePinnedSelector(sess, siv.coll.writeSelector)
op := operation.NewCreateSearchIndexes(indexes).
Session(sess).CommandMonitor(siv.coll.client.monitor).
ServerSelector(selector).ClusterClock(siv.coll.client.clock).
Collection(siv.coll.name).Database(siv.coll.db.name).
Deployment(siv.coll.client.deployment).ServerAPI(siv.coll.client.serverAPI).
Timeout(siv.coll.client.timeout).Authenticator(siv.coll.client.authenticator)
err = op.Execute(ctx)
if err != nil {
_, err = processWriteError(err)
return nil, err
}
indexesCreated := op.Result().IndexesCreated
names := make([]string, 0, len(indexesCreated))
for _, index := range indexesCreated {
names = append(names, index.Name)
}
return names, nil
}
// DropOne executes a dropSearchIndexes operation to drop a search index on the collection.
//
// The name parameter should be the name of the search index to drop. If the name is "*", ErrMultipleIndexDrop will be returned
// without running the command because doing so would drop all search indexes.
//
// The opts parameter can be used to specify options for this operation (see the options.DropSearchIndexOptions
// documentation).
//
// This is an asynchronous operation.
func (siv SearchIndexView) DropOne(
ctx context.Context,
name string,
_ ...options.Lister[options.DropSearchIndexOptions],
) error {
if name == "*" {
return ErrMultipleIndexDrop
}
if ctx == nil {
ctx = context.Background()
}
sess := sessionFromContext(ctx)
if sess == nil && siv.coll.client.sessionPool != nil {
sess = session.NewImplicitClientSession(siv.coll.client.sessionPool, siv.coll.client.id)
defer sess.EndSession()
}
err := siv.coll.client.validSession(sess)
if err != nil {
return err
}
selector := makePinnedSelector(sess, siv.coll.writeSelector)
op := operation.NewDropSearchIndex(name).
Session(sess).CommandMonitor(siv.coll.client.monitor).
ServerSelector(selector).ClusterClock(siv.coll.client.clock).
Collection(siv.coll.name).Database(siv.coll.db.name).
Deployment(siv.coll.client.deployment).ServerAPI(siv.coll.client.serverAPI).
Timeout(siv.coll.client.timeout).Authenticator(siv.coll.client.authenticator)
err = op.Execute(ctx)
var de driver.Error
if errors.As(err, &de) && de.NamespaceNotFound() {
return nil
}
return err
}
// UpdateOne executes a updateSearchIndex operation to update a search index on the collection.
//
// The name parameter should be the name of the search index to update.
//
// The definition parameter is a document describing the definition for the search index. It cannot be nil.
//
// The opts parameter can be used to specify options for this operation (see the options.UpdateSearchIndexOptions
// documentation).
//
// This is an asynchronous operation.
func (siv SearchIndexView) UpdateOne(
ctx context.Context,
name string,
definition any,
_ ...options.Lister[options.UpdateSearchIndexOptions],
) error {
if definition == nil {
return fmt.Errorf("search index definition cannot be nil")
}
indexDefinition, err := marshal(definition, siv.coll.bsonOpts, siv.coll.registry)
if err != nil {
return err
}
if ctx == nil {
ctx = context.Background()
}
sess := sessionFromContext(ctx)
if sess == nil && siv.coll.client.sessionPool != nil {
sess = session.NewImplicitClientSession(siv.coll.client.sessionPool, siv.coll.client.id)
defer sess.EndSession()
}
err = siv.coll.client.validSession(sess)
if err != nil {
return err
}
selector := makePinnedSelector(sess, siv.coll.writeSelector)
op := operation.NewUpdateSearchIndex(name, indexDefinition).
Session(sess).CommandMonitor(siv.coll.client.monitor).
ServerSelector(selector).ClusterClock(siv.coll.client.clock).
Collection(siv.coll.name).Database(siv.coll.db.name).
Deployment(siv.coll.client.deployment).ServerAPI(siv.coll.client.serverAPI).
Timeout(siv.coll.client.timeout).Authenticator(siv.coll.client.authenticator)
return op.Execute(ctx)
}

View File

@@ -0,0 +1,343 @@
// 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"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/internal/mongoutil"
"go.mongodb.org/mongo-driver/v2/internal/serverselector"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/v2/x/mongo/driver/session"
)
// ErrWrongClient is returned when a user attempts to pass in a session created by a different client than
// the method call is using.
var ErrWrongClient = errors.New("session was not created by this client")
var withTransactionTimeout = 120 * time.Second
// Session is a MongoDB logical session. Sessions can be used to enable causal
// consistency for a group of operations or to execute operations in an ACID
// transaction. A new Session can be created from a Client instance. A Session
// created from a Client must only be used to execute operations using that
// Client or a Database or Collection created from that Client. For more
// information about sessions, and their use cases, see
// https://www.mongodb.com/docs/manual/reference/server-sessions/,
// https://www.mongodb.com/docs/manual/core/read-isolation-consistency-recency/#causal-consistency, and
// https://www.mongodb.com/docs/manual/core/transactions/.
//
// Implementations of Session are not safe for concurrent use by multiple
// goroutines.
type Session struct {
clientSession *session.Client
client *Client
deployment driver.Deployment
didCommitAfterStart bool // true if commit was called after start with no other operations
}
type sessionKey struct{}
// NewSessionContext returns a Context that holds the given Session. If the
// Context already contains a Session, that Session will be replaced with the
// one provided.
//
// The returned Context can be used with Collection methods like
// [Collection.InsertOne] or [Collection.Find] to run operations in a Session.
func NewSessionContext(parent context.Context, sess *Session) context.Context {
return context.WithValue(parent, sessionKey{}, sess)
}
// SessionFromContext extracts the mongo.Session object stored in a Context. This can be used on a SessionContext that
// was created implicitly through one of the callback-based session APIs or explicitly by calling NewSessionContext. If
// there is no Session stored in the provided Context, nil is returned.
func SessionFromContext(ctx context.Context) *Session {
val := ctx.Value(sessionKey{})
if val == nil {
return nil
}
sess, ok := val.(*Session)
if !ok {
return nil
}
return sess
}
// ID returns the current ID document associated with the session. The ID
// document is in the form {"id": <BSON binary value>}.
func (s *Session) ID() bson.Raw {
return bson.Raw(s.clientSession.SessionID)
}
// EndSession aborts any existing transactions and close the session.
func (s *Session) EndSession(ctx context.Context) {
if s.clientSession.TransactionInProgress() {
// ignore all errors aborting during an end session
_ = s.AbortTransaction(ctx)
}
s.clientSession.EndSession()
}
// WithTransaction starts a transaction on this session and runs the fn
// callback. Errors with the TransientTransactionError and
// UnknownTransactionCommitResult labels are retried for up to 120 seconds.
// Inside the callback, the SessionContext must be used as the Context parameter
// for any operations that should be part of the transaction. If the ctx
// parameter already has a Session attached to it, it will be replaced by this
// session. The fn callback may be run multiple times during WithTransaction due
// to retry attempts, so it must be idempotent.
//
// If a command inside the callback fn fails, it may cause the transaction on
// the server to be aborted. This situation is normally handled transparently by
// the driver. However, if the application does not return that error from the
// fn, the driver will not be able to determine whether the transaction was
// aborted or not. The driver will then retry the block indefinitely.
//
// To avoid this situation, the application MUST NOT silently handle errors
// within the callback fn. If the application needs to handle errors within the
// block, it MUST return them after doing so.
//
// Non-retryable operation errors or any operation errors that occur after the
// timeout expires will be returned without retrying. If the callback fails, the
// driver will call AbortTransaction. Because this method must succeed to ensure
// that server-side resources are properly cleaned up, context deadlines and
// cancellations will not be respected during this call. For a usage example,
// see the Client.StartSession method documentation.
func (s *Session) WithTransaction(
ctx context.Context,
fn func(ctx context.Context) (any, error),
opts ...options.Lister[options.TransactionOptions],
) (any, error) {
timeout := time.NewTimer(withTransactionTimeout)
defer timeout.Stop()
var err error
for {
err = s.StartTransaction(opts...)
if err != nil {
return nil, err
}
res, err := fn(NewSessionContext(ctx, s))
if err != nil {
if s.clientSession.TransactionRunning() {
// Wrap the user-provided Context in a new one that behaves like context.Background() for deadlines and
// cancellations, but forwards Value requests to the original one.
_ = s.AbortTransaction(newBackgroundContext(ctx))
}
select {
case <-timeout.C:
return nil, err
default:
}
if errorHasLabel(err, driver.TransientTransactionError) {
continue
}
return res, err
}
// Check if callback intentionally aborted and, if so, return immediately
// with no error.
err = s.clientSession.CheckAbortTransaction()
if err != nil {
return res, nil
}
// If context has errored, run AbortTransaction and return, as the CommitLoop
// has no chance of succeeding.
//
// Aborting after a failed CommitTransaction is dangerous. Failed transaction
// commits may unpin the session server-side, and subsequent transaction aborts
// may run on a new mongos which could end up with commit and abort being executed
// simultaneously.
if ctx.Err() != nil {
// Wrap the user-provided Context in a new one that behaves like context.Background() for deadlines and
// cancellations, but forwards Value requests to the original one.
_ = s.AbortTransaction(newBackgroundContext(ctx))
return nil, ctx.Err()
}
CommitLoop:
for {
err = s.CommitTransaction(newBackgroundContext(ctx))
// End when error is nil, as transaction has been committed.
if err == nil {
return res, nil
}
select {
case <-timeout.C:
return res, err
default:
}
var cerr CommandError
if errors.As(err, &cerr) {
if cerr.HasErrorLabel(driver.UnknownTransactionCommitResult) && !cerr.IsMaxTimeMSExpiredError() {
continue
}
if cerr.HasErrorLabel(driver.TransientTransactionError) {
break CommitLoop
}
}
return res, err
}
}
}
// StartTransaction starts a new transaction. This method returns an error if
// there is already a transaction in-progress for this session.
func (s *Session) StartTransaction(opts ...options.Lister[options.TransactionOptions]) error {
err := s.clientSession.CheckStartTransaction()
if err != nil {
return err
}
s.didCommitAfterStart = false
args, err := mongoutil.NewOptions[options.TransactionOptions](opts...)
if err != nil {
return fmt.Errorf("failed to construct options from builder: %w", err)
}
coreOpts := &session.TransactionOptions{
ReadConcern: args.ReadConcern,
ReadPreference: args.ReadPreference,
WriteConcern: args.WriteConcern,
}
return s.clientSession.StartTransaction(coreOpts)
}
// AbortTransaction aborts the active transaction for this session. This method
// returns an error if there is no active transaction for this session or if the
// transaction has been committed or aborted.
func (s *Session) AbortTransaction(ctx context.Context) error {
err := s.clientSession.CheckAbortTransaction()
if err != nil {
return err
}
// Do not run the abort command if the transaction is in starting state
if s.clientSession.TransactionStarting() || s.didCommitAfterStart {
return s.clientSession.AbortTransaction()
}
selector := makePinnedSelector(s.clientSession, &serverselector.Write{})
s.clientSession.Aborting = true
_ = operation.NewAbortTransaction().Session(s.clientSession).ClusterClock(s.client.clock).Database("admin").
Deployment(s.deployment).WriteConcern(s.clientSession.CurrentWc).ServerSelector(selector).
Retry(driver.RetryOncePerCommand).CommandMonitor(s.client.monitor).
RecoveryToken(bsoncore.Document(s.clientSession.RecoveryToken)).ServerAPI(s.client.serverAPI).
Authenticator(s.client.authenticator).Logger(s.client.logger).Execute(ctx)
s.clientSession.Aborting = false
_ = s.clientSession.AbortTransaction()
return nil
}
// CommitTransaction commits the active transaction for this session. This
// method returns an error if there is no active transaction for this session or
// if the transaction has been aborted.
func (s *Session) CommitTransaction(ctx context.Context) error {
err := s.clientSession.CheckCommitTransaction()
if err != nil {
return err
}
// Do not run the commit command if the transaction is in started state
if s.clientSession.TransactionStarting() || s.didCommitAfterStart {
s.didCommitAfterStart = true
return s.clientSession.CommitTransaction()
}
if s.clientSession.TransactionCommitted() {
s.clientSession.RetryingCommit = true
}
selector := makePinnedSelector(s.clientSession, &serverselector.Write{})
s.clientSession.Committing = true
op := operation.NewCommitTransaction().
Session(s.clientSession).ClusterClock(s.client.clock).Database("admin").Deployment(s.deployment).
WriteConcern(s.clientSession.CurrentWc).ServerSelector(selector).Retry(driver.RetryOncePerCommand).
CommandMonitor(s.client.monitor).RecoveryToken(bsoncore.Document(s.clientSession.RecoveryToken)).
ServerAPI(s.client.serverAPI).Authenticator(s.client.authenticator).Logger(s.client.logger)
err = op.Execute(ctx)
// Return error without updating transaction state if it is a timeout, as the transaction has not
// actually been committed.
if IsTimeout(err) {
return wrapErrors(err)
}
s.clientSession.Committing = false
commitErr := s.clientSession.CommitTransaction()
// We set the write concern to majority for subsequent calls to CommitTransaction.
s.clientSession.UpdateCommitTransactionWriteConcern()
if err != nil {
return wrapErrors(err)
}
return commitErr
}
// ClusterTime returns the current cluster time document associated with the
// session.
func (s *Session) ClusterTime() bson.Raw {
return s.clientSession.ClusterTime
}
// SnapshotTime returns the current snapshot time associated with the session.
func (s *Session) SnapshotTime() bson.Timestamp {
return s.clientSession.SnapshotTime
}
// AdvanceClusterTime advances the cluster time for a session. This method
// returns an error if the session has ended.
func (s *Session) AdvanceClusterTime(d bson.Raw) error {
return s.clientSession.AdvanceClusterTime(d)
}
// OperationTime returns the current operation time document associated with the
// session.
func (s *Session) OperationTime() *bson.Timestamp {
return s.clientSession.OperationTime
}
// AdvanceOperationTime advances the operation time for a session. This method
// returns an error if the session has ended.
func (s *Session) AdvanceOperationTime(ts *bson.Timestamp) error {
return s.clientSession.AdvanceOperationTime(ts)
}
// Client is the Client associated with the session.
func (s *Session) Client() *Client {
return s.client
}
// sessionFromContext checks for a sessionImpl in the argued context and returns the session if it
// exists
func sessionFromContext(ctx context.Context) *session.Client {
if ses := SessionFromContext(ctx); ses != nil {
return ses.clientSession
}
return nil
}

View File

@@ -0,0 +1,140 @@
// 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"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo/options"
)
// ErrNoDocuments is returned by SingleResult methods when the operation that created the SingleResult did not return
// any documents.
var ErrNoDocuments = errors.New("mongo: no documents in result")
// SingleResult represents a single document returned from an operation. If the operation resulted in an error, all
// SingleResult methods will return that error. If the operation did not return any documents, all SingleResult methods
// will return ErrNoDocuments.
type SingleResult struct {
ctx context.Context
err error
cur *Cursor
rdr bson.Raw
bsonOpts *options.BSONOptions
reg *bson.Registry
// Operation performed with an acknowledged write. Values returned by
// SingleResult methods may not be deterministic if the write operation was
// unacknowledged and so should not be relied upon.
Acknowledged bool
}
// NewSingleResultFromDocument creates a SingleResult with the provided error, registry, and an underlying Cursor pre-loaded with
// the provided document, error and registry. If no registry is provided, bson.NewRegistry() will be used. If an error distinct
// from the one provided occurs during creation of the SingleResult, that error will be stored on the returned SingleResult.
//
// The document parameter must be a non-nil document.
func NewSingleResultFromDocument(
document any,
err error,
registry *bson.Registry,
) *SingleResult {
if document == nil {
return &SingleResult{err: ErrNilDocument}
}
if registry == nil {
registry = defaultRegistry
}
cur, createErr := NewCursorFromDocuments([]any{document}, err, registry)
if createErr != nil {
return &SingleResult{err: createErr}
}
return &SingleResult{
cur: cur,
err: err,
reg: registry,
}
}
// Decode will unmarshal the document represented by this SingleResult into v. If there was an error from the operation
// that created this SingleResult, that error will be returned. If the operation returned no documents, Decode will
// return ErrNoDocuments.
//
// If the operation was successful and returned a document, Decode will return any errors from the unmarshalling process
// without any modification. If v is nil or is a typed nil, an error will be returned.
func (sr *SingleResult) Decode(v any) error {
if sr.err != nil {
return sr.err
}
if sr.reg == nil {
return bson.ErrNilRegistry
}
if sr.err = sr.setRdrContents(); sr.err != nil {
return sr.err
}
dec := getDecoder(sr.rdr, sr.bsonOpts, sr.reg)
return dec.Decode(v)
}
// Raw returns the document represented by this SingleResult as a bson.Raw. If
// there was an error from the operation that created this SingleResult, both
// the result and that error will be returned. If the operation returned no
// documents, this will return (nil, ErrNoDocuments).
func (sr *SingleResult) Raw() (bson.Raw, error) {
if sr.err != nil {
return sr.rdr, sr.err
}
if sr.err = sr.setRdrContents(); sr.err != nil {
return nil, sr.err
}
return sr.rdr, nil
}
// setRdrContents will set the contents of rdr by iterating the underlying cursor if necessary.
func (sr *SingleResult) setRdrContents() error {
switch {
case sr.err != nil:
return sr.err
case sr.rdr != nil:
return nil
case sr.cur != nil:
defer sr.cur.Close(sr.ctx)
if !sr.cur.Next(sr.ctx) {
if err := sr.cur.Err(); err != nil {
return err
}
return ErrNoDocuments
}
sr.rdr = sr.cur.Current
return nil
}
return ErrNoDocuments
}
// Err provides a way to check for query errors without calling Decode. Err returns the error, if
// any, that was encountered while running the operation. If the operation was successful but did
// not return any documents, Err returns ErrNoDocuments. If this error is not nil, this error will
// also be returned from Decode.
func (sr *SingleResult) Err() error {
sr.err = sr.setRdrContents()
return sr.err
}

View File

@@ -0,0 +1,132 @@
// 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 writeconcern defines write concerns for MongoDB operations.
//
// For more information about MongoDB write concerns, see
// https://www.mongodb.com/docs/manual/reference/write-concern/
package writeconcern
// WCMajority can be used to create a WriteConcern with a W value of "majority".
const WCMajority = "majority"
// A WriteConcern defines a MongoDB write concern, which describes the level of acknowledgment
// requested from MongoDB for write operations to a standalone mongod, to replica sets, or to
// sharded clusters.
//
// For more information about MongoDB write concerns, see
// https://www.mongodb.com/docs/manual/reference/write-concern/
type WriteConcern struct {
// W requests acknowledgment that the write operation has propagated to a
// specified number of mongod instances or to mongod instances with
// specified tags. It sets the "w" option in a MongoDB write concern.
//
// W values must be a string or an int.
//
// Common values are:
// - "majority": requests acknowledgment that write operations have been
// durably committed to the calculated majority of the data-bearing
// voting members.
// - 1: requests acknowledgment that write operations have been written
// to 1 node.
// - 0: requests no acknowledgment of write operations
//
// For more information about the "w" option, see
// https://www.mongodb.com/docs/manual/reference/write-concern/#w-option
W any
// Journal requests acknowledgment from MongoDB that the write operation has
// been written to the on-disk journal. It sets the "j" option in a MongoDB
// write concern.
//
// For more information about the "j" option, see
// https://www.mongodb.com/docs/manual/reference/write-concern/#j-option
Journal *bool
}
// Unacknowledged returns a WriteConcern that requests no acknowledgment of
// write operations.
//
// For more information about write concern "w: 0", see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-writeconcern.-number-
func Unacknowledged() *WriteConcern {
return &WriteConcern{W: 0}
}
// W1 returns a WriteConcern that requests acknowledgment that write operations
// have been written to memory on one node (e.g. the standalone mongod or the
// primary in a replica set).
//
// For more information about write concern "w: 1", see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-writeconcern.-number-
func W1() *WriteConcern {
return &WriteConcern{W: 1}
}
// Journaled returns a WriteConcern that requests acknowledgment that write
// operations have been written to the on-disk journal on MongoDB.
//
// The database's default value for "w" determines how many nodes must write to
// their on-disk journal before the write operation is acknowledged.
//
// For more information about write concern "j: true", see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-ournal
func Journaled() *WriteConcern {
journal := true
return &WriteConcern{Journal: &journal}
}
// Majority returns a WriteConcern that requests acknowledgment that write
// operations have been durably committed to the calculated majority of the
// data-bearing voting members.
//
// Write concern "w: majority" typically requires write operations to be written
// to the on-disk journal before they are acknowledged, unless journaling is
// disabled on MongoDB or the "writeConcernMajorityJournalDefault" replica set
// configuration is set to false.
//
// For more information about write concern "w: majority", see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-writeconcern.-majority-
func Majority() *WriteConcern {
return &WriteConcern{W: WCMajority}
}
// Custom returns a WriteConcern that requests acknowledgment that write
// operations have propagated to tagged members that satisfy the custom write
// concern defined in "settings.getLastErrorModes".
//
// For more information about custom write concern names, see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-writeconcern.-custom-write-concern-name-
func Custom(tag string) *WriteConcern {
return &WriteConcern{W: tag}
}
// Acknowledged indicates whether or not a write with the given write concern will be acknowledged.
func (wc *WriteConcern) Acknowledged() bool {
// Only {w: 0} or {w: 0, j: false} are an unacknowledged write concerns. All other values are
// acknowledged.
return wc == nil || wc.W != 0 || (wc.Journal != nil && *wc.Journal)
}
// IsValid returns true if the WriteConcern is valid.
func (wc *WriteConcern) IsValid() bool {
if wc == nil {
return true
}
switch w := wc.W.(type) {
case int:
// A write concern with {w: int} must have a non-negative value and
// cannot have the combination {w: 0, j: true}.
return w >= 0 && (w > 0 || wc.Journal == nil || !*wc.Journal)
case string, nil:
// A write concern with {w: string} or no w specified is always valid.
return true
default:
// A write concern with an unsupported w type is not valid.
return false
}
}