升级流程引擎配置文件版本

This commit is contained in:
hoteas
2022-10-19 21:32:34 +08:00
parent cdba8af129
commit 78337c14ec
501 changed files with 157534 additions and 0 deletions
+49
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 address // import "go.mongodb.org/mongo-driver/mongo/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 {
// 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())
}
+48
View File
@@ -0,0 +1,48 @@
// 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"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/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.DocumentSequence
// 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
}
// 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
}
+532
View File
@@ -0,0 +1,532 @@
// 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/bson/bsoncodec"
"go.mongodb.org/mongo-driver/mongo/description"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/x/mongo/driver/session"
)
type bulkWriteBatch struct {
models []WriteModel
canRetry bool
indexes []int
}
// bulkWrite perfoms a bulkwrite operation
type bulkWrite struct {
comment interface{}
ordered *bool
bypassDocumentValidation *bool
models []WriteModel
session *session.Client
collection *Collection
selector description.ServerSelector
writeConcern *writeconcern.WriteConcern
result BulkWriteResult
let interface{}
}
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]interface{}),
}
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 && 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
if lastErr != nil {
_, lastErr = processWriteError(lastErr)
return lastErr
}
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]interface{}),
}
batchErr := BulkWriteException{}
var writeErrors []driver.WriteError
switch batch.models[0].(type) {
case *InsertOneModel:
res, err := bw.runInsert(ctx, batch)
if err != nil {
writeErr, ok := err.(driver.WriteCommandError)
if !ok {
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 {
writeErr, ok := err.(driver.WriteCommandError)
if !ok {
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 {
writeErr, ok := err.(driver.WriteCommandError)
if !ok {
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) (operation.InsertResult, error) {
docs := make([]bsoncore.Document, len(batch.models))
var i int
for _, model := range batch.models {
converted := model.(*InsertOneModel)
doc, _, err := transformAndEnsureID(bw.collection.registry, converted.Document)
if err != nil {
return operation.InsertResult{}, err
}
docs[i] = doc
i++
}
op := operation.NewInsert(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).
ServerAPI(bw.collection.client.serverAPI).Timeout(bw.collection.client.timeout)
if bw.comment != nil {
comment, err := transformValue(bw.collection.registry, bw.comment, true, "comment")
if err != nil {
return op.Result(), err
}
op.Comment(comment)
}
if bw.bypassDocumentValidation != nil && *bw.bypassDocumentValidation {
op = op.BypassDocumentValidation(*bw.bypassDocumentValidation)
}
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)
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.registry)
hasHint = hasHint || (converted.Hint != nil)
case *DeleteManyModel:
doc, err = createDeleteDoc(converted.Filter, converted.Collation, converted.Hint, false, 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)
if bw.comment != nil {
comment, err := transformValue(bw.collection.registry, bw.comment, true, "comment")
if err != nil {
return op.Result(), err
}
op.Comment(comment)
}
if bw.let != nil {
let, err := transformBsoncoreDocument(bw.collection.registry, bw.let, true, "let")
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)
err := op.Execute(ctx)
return op.Result(), err
}
func createDeleteDoc(filter interface{}, collation *options.Collation, hint interface{}, deleteOne bool,
registry *bsoncodec.Registry) (bsoncore.Document, error) {
f, err := transformBsoncoreDocument(registry, filter, true, "filter")
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", collation.ToDocument())
}
if hint != nil {
hintVal, err := transformValue(registry, hint, false, "hint")
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 = createUpdateDoc(converted.Filter, converted.Replacement, converted.Hint, nil, converted.Collation, converted.Upsert, false,
false, bw.collection.registry)
hasHint = hasHint || (converted.Hint != nil)
case *UpdateOneModel:
doc, err = createUpdateDoc(converted.Filter, converted.Update, converted.Hint, converted.ArrayFilters, converted.Collation, converted.Upsert, false,
true, bw.collection.registry)
hasHint = hasHint || (converted.Hint != nil)
hasArrayFilters = hasArrayFilters || (converted.ArrayFilters != nil)
case *UpdateManyModel:
doc, err = createUpdateDoc(converted.Filter, converted.Update, converted.Hint, converted.ArrayFilters, converted.Collation, converted.Upsert, true,
true, 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)
if bw.comment != nil {
comment, err := transformValue(bw.collection.registry, bw.comment, true, "comment")
if err != nil {
return op.Result(), err
}
op.Comment(comment)
}
if bw.let != nil {
let, err := transformBsoncoreDocument(bw.collection.registry, bw.let, true, "let")
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)
err := op.Execute(ctx)
return op.Result(), err
}
func createUpdateDoc(
filter interface{},
update interface{},
hint interface{},
arrayFilters *options.ArrayFilters,
collation *options.Collation,
upsert *bool,
multi bool,
checkDollarKey bool,
registry *bsoncodec.Registry,
) (bsoncore.Document, error) {
f, err := transformBsoncoreDocument(registry, filter, true, "filter")
if err != nil {
return nil, err
}
uidx, updateDoc := bsoncore.AppendDocumentStart(nil)
updateDoc = bsoncore.AppendDocumentElement(updateDoc, "q", f)
u, err := transformUpdateValue(registry, update, checkDollarKey)
if err != nil {
return nil, err
}
updateDoc = bsoncore.AppendValueElement(updateDoc, "u", u)
if multi {
updateDoc = bsoncore.AppendBooleanElement(updateDoc, "multi", multi)
}
if arrayFilters != nil {
arr, err := arrayFilters.ToArrayDocument()
if err != nil {
return nil, err
}
updateDoc = bsoncore.AppendArrayElement(updateDoc, "arrayFilters", arr)
}
if collation != nil {
updateDoc = bsoncore.AppendDocumentElement(updateDoc, "collation", bsoncore.Document(collation.ToDocument()))
}
if upsert != nil {
updateDoc = bsoncore.AppendBooleanElement(updateDoc, "upsert", *upsert)
}
if hint != nil {
hintVal, err := transformValue(registry, hint, false, "hint")
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
)
+305
View File
@@ -0,0 +1,305 @@
// 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/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.
type InsertOneModel struct {
Document interface{}
}
// 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 interface{}) *InsertOneModel {
iom.Document = doc
return iom
}
func (*InsertOneModel) writeModel() {}
// DeleteOneModel is used to delete at most one document in a BulkWriteOperation.
type DeleteOneModel struct {
Filter interface{}
Collation *options.Collation
Hint interface{}
}
// 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 interface{}) *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 >= 3.4 will
// return an error if this option is specified. For server versions < 3.4, the driver will return a client-side 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 interface{}) *DeleteOneModel {
dom.Hint = hint
return dom
}
func (*DeleteOneModel) writeModel() {}
// DeleteManyModel is used to delete multiple documents in a BulkWrite operation.
type DeleteManyModel struct {
Filter interface{}
Collation *options.Collation
Hint interface{}
}
// 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 interface{}) *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 >= 3.4 will
// return an error if this option is specified. For server versions < 3.4, the driver will return a client-side 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 interface{}) *DeleteManyModel {
dmm.Hint = hint
return dmm
}
func (*DeleteManyModel) writeModel() {}
// ReplaceOneModel is used to replace at most one document in a BulkWrite operation.
type ReplaceOneModel struct {
Collation *options.Collation
Upsert *bool
Filter interface{}
Replacement interface{}
Hint interface{}
}
// 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 >= 3.4 will
// return an error if this option is specified. For server versions < 3.4, the driver will return a client-side 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 interface{}) *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 interface{}) *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 interface{}) *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
}
func (*ReplaceOneModel) writeModel() {}
// UpdateOneModel is used to update at most one document in a BulkWrite operation.
type UpdateOneModel struct {
Collation *options.Collation
Upsert *bool
Filter interface{}
Update interface{}
ArrayFilters *options.ArrayFilters
Hint interface{}
}
// 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 >= 3.4 will
// return an error if this option is specified. For server versions < 3.4, the driver will return a client-side 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 interface{}) *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 interface{}) *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 interface{}) *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 options.ArrayFilters) *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
}
func (*UpdateOneModel) writeModel() {}
// UpdateManyModel is used to update multiple documents in a BulkWrite operation.
type UpdateManyModel struct {
Collation *options.Collation
Upsert *bool
Filter interface{}
Update interface{}
ArrayFilters *options.ArrayFilters
Hint interface{}
}
// 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 >= 3.4 will
// return an error if this option is specified. For server versions < 3.4, the driver will return a client-side 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 interface{}) *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 interface{}) *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 interface{}) *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 options.ArrayFilters) *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() {}
+700
View File
@@ -0,0 +1,700 @@
// 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"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/internal"
"go.mongodb.org/mongo-driver/mongo/description"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/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
registry *bsoncodec.Registry
streamType StreamType
options *options.ChangeStreamOptions
selector description.ServerSelector
operationTime *primitive.Timestamp
wireVersion *description.VersionRange
}
type changeStreamConfig struct {
readConcern *readconcern.ReadConcern
readPreference *readpref.ReadPref
client *Client
registry *bsoncodec.Registry
streamType StreamType
collectionName string
databaseName string
crypt driver.Crypt
}
func newChangeStream(ctx context.Context, config changeStreamConfig, pipeline interface{},
opts ...*options.ChangeStreamOptions) (*ChangeStream, error) {
if ctx == nil {
ctx = context.Background()
}
cs := &ChangeStream{
client: config.client,
registry: config.registry,
streamType: config.streamType,
options: options.MergeChangeStreamOptions(opts...),
selector: description.CompositeSelector([]description.ServerSelector{
description.ReadPrefSelector(config.readPreference),
description.LatencySelector(config.client.localThreshold),
}),
cursorOptions: config.client.createBaseCursorOptions(),
}
cs.sess = sessionFromContext(ctx)
if cs.sess == nil && cs.client.sessionPool != nil {
cs.sess, cs.err = session.NewClientSession(cs.client.sessionPool, cs.client.id, session.Implicit)
if cs.err != nil {
return nil, cs.Err()
}
}
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)
if cs.options.Collation != nil {
cs.aggregate.Collation(bsoncore.Document(cs.options.Collation.ToDocument()))
}
if comment := cs.options.Comment; comment != nil {
cs.aggregate.Comment(*comment)
commentVal, err := transformValue(cs.registry, comment, true, "comment")
if err != nil {
return nil, err
}
cs.cursorOptions.Comment = commentVal
}
if cs.options.BatchSize != nil {
cs.aggregate.BatchSize(*cs.options.BatchSize)
cs.cursorOptions.BatchSize = *cs.options.BatchSize
}
if cs.options.MaxAwaitTime != nil {
cs.cursorOptions.MaxTimeMS = int64(*cs.options.MaxAwaitTime / time.Millisecond)
}
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 {
bsonType, bsonData, err := bson.MarshalValueWithRegistry(cs.registry, optionValue)
if err != nil {
cs.err = err
closeImplicitSession(cs.sess)
return nil, cs.Err()
}
optionValueBSON := bsoncore.Value{Type: bsonType, Data: bsonData}
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 {
bsonType, bsonData, err := bson.MarshalValueWithRegistry(cs.registry, optionValue)
if err != nil {
cs.err = err
closeImplicitSession(cs.sess)
return nil, cs.Err()
}
optionValueBSON := bsoncore.Value{Type: bsonType, Data: bsonData}
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 driver.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 driver.Connection
var err error
if server, cs.err = cs.client.deployment.SelectServer(ctx, cs.selector); cs.err != nil {
return cs.Err()
}
if conn, cs.err = server.Connection(ctx); 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)
}
// If no deadline is set on the passed-in context, cs.client.timeout is set, and context is not already
// a Timeout context, honor cs.client.timeout in new Timeout context for change stream operation execution
// and potential retry.
if _, deadlineSet := ctx.Deadline(); !deadlineSet && cs.client.timeout != nil && !internal.IsTimeoutContext(ctx) {
newCtx, cancelFunc := internal.MakeTimeoutContext(ctx, *cs.client.timeout)
// Redefine ctx to be the new timeout-derived context.
ctx = newCtx
// Cancel the timeout-derived context at the end of executeOperation to avoid a context leak.
defer cancelFunc()
}
if original := cs.aggregate.Execute(ctx); original != nil {
retryableRead := cs.client.retryReads && cs.wireVersion != nil && cs.wireVersion.Max >= 6
if !retryableRead {
cs.err = replaceErrors(original)
return cs.err
}
cs.err = original
switch tt := original.(type) {
case driver.Error:
if !tt.RetryableRead() {
break
}
server, err = cs.client.deployment.SelectServer(ctx, cs.selector)
if err != nil {
break
}
conn.Close()
conn, err = server.Connection(ctx)
if err != nil {
break
}
defer conn.Close()
cs.wireVersion = conn.Description().WireVersion
if cs.wireVersion == nil || cs.wireVersion.Max < 6 {
break
}
cs.aggregate.Deployment(cs.createOperationDeployment(server, conn))
cs.err = cs.aggregate.Execute(ctx)
}
if cs.err != nil {
cs.err = replaceErrors(cs.err)
return cs.Err()
}
}
cs.err = nil
cr := cs.aggregate.ResultCursorResponse()
cr.Server = server
cs.cursor, cs.err = driver.NewBatchCursor(cr, cs.sess, cs.client.clock, cs.cursorOptions)
if cs.err = replaceErrors(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 interface{}) error {
val := reflect.ValueOf(pipeline)
if !val.IsValid() || !(val.Kind() == reflect.Slice) {
cs.err = errors.New("can only transform 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 = transformBsoncoreDocument(cs.registry, val.Index(i).Interface(), true, fmt.Sprintf("pipeline stage :%v", i))
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 {
// Only append a default "fullDocument" field if wire version is less than 6 (3.6). Otherwise,
// the server will assume users want the default behavior, and "fullDocument" does not need to be
// specified.
if *cs.options.FullDocument != options.Default || (cs.wireVersion != nil && cs.wireVersion.Max < 6) {
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 = transformBsoncoreDocument(cs.registry, cs.options.ResumeAfter, true, "resumeAfter")
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 = transformBsoncoreDocument(cs.registry, cs.options.StartAfter, true, "startAfter")
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.SetResumeAfter(cs.resumeToken)
cs.options.SetStartAfter(nil)
cs.options.SetStartAtOperationTime(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.SetStartAtOperationTime(opTime)
cs.options.SetResumeAfter(nil)
cs.options.SetStartAfter(nil)
return
}
// No cached resume token or operation time: set none of the resume options
cs.options.SetResumeAfter(nil)
cs.options.SetStartAfter(nil)
cs.options.SetStartAtOperationTime(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()
}
// 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 interface{}) error {
if cs.cursor == nil {
return ErrNilCursor
}
return bson.UnmarshalWithRegistry(cs.registry, cs.Current, 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 replaceErrors(cs.err)
}
if cs.cursor == nil {
return nil
}
return replaceErrors(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 = replaceErrors(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().
//
// 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 = replaceErrors(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) {
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 = replaceErrors(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 {
commandErr, ok := cs.err.(CommandError)
if !ok || 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 && cs.wireVersion.Includes(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
)
+54
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 mongo
import (
"context"
"time"
"go.mongodb.org/mongo-driver/mongo/description"
"go.mongodb.org/mongo-driver/x/mongo/driver"
)
type changeStreamDeployment struct {
topologyKind description.TopologyKind
server driver.Server
conn driver.Connection
}
var _ driver.Deployment = (*changeStreamDeployment)(nil)
var _ driver.Server = (*changeStreamDeployment)(nil)
var _ 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) (driver.Connection, error) {
return c.conn, nil
}
func (c *changeStreamDeployment) MinRTT() time.Duration {
return c.server.MinRTT()
}
func (c *changeStreamDeployment) RTT90() time.Duration {
return c.server.RTT90()
}
func (c *changeStreamDeployment) ProcessError(err error, conn driver.Connection) driver.ProcessErrorResult {
ep, ok := c.server.(driver.ErrorProcessor)
if !ok {
return driver.NoChange
}
return ep.ProcessError(err, conn)
}
File diff suppressed because it is too large Load Diff
+314
View File
@@ -0,0 +1,314 @@
// 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"
"fmt"
"strings"
"github.com/pkg/errors"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/x/mongo/driver/mongocrypt"
mcopts "go.mongodb.org/mongo-driver/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
}
// NewClientEncryption creates a new ClientEncryption instance configured with the given options.
func NewClientEncryption(keyVaultClient *Client, opts ...*options.ClientEncryptionOptions) (*ClientEncryption, error) {
if keyVaultClient == nil {
return nil, errors.New("keyVaultClient must not be nil")
}
ce := &ClientEncryption{
keyVaultClient: keyVaultClient,
}
ceo := options.MergeClientEncryptionOptions(opts...)
// create keyVaultColl
db, coll := splitNamespace(ceo.KeyVaultNamespace)
ce.keyVaultColl = ce.keyVaultClient.Database(db).Collection(coll, keyVaultCollOpts)
kmsProviders, err := transformBsoncoreDocument(bson.DefaultRegistry, ceo.KmsProviders, true, "kmsProviders")
if err != nil {
return nil, fmt.Errorf("error creating KMS providers map: %v", err)
}
mc, err := mongocrypt.NewMongoCrypt(mcopts.MongoCrypt().
SetKmsProviders(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.
SetCryptSharedLibDisabled(true))
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: ceo.TLSConfig,
})
return ce, 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 primitive.Binary, keyAltName string) *SingleResult {
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.DataKeyOptions) (primitive.Binary, error) {
// translate opts to mcopts.DataKeyOptions
dko := options.MergeDataKeyOptions(opts...)
co := mcopts.DataKey().SetKeyAltNames(dko.KeyAltNames)
if dko.MasterKey != nil {
keyDoc, err := transformBsoncoreDocument(ce.keyVaultClient.registry, dko.MasterKey, true, "masterKey")
if err != nil {
return primitive.Binary{}, err
}
co.SetMasterKey(keyDoc)
}
if dko.KeyMaterial != nil {
co.SetKeyMaterial(dko.KeyMaterial)
}
// create data key document
dataKeyDoc, err := ce.crypt.CreateDataKey(ctx, kmsProvider, co)
if err != nil {
return primitive.Binary{}, err
}
// insert key into key vault
_, err = ce.keyVaultColl.InsertOne(ctx, dataKeyDoc)
if err != nil {
return primitive.Binary{}, err
}
subtype, data := bson.Raw(dataKeyDoc).Lookup("_id").Binary()
return primitive.Binary{Subtype: subtype, Data: data}, 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.EncryptOptions) (primitive.Binary, error) {
eo := options.MergeEncryptOptions(opts...)
transformed := mcopts.ExplicitEncryption()
if eo.KeyID != nil {
transformed.SetKeyID(*eo.KeyID)
}
if eo.KeyAltName != nil {
transformed.SetKeyAltName(*eo.KeyAltName)
}
transformed.SetAlgorithm(eo.Algorithm)
transformed.SetQueryType(eo.QueryType)
if eo.ContentionFactor != nil {
transformed.SetContentionFactor(*eo.ContentionFactor)
}
subtype, data, err := ce.crypt.EncryptExplicit(ctx, bsoncore.Value{Type: val.Type, Data: val.Value}, transformed)
if err != nil {
return primitive.Binary{}, err
}
return primitive.Binary{Subtype: subtype, Data: data}, 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 primitive.Binary) (bson.RawValue, error) {
decrypted, err := ce.crypt.DecryptExplicit(ctx, val.Subtype, val.Data)
if err != nil {
return bson.RawValue{}, err
}
return bson.RawValue{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 {
ce.crypt.Close()
return ce.keyVaultClient.Disconnect(ctx)
}
// 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 primitive.Binary) (*DeleteResult, error) {
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 {
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 primitive.Binary) *SingleResult {
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) {
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 primitive.Binary, keyAltName string) *SingleResult {
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 := primitive.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 := primitive.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 interface{},
opts ...*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.
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)
}
rmdko := options.MergeRewrapManyDataKeyOptions(opts...)
if ctx == nil {
ctx = context.Background()
}
// Transfer rmdko options to /x/ package options to publish the mongocrypt feed.
co := mcopts.RewrapManyDataKey()
if rmdko.MasterKey != nil {
keyDoc, err := transformBsoncoreDocument(ce.keyVaultClient.registry, rmdko.MasterKey, true, "masterKey")
if err != nil {
return nil, err
}
co.SetMasterKey(keyDoc)
}
if rmdko.Provider != nil {
co.SetProvider(*rmdko.Provider)
}
// Prepare the filters and rewrap the data key using mongocrypt.
filterdoc, err := transformBsoncoreDocument(ce.keyVaultClient.registry, filter, true, "filter")
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
+65
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/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
}
+322
View File
@@ -0,0 +1,322 @@
// 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"
"io"
"reflect"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/x/bsonx"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/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.DocumentSequence
batchLength int
registry *bsoncodec.Registry
clientSession *session.Client
err error
}
func newCursor(bc batchCursor, registry *bsoncodec.Registry) (*Cursor, error) {
return newCursorWithSession(bc, registry, nil)
}
func newCursorWithSession(bc batchCursor, registry *bsoncodec.Registry, clientSession *session.Client) (*Cursor, error) {
if registry == nil {
registry = bson.DefaultRegistry
}
if bc == nil {
return nil, errors.New("batch cursor must not be nil")
}
c := &Cursor{
bc: bc,
registry: registry,
clientSession: clientSession,
}
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().DocumentCount()
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.DefaultRegistry 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 []interface{}, err error, registry *bsoncodec.Registry) (*Cursor, error) {
if registry == nil {
registry = bson.DefaultRegistry
}
// Convert documents slice to a sequence-style byte array.
var docsBytes []byte
for _, doc := range documents {
switch t := doc.(type) {
case nil:
return nil, ErrNilDocument
case bsonx.Doc:
doc = t.Copy()
case []byte:
// Slight optimization so we'll just use MarshalBSON and not go through the codec machinery.
doc = bson.Raw(t)
}
var marshalErr error
docsBytes, marshalErr = bson.MarshalAppendWithRegistry(registry, docsBytes, doc)
if marshalErr != nil {
return nil, marshalErr
}
}
c := &Cursor{
bc: driver.NewBatchCursorFromDocuments(docsBytes),
registry: registry,
err: err,
}
// 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().DocumentCount()
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, 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 (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 ctx expires, the 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()
}
doc, err := c.batch.Next()
switch err {
case nil:
// Consume the next document in the current batch.
c.batchLength--
c.Current = bson.Raw(doc)
return true
case 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 = replaceErrors(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.DocumentCount()
doc, err = c.batch.Next()
switch err {
case nil:
c.batchLength--
c.Current = bson.Raw(doc)
return true
case io.EOF: // Empty batch so we continue
default:
c.err = err
return false
}
}
}
// 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 interface{}) error {
return bson.UnmarshalWithRegistry(c.registry, c.Current, 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 replaceErrors(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. 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 interface{}) 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 c.Close(ctx)
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 = replaceErrors(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.DocumentSequence,
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()
if err = bson.UnmarshalWithRegistry(c.registry, doc, currElem); err != nil {
return sliceVal, index, err
}
index++
}
return sliceVal, index, nil
}
func (c *Cursor) closeImplicitSession() {
if c.clientSession != nil && c.clientSession.SessionType == session.Implicit {
c.clientSession.EndSession()
}
}
// 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
}
+810
View File
@@ -0,0 +1,810 @@
// 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/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/internal"
"go.mongodb.org/mongo-driver/mongo/description"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/x/mongo/driver/session"
)
var (
defaultRunCmdOpts = []*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
registry *bsoncodec.Registry
}
func newDatabase(client *Client, name string, opts ...*options.DatabaseOptions) *Database {
dbOpt := options.MergeDatabaseOptions(opts...)
rc := client.readConcern
if dbOpt.ReadConcern != nil {
rc = dbOpt.ReadConcern
}
rp := client.readPreference
if dbOpt.ReadPreference != nil {
rp = dbOpt.ReadPreference
}
wc := client.writeConcern
if dbOpt.WriteConcern != nil {
wc = dbOpt.WriteConcern
}
reg := client.registry
if dbOpt.Registry != nil {
reg = dbOpt.Registry
}
db := &Database{
client: client,
name: name,
readPreference: rp,
readConcern: rc,
writeConcern: wc,
registry: reg,
}
db.readSelector = description.CompositeSelector([]description.ServerSelector{
description.ReadPrefSelector(db.readPreference),
description.LatencySelector(db.client.localThreshold),
})
db.writeSelector = description.CompositeSelector([]description.ServerSelector{
description.WriteSelector(),
description.LatencySelector(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 gets a handle for a collection with the given name configured with the given CollectionOptions.
func (db *Database) Collection(name string, opts ...*options.CollectionOptions) *Collection {
return newCollection(db, name, opts...)
}
// Aggregate executes an aggregate command the database. This requires MongoDB version >= 3.6 and driver version >=
// 1.1.0.
//
// 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 interface{},
opts ...*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,
opts: opts,
}
return aggregate(a)
}
func (db *Database) processRunCommand(ctx context.Context, cmd interface{},
cursorCommand bool, opts ...*options.RunCmdOptions) (*operation.Command, *session.Client, error) {
sess := sessionFromContext(ctx)
if sess == nil && db.client.sessionPool != nil {
var err error
sess, err = session.NewClientSession(db.client.sessionPool, db.client.id, session.Implicit)
if err != nil {
return nil, sess, err
}
}
err := db.client.validSession(sess)
if err != nil {
return nil, sess, err
}
ro := options.MergeRunCmdOptions(append(defaultRunCmdOpts, opts...)...)
if sess != nil && sess.TransactionRunning() && ro.ReadPreference != nil && ro.ReadPreference.Mode() != readpref.PrimaryMode {
return nil, sess, errors.New("read preference in a transaction must be primary")
}
runCmdDoc, err := transformBsoncoreDocument(db.registry, cmd, false, "cmd")
if err != nil {
return nil, sess, err
}
readSelect := description.CompositeSelector([]description.ServerSelector{
description.ReadPrefSelector(ro.ReadPreference),
description.LatencySelector(db.client.localThreshold),
})
if sess != nil && sess.PinnedServer != nil {
readSelect = makePinnedSelector(sess, readSelect)
}
var op *operation.Command
switch cursorCommand {
case true:
cursorOpts := db.client.createBaseCursorOptions()
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).ReadConcern(db.readConcern).
Crypt(db.client.cryptFLE).ReadPreference(ro.ReadPreference).ServerAPI(db.client.serverAPI).
Timeout(db.client.timeout), sess, nil
}
// RunCommand executes the given command against the database. 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 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 interface{}, opts ...*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
_, convErr := processWriteError(err)
return &SingleResult{
err: convErr,
rdr: bson.Raw(op.Result()),
reg: db.registry,
}
}
// 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 interface{}, opts ...*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, replaceErrors(err)
}
if err = op.Execute(ctx); err != nil {
closeImplicitSession(sess)
return nil, replaceErrors(err)
}
bc, err := op.ResultCursor()
if err != nil {
closeImplicitSession(sess)
return nil, replaceErrors(err)
}
cursor, err := newCursorWithSession(bc, db.registry, sess)
return cursor, replaceErrors(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 {
var err error
sess, err = session.NewClientSession(db.client.sessionPool, db.client.id, session.Implicit)
if err != nil {
return err
}
defer sess.EndSession()
}
err := db.client.validSession(sess)
if err != nil {
return err
}
wc := db.writeConcern
if sess.TransactionRunning() {
wc = nil
}
if !writeconcern.AckWrite(wc) {
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)
err = op.Execute(ctx)
driverErr, ok := err.(driver.Error)
if err != nil && (!ok || !driverErr.NamespaceNotFound()) {
return replaceErrors(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/.
//
// BUG(benjirewis): ListCollectionSpecifications prevents listing more than 100 collections per database when running
// against MongoDB version 2.6.
func (db *Database) ListCollectionSpecifications(ctx context.Context, filter interface{},
opts ...*options.ListCollectionsOptions) ([]*CollectionSpecification, error) {
cursor, err := db.ListCollections(ctx, filter, opts...)
if err != nil {
return nil, err
}
var specs []*CollectionSpecification
err = cursor.All(ctx, &specs)
if err != nil {
return nil, err
}
for _, spec := range specs {
// Pre-4.4 servers report a namespace in their responses, so we only set Namespace manually if it was not in
// the response.
if spec.IDIndex != nil && spec.IDIndex.Namespace == "" {
spec.IDIndex.Namespace = db.name + "." + spec.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 interface{}, opts ...*options.ListCollectionsOptions) (*Cursor, error) {
if ctx == nil {
ctx = context.Background()
}
filterDoc, err := transformBsoncoreDocument(db.registry, filter, true, "filter")
if err != nil {
return nil, err
}
sess := sessionFromContext(ctx)
if sess == nil && db.client.sessionPool != nil {
sess, err = session.NewClientSession(db.client.sessionPool, db.client.id, session.Implicit)
if err != nil {
return nil, err
}
}
err = db.client.validSession(sess)
if err != nil {
closeImplicitSession(sess)
return nil, err
}
selector := description.CompositeSelector([]description.ServerSelector{
description.ReadPrefSelector(readpref.Primary()),
description.LatencySelector(db.client.localThreshold),
})
selector = makeReadPrefSelector(sess, selector, db.client.localThreshold)
lco := options.MergeListCollectionsOptions(opts...)
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)
cursorOpts := db.client.createBaseCursorOptions()
if lco.NameOnly != nil {
op = op.NameOnly(*lco.NameOnly)
}
if lco.BatchSize != nil {
cursorOpts.BatchSize = *lco.BatchSize
op = op.BatchSize(*lco.BatchSize)
}
if lco.AuthorizedCollections != nil {
op = op.AuthorizedCollections(*lco.AuthorizedCollections)
}
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, replaceErrors(err)
}
bc, err := op.Result(cursorOpts)
if err != nil {
closeImplicitSession(sess)
return nil, replaceErrors(err)
}
cursor, err := newCursorWithSession(bc, db.registry, sess)
return cursor, replaceErrors(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 interface{}, opts ...*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
}
// ReadConcern returns the read concern used to configure the Database object.
func (db *Database) ReadConcern() *readconcern.ReadConcern {
return db.readConcern
}
// ReadPreference returns the read preference used to configure the Database object.
func (db *Database) ReadPreference() *readpref.ReadPref {
return db.readPreference
}
// WriteConcern returns the write concern used to configure the Database object.
func (db *Database) WriteConcern() *writeconcern.WriteConcern {
return db.writeConcern
}
// 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 interface{},
opts ...*options.ChangeStreamOptions) (*ChangeStream, error) {
csConfig := changeStreamConfig{
readConcern: db.readConcern,
readPreference: db.readPreference,
client: db.client,
registry: db.registry,
streamType: DatabaseStream,
databaseName: db.Name(),
crypt: db.client.cryptFLE,
}
return newChangeStream(ctx, csConfig, pipeline, opts...)
}
// CreateCollection executes a create command to explicitly create a new collection with the specified name on the
// server. If the collection being created already exists, this method will return a mongo.CommandError. This method
// requires driver version 1.4.0 or higher.
//
// The opts parameter can be used to specify options for the operation (see the options.CreateCollectionOptions
// documentation).
//
// 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.CreateCollectionOptions) error {
cco := options.MergeCreateCollectionOptions(opts...)
// Follow Client-Side Encryption specification to check for encryptedFields.
// Check for encryptedFields from create options.
ef := cco.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) (interface{}, 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 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
}
// getEncryptedFieldsFromServer 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) interface{} {
// 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 interface{}, opts ...*options.CreateCollectionOptions) error {
efBSON, err := transformBsoncoreDocument(db.registry, ef, true /* mapAllowed */, "encryptedFields")
if err != nil {
return fmt.Errorf("error transforming document: %v", err)
}
// Create the three encryption-related, associated collections: `escCollection`, `eccCollection` and `ecocCollection`.
stateCollectionOpts := options.CreateCollection().
SetClusteredIndex(bson.D{{"key", bson.D{{"_id", 1}}}, {"unique", true}})
// Create ESCCollection.
escCollection, err := internal.GetEncryptedStateCollectionName(efBSON, name, internal.EncryptedStateCollection)
if err != nil {
return err
}
if err := db.createCollection(ctx, escCollection, stateCollectionOpts); err != nil {
return err
}
// Create ECCCollection.
eccCollection, err := internal.GetEncryptedStateCollectionName(efBSON, name, internal.EncryptedCacheCollection)
if err != nil {
return err
}
if err := db.createCollection(ctx, eccCollection, stateCollectionOpts); err != nil {
return err
}
// Create ECOCCollection.
ecocCollection, err := internal.GetEncryptedStateCollectionName(efBSON, name, internal.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: %v", err)
}
return nil
}
// createCollection creates a collection without EncryptedFields.
func (db *Database) createCollection(ctx context.Context, name string, opts ...*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.CreateCollectionOptions) (*operation.Create, error) {
cco := options.MergeCreateCollectionOptions(opts...)
op := operation.NewCreate(name).ServerAPI(db.client.serverAPI)
if cco.Capped != nil {
op.Capped(*cco.Capped)
}
if cco.Collation != nil {
op.Collation(bsoncore.Document(cco.Collation.ToDocument()))
}
if cco.ChangeStreamPreAndPostImages != nil {
csppi, err := transformBsoncoreDocument(db.registry, cco.ChangeStreamPreAndPostImages, true, "changeStreamPreAndPostImages")
if err != nil {
return nil, err
}
op.ChangeStreamPreAndPostImages(csppi)
}
if cco.DefaultIndexOptions != nil {
idx, doc := bsoncore.AppendDocumentStart(nil)
if cco.DefaultIndexOptions.StorageEngine != nil {
storageEngine, err := transformBsoncoreDocument(db.registry, cco.DefaultIndexOptions.StorageEngine, true, "storageEngine")
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 cco.MaxDocuments != nil {
op.Max(*cco.MaxDocuments)
}
if cco.SizeInBytes != nil {
op.Size(*cco.SizeInBytes)
}
if cco.StorageEngine != nil {
storageEngine, err := transformBsoncoreDocument(db.registry, cco.StorageEngine, true, "storageEngine")
if err != nil {
return nil, err
}
op.StorageEngine(storageEngine)
}
if cco.ValidationAction != nil {
op.ValidationAction(*cco.ValidationAction)
}
if cco.ValidationLevel != nil {
op.ValidationLevel(*cco.ValidationLevel)
}
if cco.Validator != nil {
validator, err := transformBsoncoreDocument(db.registry, cco.Validator, true, "validator")
if err != nil {
return nil, err
}
op.Validator(validator)
}
if cco.ExpireAfterSeconds != nil {
op.ExpireAfterSeconds(*cco.ExpireAfterSeconds)
}
if cco.TimeSeriesOptions != nil {
idx, doc := bsoncore.AppendDocumentStart(nil)
doc = bsoncore.AppendStringElement(doc, "timeField", cco.TimeSeriesOptions.TimeField)
if cco.TimeSeriesOptions.MetaField != nil {
doc = bsoncore.AppendStringElement(doc, "metaField", *cco.TimeSeriesOptions.MetaField)
}
if cco.TimeSeriesOptions.Granularity != nil {
doc = bsoncore.AppendStringElement(doc, "granularity", *cco.TimeSeriesOptions.Granularity)
}
doc, err := bsoncore.AppendDocumentEnd(doc, idx)
if err != nil {
return nil, err
}
op.TimeSeries(doc)
}
if cco.ClusteredIndex != nil {
clusteredIndex, err := transformBsoncoreDocument(db.registry, cco.ClusteredIndex, true, "clusteredIndex")
if err != nil {
return nil, err
}
op.ClusteredIndex(clusteredIndex)
}
return op, nil
}
// CreateView executes a create command to explicitly create a view on the server. See
// https://www.mongodb.com/docs/manual/core/views/ for more information about views. This method requires driver version >=
// 1.4.0 and MongoDB version >= 3.4.
//
// 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.
//
// The opts parameter can be used to specify options for the operation (see the options.CreateViewOptions
// documentation).
func (db *Database) CreateView(ctx context.Context, viewName, viewOn string, pipeline interface{},
opts ...*options.CreateViewOptions) error {
pipelineArray, _, err := transformAggregatePipeline(db.registry, pipeline)
if err != nil {
return err
}
op := operation.NewCreate(viewName).
ViewOn(viewOn).
Pipeline(pipelineArray).
ServerAPI(db.client.serverAPI)
cvo := options.MergeCreateViewOptions(opts...)
if cvo.Collation != nil {
op.Collation(bsoncore.Document(cvo.Collation.ToDocument()))
}
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 {
var err error
sess, err = session.NewClientSession(db.client.sessionPool, db.client.id, session.Implicit)
if err != nil {
return err
}
defer sess.EndSession()
}
err := db.client.validSession(sess)
if err != nil {
return err
}
wc := db.writeConcern
if sess.TransactionRunning() {
wc = nil
}
if !writeconcern.AckWrite(wc) {
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 replaceErrors(op.Execute(ctx))
}
+10
View File
@@ -0,0 +1,10 @@
// 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 description // import "go.mongodb.org/mongo-driver/mongo/description"
// Unknown is an unknown server or topology kind.
const Unknown = 0
+481
View File
@@ -0,0 +1,481 @@
// 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 description
import (
"errors"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/internal"
"go.mongodb.org/mongo-driver/mongo/address"
"go.mongodb.org/mongo-driver/tag"
)
// SelectedServer augments the Server type by also including the TopologyKind of the topology that includes the server.
// This type should be used to track the state of a server that was selected to perform an operation.
type SelectedServer struct {
Server
Kind TopologyKind
}
// Server contains information about a node in a cluster. This is created from hello command responses. If the value
// of the Kind field is LoadBalancer, only the Addr and Kind fields will be set. All other fields will be set to the
// zero value of the field's type.
type Server struct {
Addr address.Address
Arbiters []string
AverageRTT time.Duration
AverageRTTSet bool
Compression []string // compression methods returned by server
CanonicalAddr address.Address
ElectionID primitive.ObjectID
HeartbeatInterval time.Duration
HelloOK bool
Hosts []string
LastError error
LastUpdateTime time.Time
LastWriteTime time.Time
MaxBatchCount uint32
MaxDocumentSize uint32
MaxMessageSize uint32
Members []address.Address
Passives []string
Passive bool
Primary address.Address
ReadOnly bool
ServiceID *primitive.ObjectID // Only set for servers that are deployed behind a load balancer.
SessionTimeoutMinutes uint32
SetName string
SetVersion uint32
Tags tag.Set
TopologyVersion *TopologyVersion
Kind ServerKind
WireVersion *VersionRange
}
// NewServer creates a new server description from the given hello command response.
func NewServer(addr address.Address, response bson.Raw) Server {
desc := Server{Addr: addr, CanonicalAddr: addr, LastUpdateTime: time.Now().UTC()}
elements, err := response.Elements()
if err != nil {
desc.LastError = err
return desc
}
var ok bool
var isReplicaSet, isWritablePrimary, hidden, secondary, arbiterOnly bool
var msg string
var version VersionRange
for _, element := range elements {
switch element.Key() {
case "arbiters":
var err error
desc.Arbiters, err = internal.StringSliceFromRawElement(element)
if err != nil {
desc.LastError = err
return desc
}
case "arbiterOnly":
arbiterOnly, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'arbiterOnly' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "compression":
var err error
desc.Compression, err = internal.StringSliceFromRawElement(element)
if err != nil {
desc.LastError = err
return desc
}
case "electionId":
desc.ElectionID, ok = element.Value().ObjectIDOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'electionId' to be a objectID but it's a BSON %s", element.Value().Type)
return desc
}
case "helloOk":
desc.HelloOK, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'helloOk' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "hidden":
hidden, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'hidden' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "hosts":
var err error
desc.Hosts, err = internal.StringSliceFromRawElement(element)
if err != nil {
desc.LastError = err
return desc
}
case "isWritablePrimary":
isWritablePrimary, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'isWritablePrimary' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case internal.LegacyHelloLowercase:
isWritablePrimary, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected legacy hello to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "isreplicaset":
isReplicaSet, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'isreplicaset' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "lastWrite":
lastWrite, ok := element.Value().DocumentOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'lastWrite' to be a document but it's a BSON %s", element.Value().Type)
return desc
}
dateTime, err := lastWrite.LookupErr("lastWriteDate")
if err == nil {
dt, ok := dateTime.DateTimeOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'lastWriteDate' to be a datetime but it's a BSON %s", dateTime.Type)
return desc
}
desc.LastWriteTime = time.Unix(dt/1000, dt%1000*1000000).UTC()
}
case "logicalSessionTimeoutMinutes":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'logicalSessionTimeoutMinutes' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.SessionTimeoutMinutes = uint32(i64)
case "maxBsonObjectSize":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'maxBsonObjectSize' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.MaxDocumentSize = uint32(i64)
case "maxMessageSizeBytes":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'maxMessageSizeBytes' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.MaxMessageSize = uint32(i64)
case "maxWriteBatchSize":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'maxWriteBatchSize' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.MaxBatchCount = uint32(i64)
case "me":
me, ok := element.Value().StringValueOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'me' to be a string but it's a BSON %s", element.Value().Type)
return desc
}
desc.CanonicalAddr = address.Address(me).Canonicalize()
case "maxWireVersion":
version.Max, ok = element.Value().AsInt32OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'maxWireVersion' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
case "minWireVersion":
version.Min, ok = element.Value().AsInt32OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'minWireVersion' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
case "msg":
msg, ok = element.Value().StringValueOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'msg' to be a string but it's a BSON %s", element.Value().Type)
return desc
}
case "ok":
okay, ok := element.Value().AsInt32OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'ok' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
if okay != 1 {
desc.LastError = errors.New("not ok")
return desc
}
case "passives":
var err error
desc.Passives, err = internal.StringSliceFromRawElement(element)
if err != nil {
desc.LastError = err
return desc
}
case "passive":
desc.Passive, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'passive' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "primary":
primary, ok := element.Value().StringValueOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'primary' to be a string but it's a BSON %s", element.Value().Type)
return desc
}
desc.Primary = address.Address(primary)
case "readOnly":
desc.ReadOnly, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'readOnly' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "secondary":
secondary, ok = element.Value().BooleanOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'secondary' to be a boolean but it's a BSON %s", element.Value().Type)
return desc
}
case "serviceId":
oid, ok := element.Value().ObjectIDOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'serviceId' to be an ObjectId but it's a BSON %s", element.Value().Type)
}
desc.ServiceID = &oid
case "setName":
desc.SetName, ok = element.Value().StringValueOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'setName' to be a string but it's a BSON %s", element.Value().Type)
return desc
}
case "setVersion":
i64, ok := element.Value().AsInt64OK()
if !ok {
desc.LastError = fmt.Errorf("expected 'setVersion' to be an integer but it's a BSON %s", element.Value().Type)
return desc
}
desc.SetVersion = uint32(i64)
case "tags":
m, err := decodeStringMap(element, "tags")
if err != nil {
desc.LastError = err
return desc
}
desc.Tags = tag.NewTagSetFromMap(m)
case "topologyVersion":
doc, ok := element.Value().DocumentOK()
if !ok {
desc.LastError = fmt.Errorf("expected 'topologyVersion' to be a document but it's a BSON %s", element.Value().Type)
return desc
}
desc.TopologyVersion, err = NewTopologyVersion(doc)
if err != nil {
desc.LastError = err
return desc
}
}
}
for _, host := range desc.Hosts {
desc.Members = append(desc.Members, address.Address(host).Canonicalize())
}
for _, passive := range desc.Passives {
desc.Members = append(desc.Members, address.Address(passive).Canonicalize())
}
for _, arbiter := range desc.Arbiters {
desc.Members = append(desc.Members, address.Address(arbiter).Canonicalize())
}
desc.Kind = Standalone
if isReplicaSet {
desc.Kind = RSGhost
} else if desc.SetName != "" {
if isWritablePrimary {
desc.Kind = RSPrimary
} else if hidden {
desc.Kind = RSMember
} else if secondary {
desc.Kind = RSSecondary
} else if arbiterOnly {
desc.Kind = RSArbiter
} else {
desc.Kind = RSMember
}
} else if msg == "isdbgrid" {
desc.Kind = Mongos
}
desc.WireVersion = &version
return desc
}
// NewDefaultServer creates a new unknown server description with the given address.
func NewDefaultServer(addr address.Address) Server {
return NewServerFromError(addr, nil, nil)
}
// NewServerFromError creates a new unknown server description with the given parameters.
func NewServerFromError(addr address.Address, err error, tv *TopologyVersion) Server {
return Server{
Addr: addr,
LastError: err,
Kind: Unknown,
TopologyVersion: tv,
}
}
// SetAverageRTT sets the average round trip time for this server description.
func (s Server) SetAverageRTT(rtt time.Duration) Server {
s.AverageRTT = rtt
s.AverageRTTSet = true
return s
}
// DataBearing returns true if the server is a data bearing server.
func (s Server) DataBearing() bool {
return s.Kind == RSPrimary ||
s.Kind == RSSecondary ||
s.Kind == Mongos ||
s.Kind == Standalone
}
// LoadBalanced returns true if the server is a load balancer or is behind a load balancer.
func (s Server) LoadBalanced() bool {
return s.Kind == LoadBalancer || s.ServiceID != nil
}
// String implements the Stringer interface
func (s Server) String() string {
str := fmt.Sprintf("Addr: %s, Type: %s",
s.Addr, s.Kind)
if len(s.Tags) != 0 {
str += fmt.Sprintf(", Tag sets: %s", s.Tags)
}
if s.AverageRTTSet {
str += fmt.Sprintf(", Average RTT: %d", s.AverageRTT)
}
if s.LastError != nil {
str += fmt.Sprintf(", Last error: %s", s.LastError)
}
return str
}
func decodeStringMap(element bson.RawElement, name string) (map[string]string, error) {
doc, ok := element.Value().DocumentOK()
if !ok {
return nil, fmt.Errorf("expected '%s' to be a document but it's a BSON %s", name, element.Value().Type)
}
elements, err := doc.Elements()
if err != nil {
return nil, err
}
m := make(map[string]string)
for _, element := range elements {
key := element.Key()
value, ok := element.Value().StringValueOK()
if !ok {
return nil, fmt.Errorf("expected '%s' to be a document of strings, but found a BSON %s", name, element.Value().Type)
}
m[key] = value
}
return m, nil
}
// Equal compares two server descriptions and returns true if they are equal
func (s Server) Equal(other Server) bool {
if s.CanonicalAddr.String() != other.CanonicalAddr.String() {
return false
}
if !sliceStringEqual(s.Arbiters, other.Arbiters) {
return false
}
if !sliceStringEqual(s.Hosts, other.Hosts) {
return false
}
if !sliceStringEqual(s.Passives, other.Passives) {
return false
}
if s.Primary != other.Primary {
return false
}
if s.SetName != other.SetName {
return false
}
if s.Kind != other.Kind {
return false
}
if s.LastError != nil || other.LastError != nil {
if s.LastError == nil || other.LastError == nil {
return false
}
if s.LastError.Error() != other.LastError.Error() {
return false
}
}
if !s.WireVersion.Equals(other.WireVersion) {
return false
}
if len(s.Tags) != len(other.Tags) || !s.Tags.ContainsAll(other.Tags) {
return false
}
if s.SetVersion != other.SetVersion {
return false
}
if s.ElectionID != other.ElectionID {
return false
}
if s.SessionTimeoutMinutes != other.SessionTimeoutMinutes {
return false
}
// If TopologyVersion is nil for both servers, CompareToIncoming will return -1 because it assumes that the
// incoming response is newer. We want the descriptions to be considered equal in this case, though, so an
// explicit check is required.
if s.TopologyVersion == nil && other.TopologyVersion == nil {
return true
}
return s.TopologyVersion.CompareToIncoming(other.TopologyVersion) == 0
}
func sliceStringEqual(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
+46
View File
@@ -0,0 +1,46 @@
// 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 description
// ServerKind represents the type of a single server in a topology.
type ServerKind uint32
// These constants are the possible types of servers.
const (
Standalone ServerKind = 1
RSMember ServerKind = 2
RSPrimary ServerKind = 4 + RSMember
RSSecondary ServerKind = 8 + RSMember
RSArbiter ServerKind = 16 + RSMember
RSGhost ServerKind = 32 + RSMember
Mongos ServerKind = 256
LoadBalancer ServerKind = 512
)
// String returns a stringified version of the kind or "Unknown" if the kind is invalid.
func (kind ServerKind) String() string {
switch kind {
case Standalone:
return "Standalone"
case RSMember:
return "RSOther"
case RSPrimary:
return "RSPrimary"
case RSSecondary:
return "RSSecondary"
case RSArbiter:
return "RSArbiter"
case RSGhost:
return "RSGhost"
case Mongos:
return "Mongos"
case LoadBalancer:
return "LoadBalancer"
}
return "Unknown"
}
+336
View File
@@ -0,0 +1,336 @@
// 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 description
import (
"fmt"
"math"
"time"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/tag"
)
// ServerSelector is an interface implemented by types that can perform server selection given a topology description
// and list of candidate servers. The selector should filter the provided candidates list and return a subset that
// matches some criteria.
type ServerSelector interface {
SelectServer(Topology, []Server) ([]Server, error)
}
// ServerSelectorFunc is a function that can be used as a ServerSelector.
type ServerSelectorFunc func(Topology, []Server) ([]Server, error)
// SelectServer implements the ServerSelector interface.
func (ssf ServerSelectorFunc) SelectServer(t Topology, s []Server) ([]Server, error) {
return ssf(t, s)
}
type compositeSelector struct {
selectors []ServerSelector
}
// CompositeSelector combines multiple selectors into a single selector by applying them in order to the candidates
// list.
//
// For example, if the initial candidates list is [s0, s1, s2, s3] and two selectors are provided where the first
// matches s0 and s1 and the second matches s1 and s2, the following would occur during server selection:
//
// 1. firstSelector([s0, s1, s2, s3]) -> [s0, s1]
// 2. secondSelector([s0, s1]) -> [s1]
//
// The final list of candidates returned by the composite selector would be [s1].
func CompositeSelector(selectors []ServerSelector) ServerSelector {
return &compositeSelector{selectors: selectors}
}
func (cs *compositeSelector) SelectServer(t Topology, candidates []Server) ([]Server, error) {
var err error
for _, sel := range cs.selectors {
candidates, err = sel.SelectServer(t, candidates)
if err != nil {
return nil, err
}
}
return candidates, nil
}
type latencySelector struct {
latency time.Duration
}
// LatencySelector creates a ServerSelector which selects servers based on their average RTT values.
func LatencySelector(latency time.Duration) ServerSelector {
return &latencySelector{latency: latency}
}
func (ls *latencySelector) SelectServer(t Topology, candidates []Server) ([]Server, error) {
if ls.latency < 0 {
return candidates, nil
}
if t.Kind == LoadBalanced {
// In LoadBalanced mode, there should only be one server in the topology and it must be selected.
return candidates, nil
}
switch len(candidates) {
case 0, 1:
return candidates, nil
default:
min := time.Duration(math.MaxInt64)
for _, candidate := range candidates {
if candidate.AverageRTTSet {
if candidate.AverageRTT < min {
min = candidate.AverageRTT
}
}
}
if min == math.MaxInt64 {
return candidates, nil
}
max := min + ls.latency
var result []Server
for _, candidate := range candidates {
if candidate.AverageRTTSet {
if candidate.AverageRTT <= max {
result = append(result, candidate)
}
}
}
return result, nil
}
}
// WriteSelector selects all the writable servers.
func WriteSelector() ServerSelector {
return ServerSelectorFunc(func(t Topology, candidates []Server) ([]Server, error) {
switch t.Kind {
case Single, LoadBalanced:
return candidates, nil
default:
result := []Server{}
for _, candidate := range candidates {
switch candidate.Kind {
case Mongos, RSPrimary, Standalone:
result = append(result, candidate)
}
}
return result, nil
}
})
}
// ReadPrefSelector selects servers based on the provided read preference.
func ReadPrefSelector(rp *readpref.ReadPref) ServerSelector {
return readPrefSelector(rp, false)
}
// OutputAggregateSelector selects servers based on the provided read preference given that the underlying operation is
// aggregate with an output stage.
func OutputAggregateSelector(rp *readpref.ReadPref) ServerSelector {
return readPrefSelector(rp, true)
}
func readPrefSelector(rp *readpref.ReadPref, isOutputAggregate bool) ServerSelector {
return ServerSelectorFunc(func(t Topology, candidates []Server) ([]Server, error) {
if t.Kind == LoadBalanced {
// In LoadBalanced mode, there should only be one server in the topology and it must be selected. We check
// this before checking MaxStaleness support because there's no monitoring in this mode, so the candidate
// server wouldn't have a wire version set, which would result in an error.
return candidates, nil
}
if _, set := rp.MaxStaleness(); set {
for _, s := range candidates {
if s.Kind != Unknown {
if err := maxStalenessSupported(s.WireVersion); err != nil {
return nil, err
}
}
}
}
switch t.Kind {
case Single:
return candidates, nil
case ReplicaSetNoPrimary, ReplicaSetWithPrimary:
return selectForReplicaSet(rp, isOutputAggregate, t, candidates)
case Sharded:
return selectByKind(candidates, Mongos), nil
}
return nil, nil
})
}
// maxStalenessSupported returns an error if the given server version does not support max staleness.
func maxStalenessSupported(wireVersion *VersionRange) error {
if wireVersion != nil && wireVersion.Max < 5 {
return fmt.Errorf("max staleness is only supported for servers 3.4 or newer")
}
return nil
}
func selectForReplicaSet(rp *readpref.ReadPref, isOutputAggregate bool, t Topology, candidates []Server) ([]Server, error) {
if err := verifyMaxStaleness(rp, t); err != nil {
return nil, err
}
// If underlying operation is an aggregate with an output stage, only apply read preference
// if all candidates are 5.0+. Otherwise, operate under primary read preference.
if isOutputAggregate {
for _, s := range candidates {
if s.WireVersion.Max < 13 {
return selectByKind(candidates, RSPrimary), nil
}
}
}
switch rp.Mode() {
case readpref.PrimaryMode:
return selectByKind(candidates, RSPrimary), nil
case readpref.PrimaryPreferredMode:
selected := selectByKind(candidates, RSPrimary)
if len(selected) == 0 {
selected = selectSecondaries(rp, candidates)
return selectByTagSet(selected, rp.TagSets()), nil
}
return selected, nil
case readpref.SecondaryPreferredMode:
selected := selectSecondaries(rp, candidates)
selected = selectByTagSet(selected, rp.TagSets())
if len(selected) > 0 {
return selected, nil
}
return selectByKind(candidates, RSPrimary), nil
case readpref.SecondaryMode:
selected := selectSecondaries(rp, candidates)
return selectByTagSet(selected, rp.TagSets()), nil
case readpref.NearestMode:
selected := selectByKind(candidates, RSPrimary)
selected = append(selected, selectSecondaries(rp, candidates)...)
return selectByTagSet(selected, rp.TagSets()), nil
}
return nil, fmt.Errorf("unsupported mode: %d", rp.Mode())
}
func selectSecondaries(rp *readpref.ReadPref, candidates []Server) []Server {
secondaries := selectByKind(candidates, RSSecondary)
if len(secondaries) == 0 {
return secondaries
}
if maxStaleness, set := rp.MaxStaleness(); set {
primaries := selectByKind(candidates, RSPrimary)
if len(primaries) == 0 {
baseTime := secondaries[0].LastWriteTime
for i := 1; i < len(secondaries); i++ {
if secondaries[i].LastWriteTime.After(baseTime) {
baseTime = secondaries[i].LastWriteTime
}
}
var selected []Server
for _, secondary := range secondaries {
estimatedStaleness := baseTime.Sub(secondary.LastWriteTime) + secondary.HeartbeatInterval
if estimatedStaleness <= maxStaleness {
selected = append(selected, secondary)
}
}
return selected
}
primary := primaries[0]
var selected []Server
for _, secondary := range secondaries {
estimatedStaleness := secondary.LastUpdateTime.Sub(secondary.LastWriteTime) - primary.LastUpdateTime.Sub(primary.LastWriteTime) + secondary.HeartbeatInterval
if estimatedStaleness <= maxStaleness {
selected = append(selected, secondary)
}
}
return selected
}
return secondaries
}
func selectByTagSet(candidates []Server, tagSets []tag.Set) []Server {
if len(tagSets) == 0 {
return candidates
}
for _, ts := range tagSets {
// If this tag set is empty, we can take a fast path because the empty list is a subset of all tag sets, so
// all candidate servers will be selected.
if len(ts) == 0 {
return candidates
}
var results []Server
for _, s := range candidates {
// ts is non-empty, so only servers with a non-empty set of tags need to be checked.
if len(s.Tags) > 0 && s.Tags.ContainsAll(ts) {
results = append(results, s)
}
}
if len(results) > 0 {
return results
}
}
return []Server{}
}
func selectByKind(candidates []Server, kind ServerKind) []Server {
var result []Server
for _, s := range candidates {
if s.Kind == kind {
result = append(result, s)
}
}
return result
}
func verifyMaxStaleness(rp *readpref.ReadPref, t Topology) error {
maxStaleness, set := rp.MaxStaleness()
if !set {
return nil
}
if maxStaleness < 90*time.Second {
return fmt.Errorf("max staleness (%s) must be greater than or equal to 90s", maxStaleness)
}
if len(t.Servers) < 1 {
// Maybe we should return an error here instead?
return nil
}
// we'll assume all candidates have the same heartbeat interval.
s := t.Servers[0]
idleWritePeriod := 10 * time.Second
if maxStaleness < s.HeartbeatInterval+idleWritePeriod {
return fmt.Errorf(
"max staleness (%s) must be greater than or equal to the heartbeat interval (%s) plus idle write period (%s)",
maxStaleness, s.HeartbeatInterval, idleWritePeriod,
)
}
return nil
}
+142
View File
@@ -0,0 +1,142 @@
// 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 description
import (
"fmt"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
// Topology contains information about a MongoDB cluster.
type Topology struct {
Servers []Server
SetName string
Kind TopologyKind
SessionTimeoutMinutes uint32
CompatibilityErr error
}
// String implements the Stringer interface.
func (t Topology) String() string {
var serversStr string
for _, s := range t.Servers {
serversStr += "{ " + s.String() + " }, "
}
return fmt.Sprintf("Type: %s, Servers: [%s]", t.Kind, serversStr)
}
// Equal compares two topology descriptions and returns true if they are equal.
func (t Topology) Equal(other Topology) bool {
if t.Kind != other.Kind {
return false
}
topoServers := make(map[string]Server)
for _, s := range t.Servers {
topoServers[s.Addr.String()] = s
}
otherServers := make(map[string]Server)
for _, s := range other.Servers {
otherServers[s.Addr.String()] = s
}
if len(topoServers) != len(otherServers) {
return false
}
for _, server := range topoServers {
otherServer := otherServers[server.Addr.String()]
if !server.Equal(otherServer) {
return false
}
}
return true
}
// HasReadableServer returns true if the topology contains a server suitable for reading.
//
// If the Topology's kind is Single or Sharded, the mode parameter is ignored and the function contains true if any of
// the servers in the Topology are of a known type.
//
// For replica sets, the function returns true if the cluster contains a server that matches the provided read
// preference mode.
func (t Topology) HasReadableServer(mode readpref.Mode) bool {
switch t.Kind {
case Single, Sharded:
return hasAvailableServer(t.Servers, 0)
case ReplicaSetWithPrimary:
return hasAvailableServer(t.Servers, mode)
case ReplicaSetNoPrimary, ReplicaSet:
if mode == readpref.PrimaryMode {
return false
}
// invalid read preference
if !mode.IsValid() {
return false
}
return hasAvailableServer(t.Servers, mode)
}
return false
}
// HasWritableServer returns true if a topology has a server available for writing.
//
// If the Topology's kind is Single or Sharded, this function returns true if any of the servers in the Topology are of
// a known type.
//
// For replica sets, the function returns true if the replica set contains a primary.
func (t Topology) HasWritableServer() bool {
return t.HasReadableServer(readpref.PrimaryMode)
}
// hasAvailableServer returns true if any servers are available based on the read preference.
func hasAvailableServer(servers []Server, mode readpref.Mode) bool {
switch mode {
case readpref.PrimaryMode:
for _, s := range servers {
if s.Kind == RSPrimary {
return true
}
}
return false
case readpref.PrimaryPreferredMode, readpref.SecondaryPreferredMode, readpref.NearestMode:
for _, s := range servers {
if s.Kind == RSPrimary || s.Kind == RSSecondary {
return true
}
}
return false
case readpref.SecondaryMode:
for _, s := range servers {
if s.Kind == RSSecondary {
return true
}
}
return false
}
// read preference is not specified
for _, s := range servers {
switch s.Kind {
case Standalone,
RSMember,
RSPrimary,
RSSecondary,
RSArbiter,
RSGhost,
Mongos:
return true
}
}
return false
}
+40
View File
@@ -0,0 +1,40 @@
// 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 description
// TopologyKind represents a specific topology configuration.
type TopologyKind uint32
// These constants are the available topology configurations.
const (
Single TopologyKind = 1
ReplicaSet TopologyKind = 2
ReplicaSetNoPrimary TopologyKind = 4 + ReplicaSet
ReplicaSetWithPrimary TopologyKind = 8 + ReplicaSet
Sharded TopologyKind = 256
LoadBalanced TopologyKind = 512
)
// String implements the fmt.Stringer interface.
func (kind TopologyKind) String() string {
switch kind {
case Single:
return "Single"
case ReplicaSet:
return "ReplicaSet"
case ReplicaSetNoPrimary:
return "ReplicaSetNoPrimary"
case ReplicaSetWithPrimary:
return "ReplicaSetWithPrimary"
case Sharded:
return "Sharded"
case LoadBalanced:
return "LoadBalanced"
}
return "Unknown"
}
@@ -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 description
import (
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// TopologyVersion represents a software version.
type TopologyVersion struct {
ProcessID primitive.ObjectID
Counter int64
}
// NewTopologyVersion creates a TopologyVersion based on doc
func NewTopologyVersion(doc bson.Raw) (*TopologyVersion, error) {
elements, err := doc.Elements()
if err != nil {
return nil, err
}
var tv TopologyVersion
var ok bool
for _, element := range elements {
switch element.Key() {
case "processId":
tv.ProcessID, ok = element.Value().ObjectIDOK()
if !ok {
return nil, fmt.Errorf("expected 'processId' to be a objectID but it's a BSON %s", element.Value().Type)
}
case "counter":
tv.Counter, ok = element.Value().Int64OK()
if !ok {
return nil, fmt.Errorf("expected 'counter' to be an int64 but it's a BSON %s", element.Value().Type)
}
}
}
return &tv, nil
}
// CompareToIncoming compares the receiver, which represents the currently known TopologyVersion for a server, to an
// incoming TopologyVersion extracted from a server command response.
//
// This returns -1 if the receiver version is less than the response, 0 if the versions are equal, and 1 if the
// receiver version is greater than the response. This comparison is not commutative.
func (tv *TopologyVersion) CompareToIncoming(responseTV *TopologyVersion) int {
if tv == nil || responseTV == nil {
return -1
}
if tv.ProcessID != responseTV.ProcessID {
return -1
}
if tv.Counter == responseTV.Counter {
return 0
}
if tv.Counter < responseTV.Counter {
return -1
}
return 1
}
+42
View File
@@ -0,0 +1,42 @@
// 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 description
import "fmt"
// VersionRange represents a range of versions.
type VersionRange struct {
Min int32
Max int32
}
// NewVersionRange creates a new VersionRange given a min and a max.
func NewVersionRange(min, max int32) VersionRange {
return VersionRange{Min: min, Max: max}
}
// Includes returns a bool indicating whether the supplied integer is included
// in the range.
func (vr VersionRange) Includes(v int32) bool {
return v >= vr.Min && v <= vr.Max
}
// Equals returns a bool indicating whether the supplied VersionRange is equal.
func (vr *VersionRange) Equals(other *VersionRange) bool {
if vr == nil && other == nil {
return true
}
if vr == nil || other == nil {
return false
}
return vr.Min == other.Min && vr.Max == other.Max
}
// String implements the fmt.Stringer interface.
func (vr VersionRange) String() string {
return fmt.Sprintf("[%d, %d]", vr.Min, vr.Max)
}
+149
View File
@@ -0,0 +1,149 @@
// 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(ctx, 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 interface{}
// will return ErrNilDocument if nil is passed in for an interface{}.
//
// 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
// incompatible with some DNS servers in the wild due to the change introduced in
// https://github.com/golang/go/issues/10622. If you receive an error with the message "cannot
// unmarshal DNS message" while running an operation, we suggest you use a different DNS server.
//
// Client Side Encryption
//
// Client-side 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.
//
// Note: Auto encryption is an enterprise-only feature.
//
// The libmongocrypt C library is required when using client-side 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.
//
// 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
+641
View File
@@ -0,0 +1,641 @@
// 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"
"strings"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/x/mongo/driver/mongocrypt"
"go.mongodb.org/mongo-driver/x/mongo/driver/topology"
)
// ErrUnacknowledgedWrite is returned by operations that have an unacknowledged write concern.
var ErrUnacknowledgedWrite = errors.New("unacknowledged write")
// ErrClientDisconnected is returned when disconnected Client is used to run an operation.
var ErrClientDisconnected = errors.New("client is disconnected")
// ErrNilDocument is returned when a nil document is passed to a CRUD method.
var ErrNilDocument = errors.New("document is nil")
// ErrNilValue is returned when a nil value is passed to a CRUD method.
var ErrNilValue = 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 = errors.New("must provide at least one element in input 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)
}
func replaceErrors(err error) error {
if err == topology.ErrTopologyClosed {
return ErrClientDisconnected
}
if de, ok := err.(driver.Error); ok {
return CommandError{
Code: de.Code,
Message: de.Message,
Labels: de.Labels,
Name: de.Name,
Wrapped: de.Wrapped,
Raw: bson.Raw(de.Raw),
}
}
if qe, ok := err.(driver.QueryFailureError); ok {
// qe.Message is "command failure"
ce := CommandError{
Name: qe.Message,
Wrapped: qe.Wrapped,
Raw: bson.Raw(qe.Response),
}
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
}
if me, ok := err.(mongocrypt.Error); ok {
return MongocryptError{Code: me.Code, Message: me.Message}
}
return err
}
// IsDuplicateKeyError returns true if err is a duplicate key error
func IsDuplicateKeyError(err error) bool {
// handles SERVER-7164 and SERVER-11493
for ; err != nil; err = unwrap(err) {
if e, ok := err.(ServerError); ok {
return e.HasErrorCode(11000) || e.HasErrorCode(11001) || e.HasErrorCode(12582) ||
e.HasErrorCodeWithMessage(16460, " E11000 ")
}
}
return false
}
// IsTimeout returns true if err is from a timeout
func IsTimeout(err error) bool {
for ; err != nil; err = unwrap(err) {
// check unwrappable errors together
if err == context.DeadlineExceeded {
return true
}
if err == driver.ErrDeadlineWouldBeExceeded {
return true
}
if ne, ok := err.(net.Error); ok {
return ne.Timeout()
}
//timeout error labels
if le, ok := err.(labeledError); ok {
if le.HasErrorLabel("NetworkTimeoutError") || le.HasErrorLabel("ExceededTimeLimitError") {
return true
}
}
}
return false
}
// unwrap returns the inner error if err implements Unwrap(), otherwise it returns nil.
func unwrap(err error) error {
u, ok := err.(interface {
Unwrap() error
})
if !ok {
return nil
}
return u.Unwrap()
}
// errorHasLabel returns true if err contains the specified label
func errorHasLabel(err error, label string) bool {
for ; err != nil; err = unwrap(err) {
if le, ok := err.(labeledError); ok && le.HasErrorLabel(label) {
return true
}
}
return false
}
// IsNetworkError returns true if err is a network error
func IsNetworkError(err error) bool {
return errorHasLabel(err, "NetworkError")
}
// MongocryptError represents an libmongocrypt error during client-side encryption.
type MongocryptError struct {
Code int32
Message string
}
// Error implements the error interface.
func (m MongocryptError) Error() string {
return fmt.Sprintf("mongocrypt error %d: %v", m.Code, m.Message)
}
// EncryptionKeyVaultError represents an error while communicating with the key vault collection during client-side
// 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 client-side 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
}
type labeledError interface {
error
// HasErrorLabel returns true if the error contains the specified label.
HasErrorLabel(string) bool
}
// 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 {
error
// HasErrorCode returns true if the error has the specified code.
HasErrorCode(int) bool
// HasErrorLabel returns true if the error contains the specified label.
HasErrorLabel(string) 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
serverError()
}
var _ ServerError = CommandError{}
var _ ServerError = WriteError{}
var _ ServerError = WriteException{}
var _ ServerError = BulkWriteException{}
// 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.
}
// Error implements the error interface.
func (e CommandError) Error() string {
if e.Name != "" {
return fmt.Sprintf("(%v) %v", e.Name, e.Message)
}
return e.Message
}
// 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
}
// HasErrorLabel returns true if the error contains the specified label.
func (e CommandError) HasErrorLabel(label string) bool {
if e.Labels != nil {
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
}
// HasErrorLabel returns true if the error contains the specified label. WriteErrors do not contain labels,
// so we always return false.
func (we WriteError) HasErrorLabel(label 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
}
// 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 {
if mwe.WriteConcernError != nil && mwe.WriteConcernError.Code == code {
return true
}
for _, we := range mwe.WriteErrors {
if we.Code == code {
return true
}
}
return false
}
// HasErrorLabel returns true if the error contains the specified label.
func (mwe WriteException) HasErrorLabel(label string) bool {
if mwe.Labels != nil {
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 {
if bwe.WriteConcernError != nil && bwe.WriteConcernError.Code == code {
return true
}
for _, we := range bwe.WriteErrors {
if we.Code == code {
return true
}
}
return false
}
// HasErrorLabel returns true if the error contains the specified label.
func (bwe BulkWriteException) HasErrorLabel(label string) bool {
if bwe.Labels != nil {
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() {}
// 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.
rrAll returnResult = rrOne | rrMany // All means always return the result.
)
// 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) {
switch {
case err == driver.ErrUnacknowledgedWrite:
return rrAll, ErrUnacknowledgedWrite
case err != nil:
switch tt := err.(type) {
case driver.WriteCommandError:
return rrMany, WriteException{
WriteConcernError: convertDriverWriteConcernError(tt.WriteConcernError),
WriteErrors: writeErrorsFromDriverWriteErrors(tt.WriteErrors),
Labels: tt.Labels,
Raw: bson.Raw(tt.Raw),
}
default:
return rrNone, replaceErrors(err)
}
default:
return rrAll, nil
}
}
// 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()
}
+176
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 mongo
import (
"go.mongodb.org/mongo-driver/bson"
)
// IndexOptionsBuilder specifies options for a new index.
//
// Deprecated: Use the IndexOptions type in the mongo/options package instead.
type IndexOptionsBuilder struct {
document bson.D
}
// NewIndexOptionsBuilder creates a new IndexOptionsBuilder.
//
// Deprecated: Use the Index function in mongo/options instead.
func NewIndexOptionsBuilder() *IndexOptionsBuilder {
return &IndexOptionsBuilder{}
}
// Background specifies a value for the background option.
//
// Deprecated: Use the IndexOptions.SetBackground function in mongo/options instead.
func (iob *IndexOptionsBuilder) Background(background bool) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"background", background})
return iob
}
// ExpireAfterSeconds specifies a value for the expireAfterSeconds option.
//
// Deprecated: Use the IndexOptions.SetExpireAfterSeconds function in mongo/options instead.
func (iob *IndexOptionsBuilder) ExpireAfterSeconds(expireAfterSeconds int32) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"expireAfterSeconds", expireAfterSeconds})
return iob
}
// Name specifies a value for the name option.
//
// Deprecated: Use the IndexOptions.SetName function in mongo/options instead.
func (iob *IndexOptionsBuilder) Name(name string) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"name", name})
return iob
}
// Sparse specifies a value for the sparse option.
//
// Deprecated: Use the IndexOptions.SetSparse function in mongo/options instead.
func (iob *IndexOptionsBuilder) Sparse(sparse bool) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"sparse", sparse})
return iob
}
// StorageEngine specifies a value for the storageEngine option.
//
// Deprecated: Use the IndexOptions.SetStorageEngine function in mongo/options instead.
func (iob *IndexOptionsBuilder) StorageEngine(storageEngine interface{}) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"storageEngine", storageEngine})
return iob
}
// Unique specifies a value for the unique option.
//
// Deprecated: Use the IndexOptions.SetUnique function in mongo/options instead.
func (iob *IndexOptionsBuilder) Unique(unique bool) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"unique", unique})
return iob
}
// Version specifies a value for the version option.
//
// Deprecated: Use the IndexOptions.SetVersion function in mongo/options instead.
func (iob *IndexOptionsBuilder) Version(version int32) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"v", version})
return iob
}
// DefaultLanguage specifies a value for the default_language option.
//
// Deprecated: Use the IndexOptions.SetDefaultLanguage function in mongo/options instead.
func (iob *IndexOptionsBuilder) DefaultLanguage(defaultLanguage string) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"default_language", defaultLanguage})
return iob
}
// LanguageOverride specifies a value for the language_override option.
//
// Deprecated: Use the IndexOptions.SetLanguageOverride function in mongo/options instead.
func (iob *IndexOptionsBuilder) LanguageOverride(languageOverride string) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"language_override", languageOverride})
return iob
}
// TextVersion specifies a value for the textIndexVersion option.
//
// Deprecated: Use the IndexOptions.SetTextVersion function in mongo/options instead.
func (iob *IndexOptionsBuilder) TextVersion(textVersion int32) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"textIndexVersion", textVersion})
return iob
}
// Weights specifies a value for the weights option.
//
// Deprecated: Use the IndexOptions.SetWeights function in mongo/options instead.
func (iob *IndexOptionsBuilder) Weights(weights interface{}) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"weights", weights})
return iob
}
// SphereVersion specifies a value for the 2dsphereIndexVersion option.
//
// Deprecated: Use the IndexOptions.SetSphereVersion function in mongo/options instead.
func (iob *IndexOptionsBuilder) SphereVersion(sphereVersion int32) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"2dsphereIndexVersion", sphereVersion})
return iob
}
// Bits specifies a value for the bits option.
//
// Deprecated: Use the IndexOptions.SetBits function in mongo/options instead.
func (iob *IndexOptionsBuilder) Bits(bits int32) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"bits", bits})
return iob
}
// Max specifies a value for the max option.
//
// Deprecated: Use the IndexOptions.SetMax function in mongo/options instead.
func (iob *IndexOptionsBuilder) Max(max float64) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"max", max})
return iob
}
// Min specifies a value for the min option.
//
// Deprecated: Use the IndexOptions.SetMin function in mongo/options instead.
func (iob *IndexOptionsBuilder) Min(min float64) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"min", min})
return iob
}
// BucketSize specifies a value for the bucketSize option.
//
// Deprecated: Use the IndexOptions.SetBucketSize function in mongo/options instead.
func (iob *IndexOptionsBuilder) BucketSize(bucketSize int32) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"bucketSize", bucketSize})
return iob
}
// PartialFilterExpression specifies a value for the partialFilterExpression option.
//
// Deprecated: Use the IndexOptions.SetPartialFilterExpression function in mongo/options instead.
func (iob *IndexOptionsBuilder) PartialFilterExpression(partialFilterExpression interface{}) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"partialFilterExpression", partialFilterExpression})
return iob
}
// Collation specifies a value for the collation option.
//
// Deprecated: Use the IndexOptions.SetCollation function in mongo/options instead.
func (iob *IndexOptionsBuilder) Collation(collation interface{}) *IndexOptionsBuilder {
iob.document = append(iob.document, bson.E{"collation", collation})
return iob
}
// Build finishes constructing an the builder.
//
// Deprecated: Use the IndexOptions type in the mongo/options package instead.
func (iob *IndexOptionsBuilder) Build() bson.D {
return iob.document
}
+507
View File
@@ -0,0 +1,507 @@
// 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"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/mongo/description"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/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.
var ErrNonStringIndexName = errors.New("index name must be a string")
// ErrMultipleIndexDrop is returned if multiple indexes would be dropped from a call to IndexView.DropOne.
var ErrMultipleIndexDrop = errors.New("multiple indexes would be dropped")
// 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 interface{}
// The options to use to create the index.
Options *options.IndexOptions
}
func isNamespaceNotFoundError(err error) bool {
if de, ok := err.(driver.Error); ok {
return de.Code == 26
}
return false
}
// 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.ListIndexesOptions) (*Cursor, error) {
if ctx == nil {
ctx = context.Background()
}
sess := sessionFromContext(ctx)
if sess == nil && iv.coll.client.sessionPool != nil {
var err error
sess, err = session.NewClientSession(iv.coll.client.sessionPool, iv.coll.client.id, session.Implicit)
if err != nil {
return nil, err
}
}
err := iv.coll.client.validSession(sess)
if err != nil {
closeImplicitSession(sess)
return nil, err
}
selector := description.CompositeSelector([]description.ServerSelector{
description.ReadPrefSelector(readpref.Primary()),
description.LatencySelector(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)
cursorOpts := iv.coll.client.createBaseCursorOptions()
lio := options.MergeListIndexesOptions(opts...)
if lio.BatchSize != nil {
op = op.BatchSize(*lio.BatchSize)
cursorOpts.BatchSize = *lio.BatchSize
}
if lio.MaxTime != nil {
op = op.MaxTimeMS(int64(*lio.MaxTime / time.Millisecond))
}
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)
if isNamespaceNotFoundError(err) {
return newEmptyCursor(), nil
}
return nil, replaceErrors(err)
}
bc, err := op.Result(cursorOpts)
if err != nil {
closeImplicitSession(sess)
return nil, replaceErrors(err)
}
cursor, err := newCursorWithSession(bc, iv.coll.registry, sess)
return cursor, replaceErrors(err)
}
// ListSpecifications executes a List command and returns a slice of returned IndexSpecifications
func (iv IndexView) ListSpecifications(ctx context.Context, opts ...*options.ListIndexesOptions) ([]*IndexSpecification, error) {
cursor, err := iv.List(ctx, opts...)
if err != nil {
return nil, err
}
var results []*IndexSpecification
err = cursor.All(ctx, &results)
if err != nil {
return nil, err
}
ns := iv.coll.db.Name() + "." + iv.coll.Name()
for _, res := range results {
// Pre-4.4 servers report a namespace in their responses, so we only set Namespace manually if it was not in
// the response.
res.Namespace = ns
}
return results, 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.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.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")
}
keys, err := transformBsoncoreDocument(iv.coll.registry, model.Keys, false, "keys")
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, err = session.NewClientSession(iv.coll.client.sessionPool, iv.coll.client.id, session.Implicit)
if err != nil {
return nil, err
}
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 !writeconcern.AckWrite(wc) {
sess = nil
}
selector := makePinnedSelector(sess, iv.coll.writeSelector)
option := options.MergeCreateIndexesOptions(opts...)
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)
if option.MaxTime != nil {
op.MaxTimeMS(int64(*option.MaxTime / time.Millisecond))
}
if option.CommitQuorum != nil {
commitQuorum, err := transformValue(iv.coll.registry, option.CommitQuorum, true, "commitQuorum")
if err != nil {
return nil, err
}
op.CommitQuorum(commitQuorum)
}
err = op.Execute(ctx)
if err != nil {
_, err = processWriteError(err)
return nil, err
}
return names, nil
}
func (iv IndexView) createOptionsDoc(opts *options.IndexOptions) (bsoncore.Document, error) {
optsDoc := bsoncore.Document{}
if opts.Background != nil {
optsDoc = bsoncore.AppendBooleanElement(optsDoc, "background", *opts.Background)
}
if opts.ExpireAfterSeconds != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "expireAfterSeconds", *opts.ExpireAfterSeconds)
}
if opts.Name != nil {
optsDoc = bsoncore.AppendStringElement(optsDoc, "name", *opts.Name)
}
if opts.Sparse != nil {
optsDoc = bsoncore.AppendBooleanElement(optsDoc, "sparse", *opts.Sparse)
}
if opts.StorageEngine != nil {
doc, err := transformBsoncoreDocument(iv.coll.registry, opts.StorageEngine, true, "storageEngine")
if err != nil {
return nil, err
}
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "storageEngine", doc)
}
if opts.Unique != nil {
optsDoc = bsoncore.AppendBooleanElement(optsDoc, "unique", *opts.Unique)
}
if opts.Version != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "v", *opts.Version)
}
if opts.DefaultLanguage != nil {
optsDoc = bsoncore.AppendStringElement(optsDoc, "default_language", *opts.DefaultLanguage)
}
if opts.LanguageOverride != nil {
optsDoc = bsoncore.AppendStringElement(optsDoc, "language_override", *opts.LanguageOverride)
}
if opts.TextVersion != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "textIndexVersion", *opts.TextVersion)
}
if opts.Weights != nil {
doc, err := transformBsoncoreDocument(iv.coll.registry, opts.Weights, true, "weights")
if err != nil {
return nil, err
}
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "weights", doc)
}
if opts.SphereVersion != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "2dsphereIndexVersion", *opts.SphereVersion)
}
if opts.Bits != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "bits", *opts.Bits)
}
if opts.Max != nil {
optsDoc = bsoncore.AppendDoubleElement(optsDoc, "max", *opts.Max)
}
if opts.Min != nil {
optsDoc = bsoncore.AppendDoubleElement(optsDoc, "min", *opts.Min)
}
if opts.BucketSize != nil {
optsDoc = bsoncore.AppendInt32Element(optsDoc, "bucketSize", *opts.BucketSize)
}
if opts.PartialFilterExpression != nil {
doc, err := transformBsoncoreDocument(iv.coll.registry, opts.PartialFilterExpression, true, "partialFilterExpression")
if err != nil {
return nil, err
}
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "partialFilterExpression", doc)
}
if opts.Collation != nil {
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "collation", bsoncore.Document(opts.Collation.ToDocument()))
}
if opts.WildcardProjection != nil {
doc, err := transformBsoncoreDocument(iv.coll.registry, opts.WildcardProjection, true, "wildcardProjection")
if err != nil {
return nil, err
}
optsDoc = bsoncore.AppendDocumentElement(optsDoc, "wildcardProjection", doc)
}
if opts.Hidden != nil {
optsDoc = bsoncore.AppendBooleanElement(optsDoc, "hidden", *opts.Hidden)
}
return optsDoc, nil
}
func (iv IndexView) drop(ctx context.Context, name string, opts ...*options.DropIndexesOptions) (bson.Raw, error) {
if ctx == nil {
ctx = context.Background()
}
sess := sessionFromContext(ctx)
if sess == nil && iv.coll.client.sessionPool != nil {
var err error
sess, err = session.NewClientSession(iv.coll.client.sessionPool, iv.coll.client.id, session.Implicit)
if err != nil {
return nil, err
}
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 !writeconcern.AckWrite(wc) {
sess = nil
}
selector := makePinnedSelector(sess, iv.coll.writeSelector)
dio := options.MergeDropIndexesOptions(opts...)
op := operation.NewDropIndexes(name).
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)
if dio.MaxTime != nil {
op.MaxTimeMS(int64(*dio.MaxTime / time.Millisecond))
}
err = op.Execute(ctx)
if err != nil {
return nil, replaceErrors(err)
}
// TODO: it's weird to return a bson.Raw here because we have to convert the result back to BSON
ridx, res := bsoncore.AppendDocumentStart(nil)
res = bsoncore.AppendInt32Element(res, "nIndexesWas", op.Result().NIndexesWas)
res, _ = bsoncore.AppendDocumentEnd(res, ridx)
return res, nil
}
// DropOne executes a dropIndexes operation to drop an index on the collection. If the operation succeeds, this returns
// a BSON document in the form {nIndexesWas: <int32>}. The "nIndexesWas" field in the response contains the number of
// indexes that existed prior to the drop.
//
// 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.DropIndexesOptions) (bson.Raw, error) {
if name == "*" {
return nil, ErrMultipleIndexDrop
}
return iv.drop(ctx, name, opts...)
}
// DropAll executes a dropIndexes operation to drop all indexes on the collection. If the operation succeeds, this
// returns a BSON document in the form {nIndexesWas: <int32>}. The "nIndexesWas" field in the response contains the
// number of indexes that existed prior to the drop.
//
// 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.DropIndexesOptions) (bson.Raw, error) {
return iv.drop(ctx, "*", opts...)
}
func getOrGenerateIndexName(keySpecDocument bsoncore.Document, model IndexModel) (string, error) {
if model.Options != nil && model.Options.Name != nil {
return *model.Options.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 bsontype.Int32:
value = fmt.Sprintf("%d", bsonValue.Int32())
case bsontype.Int64:
value = fmt.Sprintf("%d", bsonValue.Int64())
case bsontype.String:
value = bsonValue.StringValue()
default:
return "", ErrInvalidIndexValue
}
_, err = name.WriteString(value)
if err != nil {
return "", err
}
first = false
}
return name.String(), nil
}
+402
View File
@@ -0,0 +1,402 @@
// 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/mongo"
import (
"context"
"errors"
"fmt"
"net"
"reflect"
"strconv"
"strings"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Dialer is used to make network connections.
type Dialer interface {
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
// BSONAppender is an interface implemented by types that can marshal a
// provided type into BSON bytes and append those bytes to the provided []byte.
// The AppendBSON can return a non-nil error and non-nil []byte. The AppendBSON
// method may also write incomplete BSON to the []byte.
type BSONAppender interface {
AppendBSON([]byte, interface{}) ([]byte, error)
}
// BSONAppenderFunc is an adapter function that allows any function that
// satisfies the AppendBSON method signature to be used where a BSONAppender is
// used.
type BSONAppenderFunc func([]byte, interface{}) ([]byte, error)
// AppendBSON implements the BSONAppender interface
func (baf BSONAppenderFunc) AppendBSON(dst []byte, val interface{}) ([]byte, error) {
return baf(dst, val)
}
// MarshalError is returned when attempting to transform a value into a document
// results in an error.
type MarshalError struct {
Value interface{}
Err error
}
// Error implements the error interface.
func (me MarshalError) Error() string {
return fmt.Sprintf("cannot transform type %s to a BSON Document: %v", reflect.TypeOf(me.Value), me.Err)
}
// 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
// transformAndEnsureID is a hack that makes it easy to get a RawValue as the _id value.
// It will also add an ObjectID _id as the first key if it not already present in the passed-in val.
func transformAndEnsureID(registry *bsoncodec.Registry, val interface{}) (bsoncore.Document, interface{}, error) {
if registry == nil {
registry = bson.NewRegistryBuilder().Build()
}
switch tt := val.(type) {
case nil:
return nil, nil, ErrNilDocument
case bsonx.Doc:
val = tt.Copy()
case []byte:
// Slight optimization so we'll just use MarshalBSON and not go through the codec machinery.
val = bson.Raw(tt)
}
// TODO(skriptble): Use a pool of these instead.
doc := make(bsoncore.Document, 0, 256)
doc, err := bson.MarshalAppendWithRegistry(registry, doc, val)
if err != nil {
return nil, nil, MarshalError{Value: val, Err: err}
}
var id interface{}
value := doc.Lookup("_id")
switch value.Type {
case bsontype.Type(0):
value = bsoncore.Value{Type: bsontype.ObjectID, Data: bsoncore.AppendObjectID(nil, primitive.NewObjectID())}
olddoc := doc
doc = make(bsoncore.Document, 0, len(olddoc)+17) // type byte + _id + null byte + object ID
_, doc = bsoncore.ReserveLength(doc)
doc = bsoncore.AppendValueElement(doc, "_id", value)
doc = append(doc, olddoc[4:]...) // remove the length
doc = bsoncore.UpdateLength(doc, 0, int32(len(doc)))
default:
// We copy the bytes here to ensure that any bytes returned to the user aren't modified
// later.
buf := make([]byte, len(value.Data))
copy(buf, value.Data)
value.Data = buf
}
err = bson.RawValue{Type: value.Type, Value: value.Data}.UnmarshalWithRegistry(registry, &id)
if err != nil {
return nil, nil, err
}
return doc, id, nil
}
func transformBsoncoreDocument(registry *bsoncodec.Registry, val interface{}, mapAllowed bool, paramName string) (bsoncore.Document, error) {
if registry == nil {
registry = bson.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)
}
if !mapAllowed {
refValue := reflect.ValueOf(val)
if refValue.Kind() == reflect.Map && refValue.Len() > 1 {
return nil, ErrMapForOrderedArgument{paramName}
}
}
// TODO(skriptble): Use a pool of these instead.
buf := make([]byte, 0, 256)
b, err := bson.MarshalAppendWithRegistry(registry, buf[:0], val)
if err != nil {
return nil, MarshalError{Value: val, Err: err}
}
return b, 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 transformAggregatePipeline(registry *bsoncodec.Registry, pipeline interface{}) (bsoncore.Document, bool, error) {
switch t := pipeline.(type) {
case bsoncodec.ValueMarshaler:
btype, val, err := t.MarshalBSONValue()
if err != nil {
return nil, false, err
}
if btype != bsontype.Array {
return nil, false, fmt.Errorf("ValueMarshaler returned a %v, but was expecting %v", btype, bsontype.Array)
}
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 transform 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 transformed. 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 := transformBsoncoreDocument(registry, val.Index(idx).Interface(), true, fmt.Sprintf("pipeline stage :%v", idx))
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 transformUpdateValue(registry *bsoncodec.Registry, update interface{}, 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 primitive.D, bsonx.Doc:
u.Type = bsontype.EmbeddedDocument
u.Data, err = transformBsoncoreDocument(registry, update, true, "update")
if err != nil {
return u, err
}
return u, documentCheckerFunc(u.Data)
case bson.Raw:
u.Type = bsontype.EmbeddedDocument
u.Data = t
return u, documentCheckerFunc(u.Data)
case bsoncore.Document:
u.Type = bsontype.EmbeddedDocument
u.Data = t
return u, documentCheckerFunc(u.Data)
case []byte:
u.Type = bsontype.EmbeddedDocument
u.Data = t
return u, documentCheckerFunc(u.Data)
case bsoncodec.Marshaler:
u.Type = bsontype.EmbeddedDocument
u.Data, err = t.MarshalBSON()
if err != nil {
return u, err
}
return u, documentCheckerFunc(u.Data)
case bsoncodec.ValueMarshaler:
u.Type, u.Data, err = t.MarshalBSONValue()
if err != nil {
return u, err
}
if u.Type != bsontype.Array && u.Type != bsontype.EmbeddedDocument {
return u, fmt.Errorf("ValueMarshaler returned a %v, but was expecting %v or %v", u.Type, bsontype.Array, bsontype.EmbeddedDocument)
}
return u, err
default:
val := reflect.ValueOf(t)
if !val.IsValid() {
return u, fmt.Errorf("can only transform slices and arrays into update pipelines, but got %v", val.Kind())
}
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
u.Type = bsontype.EmbeddedDocument
u.Data, err = transformBsoncoreDocument(registry, update, true, "update")
if err != nil {
return u, err
}
return u, documentCheckerFunc(u.Data)
}
u.Type = bsontype.Array
aidx, arr := bsoncore.AppendArrayStart(nil)
valLen := val.Len()
for idx := 0; idx < valLen; idx++ {
doc, err := transformBsoncoreDocument(registry, val.Index(idx).Interface(), true, "update")
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 transformValue(registry *bsoncodec.Registry, val interface{}, mapAllowed bool, paramName string) (bsoncore.Value, error) {
if registry == nil {
registry = bson.DefaultRegistry
}
if val == nil {
return bsoncore.Value{}, ErrNilValue
}
if !mapAllowed {
refValue := reflect.ValueOf(val)
if refValue.Kind() == reflect.Map && refValue.Len() > 1 {
return bsoncore.Value{}, ErrMapForOrderedArgument{paramName}
}
}
buf := make([]byte, 0, 256)
bsonType, bsonValue, err := bson.MarshalValueAppendWithRegistry(registry, buf[:0], val)
if err != nil {
return bsoncore.Value{}, MarshalError{Value: val, Err: err}
}
return bsoncore.Value{Type: bsonType, Data: bsonValue}, nil
}
// Build the aggregation pipeline for the CountDocument command.
func countDocumentsAggregatePipeline(registry *bsoncodec.Registry, filter interface{}, opts *options.CountOptions) (bsoncore.Document, error) {
filterDoc, err := transformBsoncoreDocument(registry, filter, true, "filter")
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 opts != nil {
if opts.Skip != nil {
didx, arr = bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(index))
arr = bsoncore.AppendInt64Element(arr, "$skip", *opts.Skip)
arr, _ = bsoncore.AppendDocumentEnd(arr, didx)
index++
}
if opts.Limit != nil {
didx, arr = bsoncore.AppendDocumentElementStart(arr, strconv.Itoa(index))
arr = bsoncore.AppendInt64Element(arr, "$limit", *opts.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)
}
+162
View File
@@ -0,0 +1,162 @@
// 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/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
const (
defaultServerSelectionTimeout = 10 * time.Second
defaultURI = "mongodb://localhost:27020"
defaultPath = "mongocryptd"
serverSelectionTimeoutStr = "server selection error"
)
var defaultTimeoutArgs = []string{"--idleShutdownTimeoutSecs=60"}
var databaseOpts = options.Database().SetReadConcern(readconcern.New()).SetReadPreference(readpref.Primary())
type mongocryptdClient struct {
bypassSpawn bool
client *Client
path string
spawnArgs []string
}
func newMongocryptdClient(cryptSharedLibAvailable bool, 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
// - the crypt_shared library is available because it replaces all mongocryptd functionality.
bypassSpawn: bypassSpawn || bypassAutoEncryption || bypassQueryAnalysis || cryptSharedLibAvailable,
}
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).DecodeBytes()
// 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).DecodeBytes()
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(ctx context.Context) error {
return mc.client.Connect(ctx)
}
// 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]interface{}) (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
}
+181
View File
@@ -0,0 +1,181 @@
// 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/bson"
)
// AggregateOptions represents options that can be used to configure an Aggregate operation.
type AggregateOptions struct {
// 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.
AllowDiskUse *bool
// The maximum number of documents to be included in each batch returned by the server.
BatchSize *int32
// If true, writes executed as part of the operation will opt out of document-level validation on the server. This
// option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is
// false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document
// validation.
BypassDocumentValidation *bool
// Specifies a collation to use for string comparisons during the operation. This option is only valid for MongoDB
// versions >= 3.4. For previous server versions, the driver will return an error if this option is used. The
// default value is nil, which means the default collation of the collection will be used.
Collation *Collation
// The maximum amount of time that the query can run on the server. The default value is nil, meaning that there
// is no time limit for query execution.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout option may be used
// in its place to control the amount of time that a single operation can run before returning an error. MaxTime
// is ignored if Timeout is set on the client.
MaxTime *time.Duration
// 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 MongoDB versions >= 3.2 and is ignored for previous server versions.
MaxAwaitTime *time.Duration
// A string 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.
Comment *string
// 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.
Hint interface{}
// 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").
Let interface{}
// Custom options to be added to aggregate expression. 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.
Custom bson.M
}
// Aggregate creates a new AggregateOptions instance.
func Aggregate() *AggregateOptions {
return &AggregateOptions{}
}
// SetAllowDiskUse sets the value for the AllowDiskUse field.
func (ao *AggregateOptions) SetAllowDiskUse(b bool) *AggregateOptions {
ao.AllowDiskUse = &b
return ao
}
// SetBatchSize sets the value for the BatchSize field.
func (ao *AggregateOptions) SetBatchSize(i int32) *AggregateOptions {
ao.BatchSize = &i
return ao
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field.
func (ao *AggregateOptions) SetBypassDocumentValidation(b bool) *AggregateOptions {
ao.BypassDocumentValidation = &b
return ao
}
// SetCollation sets the value for the Collation field.
func (ao *AggregateOptions) SetCollation(c *Collation) *AggregateOptions {
ao.Collation = c
return ao
}
// SetMaxTime sets the value for the MaxTime field.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can
// run before returning an error. MaxTime is ignored if Timeout is set on the client.
func (ao *AggregateOptions) SetMaxTime(d time.Duration) *AggregateOptions {
ao.MaxTime = &d
return ao
}
// SetMaxAwaitTime sets the value for the MaxAwaitTime field.
func (ao *AggregateOptions) SetMaxAwaitTime(d time.Duration) *AggregateOptions {
ao.MaxAwaitTime = &d
return ao
}
// SetComment sets the value for the Comment field.
func (ao *AggregateOptions) SetComment(s string) *AggregateOptions {
ao.Comment = &s
return ao
}
// SetHint sets the value for the Hint field.
func (ao *AggregateOptions) SetHint(h interface{}) *AggregateOptions {
ao.Hint = h
return ao
}
// SetLet sets the value for the Let field.
func (ao *AggregateOptions) SetLet(let interface{}) *AggregateOptions {
ao.Let = let
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 *AggregateOptions) SetCustom(c bson.M) *AggregateOptions {
ao.Custom = c
return ao
}
// MergeAggregateOptions combines the given AggregateOptions instances into a single AggregateOptions in a last-one-wins
// fashion.
func MergeAggregateOptions(opts ...*AggregateOptions) *AggregateOptions {
aggOpts := Aggregate()
for _, ao := range opts {
if ao == nil {
continue
}
if ao.AllowDiskUse != nil {
aggOpts.AllowDiskUse = ao.AllowDiskUse
}
if ao.BatchSize != nil {
aggOpts.BatchSize = ao.BatchSize
}
if ao.BypassDocumentValidation != nil {
aggOpts.BypassDocumentValidation = ao.BypassDocumentValidation
}
if ao.Collation != nil {
aggOpts.Collation = ao.Collation
}
if ao.MaxTime != nil {
aggOpts.MaxTime = ao.MaxTime
}
if ao.MaxAwaitTime != nil {
aggOpts.MaxAwaitTime = ao.MaxAwaitTime
}
if ao.Comment != nil {
aggOpts.Comment = ao.Comment
}
if ao.Hint != nil {
aggOpts.Hint = ao.Hint
}
if ao.Let != nil {
aggOpts.Let = ao.Let
}
if ao.Custom != nil {
aggOpts.Custom = ao.Custom
}
}
return aggOpts
}
@@ -0,0 +1,200 @@
// 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"
)
// AutoEncryptionOptions represents options 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 Client Side Encryption reduces the maximum document and message size (using a maxBsonObjectSize of 2MiB and
// maxMessageSizeBytes of 6MB) and may have a negative performance impact.
type AutoEncryptionOptions struct {
KeyVaultClientOptions *ClientOptions
KeyVaultNamespace string
KmsProviders map[string]map[string]interface{}
SchemaMap map[string]interface{}
BypassAutoEncryption *bool
ExtraOptions map[string]interface{}
TLSConfig map[string]*tls.Config
EncryptedFieldsMap map[string]interface{}
BypassQueryAnalysis *bool
}
// AutoEncryption creates a new AutoEncryptionOptions configured with default values.
func AutoEncryption() *AutoEncryptionOptions {
return &AutoEncryptionOptions{}
}
// 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]interface{}) *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 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]interface{}) *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]interface{}) *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.
//
// 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 (a *AutoEncryptionOptions) SetTLSConfig(tlsOpts map[string]*tls.Config) *AutoEncryptionOptions {
tlsConfigs := make(map[string]*tls.Config)
for provider, config := range tlsOpts {
// use TLS min version 1.2 to enforce more secure hash algorithms and advanced cipher suites
if config.MinVersion == 0 {
config.MinVersion = tls.VersionTLS12
}
tlsConfigs[provider] = config
}
a.TLSConfig = tlsConfigs
return a
}
// SetEncryptedFieldsMap specifies a map from namespace to local EncryptedFieldsMap document.
// EncryptedFieldsMap is used for Queryable Encryption.
// Queryable Encryption is in Public Technical Preview. Queryable Encryption should not be used in production and is subject to backwards breaking changes.
func (a *AutoEncryptionOptions) SetEncryptedFieldsMap(ef map[string]interface{}) *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.
// Queryable Encryption is in Public Technical Preview. Queryable Encryption should not be used in production and is subject to backwards breaking changes.
func (a *AutoEncryptionOptions) SetBypassQueryAnalysis(bypass bool) *AutoEncryptionOptions {
a.BypassQueryAnalysis = &bypass
return a
}
// MergeAutoEncryptionOptions combines the argued AutoEncryptionOptions in a last-one wins fashion.
func MergeAutoEncryptionOptions(opts ...*AutoEncryptionOptions) *AutoEncryptionOptions {
aeo := AutoEncryption()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.KeyVaultClientOptions != nil {
aeo.KeyVaultClientOptions = opt.KeyVaultClientOptions
}
if opt.KeyVaultNamespace != "" {
aeo.KeyVaultNamespace = opt.KeyVaultNamespace
}
if opt.KmsProviders != nil {
aeo.KmsProviders = opt.KmsProviders
}
if opt.SchemaMap != nil {
aeo.SchemaMap = opt.SchemaMap
}
if opt.BypassAutoEncryption != nil {
aeo.BypassAutoEncryption = opt.BypassAutoEncryption
}
if opt.ExtraOptions != nil {
aeo.ExtraOptions = opt.ExtraOptions
}
if opt.TLSConfig != nil {
aeo.TLSConfig = opt.TLSConfig
}
if opt.EncryptedFieldsMap != nil {
aeo.EncryptedFieldsMap = opt.EncryptedFieldsMap
}
if opt.BypassQueryAnalysis != nil {
aeo.BypassQueryAnalysis = opt.BypassQueryAnalysis
}
}
return aeo
}
+91
View File
@@ -0,0 +1,91 @@
// 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
// DefaultOrdered is the default value for the Ordered option in BulkWriteOptions.
var DefaultOrdered = true
// BulkWriteOptions represents options that can be used to configure a BulkWrite operation.
type BulkWriteOptions struct {
// If true, writes executed as part of the operation will opt out of document-level validation on the server. This
// option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is
// false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document
// validation.
BypassDocumentValidation *bool
// 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.
Comment interface{}
// If true, no writes will be executed after one fails. The default value is true.
Ordered *bool
// 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").
Let interface{}
}
// BulkWrite creates a new *BulkWriteOptions instance.
func BulkWrite() *BulkWriteOptions {
return &BulkWriteOptions{
Ordered: &DefaultOrdered,
}
}
// SetComment sets the value for the Comment field.
func (b *BulkWriteOptions) SetComment(comment interface{}) *BulkWriteOptions {
b.Comment = comment
return b
}
// SetOrdered sets the value for the Ordered field.
func (b *BulkWriteOptions) SetOrdered(ordered bool) *BulkWriteOptions {
b.Ordered = &ordered
return b
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field.
func (b *BulkWriteOptions) SetBypassDocumentValidation(bypass bool) *BulkWriteOptions {
b.BypassDocumentValidation = &bypass
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 *BulkWriteOptions) SetLet(let interface{}) *BulkWriteOptions {
b.Let = &let
return b
}
// MergeBulkWriteOptions combines the given BulkWriteOptions instances into a single BulkWriteOptions in a last-one-wins
// fashion.
func MergeBulkWriteOptions(opts ...*BulkWriteOptions) *BulkWriteOptions {
b := BulkWrite()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.Comment != nil {
b.Comment = opt.Comment
}
if opt.Ordered != nil {
b.Ordered = opt.Ordered
}
if opt.BypassDocumentValidation != nil {
b.BypassDocumentValidation = opt.BypassDocumentValidation
}
if opt.Let != nil {
b.Let = opt.Let
}
}
return b
}
+205
View File
@@ -0,0 +1,205 @@
// 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/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// ChangeStreamOptions represents options that can be used to configure a Watch operation.
type ChangeStreamOptions struct {
// The maximum number of documents to be included in each batch returned by the server.
BatchSize *int32
// Specifies a collation to use for string comparisons during the operation. This option is only valid for MongoDB
// versions >= 3.4. For previous server versions, the driver will return an error if this option is used. The
// default value is nil, which means the default collation of the collection will be used.
Collation *Collation
// A string 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.
Comment *string
// 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.
FullDocument *FullDocument
// 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.
FullDocumentBeforeChange *FullDocument
// The maximum amount of time that the server should wait for new documents to satisfy a tailable cursor query.
MaxAwaitTime *time.Duration
// 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.
ResumeAfter interface{}
// 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.
ShowExpandedEvents *bool
// If specified, the change stream will only return changes that occurred at or after the given timestamp. This
// option is only valid for MongoDB versions >= 4.0. If this is specified, ResumeAfter and StartAfter must not be
// set.
StartAtOperationTime *primitive.Timestamp
// 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.
StartAfter interface{}
// Custom options to be added to the initial aggregate for the change stream. 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.
Custom bson.M
// Custom options to be added to the $changeStream stage in the initial aggregate. 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.
CustomPipeline bson.M
}
// ChangeStream creates a new ChangeStreamOptions instance.
func ChangeStream() *ChangeStreamOptions {
cso := &ChangeStreamOptions{}
cso.SetFullDocument(Default)
return cso
}
// SetBatchSize sets the value for the BatchSize field.
func (cso *ChangeStreamOptions) SetBatchSize(i int32) *ChangeStreamOptions {
cso.BatchSize = &i
return cso
}
// SetCollation sets the value for the Collation field.
func (cso *ChangeStreamOptions) SetCollation(c Collation) *ChangeStreamOptions {
cso.Collation = &c
return cso
}
// SetComment sets the value for the Comment field.
func (cso *ChangeStreamOptions) SetComment(comment string) *ChangeStreamOptions {
cso.Comment = &comment
return cso
}
// SetFullDocument sets the value for the FullDocument field.
func (cso *ChangeStreamOptions) SetFullDocument(fd FullDocument) *ChangeStreamOptions {
cso.FullDocument = &fd
return cso
}
// SetFullDocumentBeforeChange sets the value for the FullDocumentBeforeChange field.
func (cso *ChangeStreamOptions) SetFullDocumentBeforeChange(fdbc FullDocument) *ChangeStreamOptions {
cso.FullDocumentBeforeChange = &fdbc
return cso
}
// SetMaxAwaitTime sets the value for the MaxAwaitTime field.
func (cso *ChangeStreamOptions) SetMaxAwaitTime(d time.Duration) *ChangeStreamOptions {
cso.MaxAwaitTime = &d
return cso
}
// SetResumeAfter sets the value for the ResumeAfter field.
func (cso *ChangeStreamOptions) SetResumeAfter(rt interface{}) *ChangeStreamOptions {
cso.ResumeAfter = rt
return cso
}
// SetShowExpandedEvents sets the value for the ShowExpandedEvents field.
func (cso *ChangeStreamOptions) SetShowExpandedEvents(see bool) *ChangeStreamOptions {
cso.ShowExpandedEvents = &see
return cso
}
// SetStartAtOperationTime sets the value for the StartAtOperationTime field.
func (cso *ChangeStreamOptions) SetStartAtOperationTime(t *primitive.Timestamp) *ChangeStreamOptions {
cso.StartAtOperationTime = t
return cso
}
// SetStartAfter sets the value for the StartAfter field.
func (cso *ChangeStreamOptions) SetStartAfter(sa interface{}) *ChangeStreamOptions {
cso.StartAfter = sa
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 *ChangeStreamOptions) SetCustom(c bson.M) *ChangeStreamOptions {
cso.Custom = c
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 *ChangeStreamOptions) SetCustomPipeline(cp bson.M) *ChangeStreamOptions {
cso.CustomPipeline = cp
return cso
}
// MergeChangeStreamOptions combines the given ChangeStreamOptions instances into a single ChangeStreamOptions in a
// last-one-wins fashion.
func MergeChangeStreamOptions(opts ...*ChangeStreamOptions) *ChangeStreamOptions {
csOpts := ChangeStream()
for _, cso := range opts {
if cso == nil {
continue
}
if cso.BatchSize != nil {
csOpts.BatchSize = cso.BatchSize
}
if cso.Collation != nil {
csOpts.Collation = cso.Collation
}
if cso.Comment != nil {
csOpts.Comment = cso.Comment
}
if cso.FullDocument != nil {
csOpts.FullDocument = cso.FullDocument
}
if cso.FullDocumentBeforeChange != nil {
csOpts.FullDocumentBeforeChange = cso.FullDocumentBeforeChange
}
if cso.MaxAwaitTime != nil {
csOpts.MaxAwaitTime = cso.MaxAwaitTime
}
if cso.ResumeAfter != nil {
csOpts.ResumeAfter = cso.ResumeAfter
}
if cso.ShowExpandedEvents != nil {
csOpts.ShowExpandedEvents = cso.ShowExpandedEvents
}
if cso.StartAtOperationTime != nil {
csOpts.StartAtOperationTime = cso.StartAtOperationTime
}
if cso.StartAfter != nil {
csOpts.StartAfter = cso.StartAfter
}
if cso.Custom != nil {
csOpts.Custom = cso.Custom
}
if cso.CustomPipeline != nil {
csOpts.CustomPipeline = cso.CustomPipeline
}
}
return csOpts
}
@@ -0,0 +1,138 @@
// 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"
)
// ClientEncryptionOptions represents all possible options used to configure a ClientEncryption instance.
type ClientEncryptionOptions struct {
KeyVaultNamespace string
KmsProviders map[string]map[string]interface{}
TLSConfig map[string]*tls.Config
}
// ClientEncryption creates a new ClientEncryptionOptions instance.
func ClientEncryption() *ClientEncryptionOptions {
return &ClientEncryptionOptions{}
}
// SetKeyVaultNamespace specifies the namespace of the key vault collection. This is required.
func (c *ClientEncryptionOptions) SetKeyVaultNamespace(ns string) *ClientEncryptionOptions {
c.KeyVaultNamespace = ns
return c
}
// SetKmsProviders specifies options for KMS providers. This is required.
func (c *ClientEncryptionOptions) SetKmsProviders(providers map[string]map[string]interface{}) *ClientEncryptionOptions {
c.KmsProviders = providers
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 *ClientEncryptionOptions) SetTLSConfig(tlsOpts map[string]*tls.Config) *ClientEncryptionOptions {
tlsConfigs := make(map[string]*tls.Config)
for provider, config := range tlsOpts {
// use TLS min version 1.2 to enforce more secure hash algorithms and advanced cipher suites
if config.MinVersion == 0 {
config.MinVersion = tls.VersionTLS12
}
tlsConfigs[provider] = config
}
c.TLSConfig = tlsConfigs
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]interface{}) (*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
}
// MergeClientEncryptionOptions combines the argued ClientEncryptionOptions in a last-one wins fashion.
func MergeClientEncryptionOptions(opts ...*ClientEncryptionOptions) *ClientEncryptionOptions {
ceo := ClientEncryption()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.KeyVaultNamespace != "" {
ceo.KeyVaultNamespace = opt.KeyVaultNamespace
}
if opt.KmsProviders != nil {
ceo.KmsProviders = opt.KmsProviders
}
if opt.TLSConfig != nil {
ceo.TLSConfig = opt.TLSConfig
}
}
return ceo
}
File diff suppressed because it is too large Load Diff
+88
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 options
import (
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
)
// CollectionOptions represents options that can be used to configure a Collection.
type CollectionOptions struct {
// 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.
ReadConcern *readconcern.ReadConcern
// 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.
WriteConcern *writeconcern.WriteConcern
// 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.
ReadPreference *readpref.ReadPref
// 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.
Registry *bsoncodec.Registry
}
// Collection creates a new CollectionOptions instance.
func Collection() *CollectionOptions {
return &CollectionOptions{}
}
// SetReadConcern sets the value for the ReadConcern field.
func (c *CollectionOptions) SetReadConcern(rc *readconcern.ReadConcern) *CollectionOptions {
c.ReadConcern = rc
return c
}
// SetWriteConcern sets the value for the WriteConcern field.
func (c *CollectionOptions) SetWriteConcern(wc *writeconcern.WriteConcern) *CollectionOptions {
c.WriteConcern = wc
return c
}
// SetReadPreference sets the value for the ReadPreference field.
func (c *CollectionOptions) SetReadPreference(rp *readpref.ReadPref) *CollectionOptions {
c.ReadPreference = rp
return c
}
// SetRegistry sets the value for the Registry field.
func (c *CollectionOptions) SetRegistry(r *bsoncodec.Registry) *CollectionOptions {
c.Registry = r
return c
}
// MergeCollectionOptions combines the given CollectionOptions instances into a single *CollectionOptions in a
// last-one-wins fashion.
func MergeCollectionOptions(opts ...*CollectionOptions) *CollectionOptions {
c := Collection()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.ReadConcern != nil {
c.ReadConcern = opt.ReadConcern
}
if opt.WriteConcern != nil {
c.WriteConcern = opt.WriteConcern
}
if opt.ReadPreference != nil {
c.ReadPreference = opt.ReadPreference
}
if opt.Registry != nil {
c.Registry = opt.Registry
}
}
return c
}
+119
View File
@@ -0,0 +1,119 @@
// 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"
// CountOptions represents options that can be used to configure a CountDocuments operation.
type CountOptions struct {
// Specifies a collation to use for string comparisons during the operation. This option is only valid for MongoDB
// versions >= 3.4. For previous server versions, the driver will return an error if this option is used. The
// default value is nil, which means the default collation of the collection will be used.
Collation *Collation
// TODO(GODRIVER-2386): CountOptions executor uses aggregation under the hood, which means this type has to be
// TODO a string for now. This can be replaced with `Comment interface{}` once 2386 is implemented.
// 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.
Comment *string
// 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.
Hint interface{}
// 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.
Limit *int64
// The maximum amount of time that the query can run on the server. The default value is nil, meaning that there is
// no time limit for query execution.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout option may be used in
// its place to control the amount of time that a single operation can run before returning an error. MaxTime is
// ignored if Timeout is set on the client.
MaxTime *time.Duration
// The number of documents to skip before counting. The default value is 0.
Skip *int64
}
// Count creates a new CountOptions instance.
func Count() *CountOptions {
return &CountOptions{}
}
// SetCollation sets the value for the Collation field.
func (co *CountOptions) SetCollation(c *Collation) *CountOptions {
co.Collation = c
return co
}
// SetComment sets the value for the Comment field.
func (co *CountOptions) SetComment(c string) *CountOptions {
co.Comment = &c
return co
}
// SetHint sets the value for the Hint field.
func (co *CountOptions) SetHint(h interface{}) *CountOptions {
co.Hint = h
return co
}
// SetLimit sets the value for the Limit field.
func (co *CountOptions) SetLimit(i int64) *CountOptions {
co.Limit = &i
return co
}
// SetMaxTime sets the value for the MaxTime field.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can
// run before returning an error. MaxTime is ignored if Timeout is set on the client.
func (co *CountOptions) SetMaxTime(d time.Duration) *CountOptions {
co.MaxTime = &d
return co
}
// SetSkip sets the value for the Skip field.
func (co *CountOptions) SetSkip(i int64) *CountOptions {
co.Skip = &i
return co
}
// MergeCountOptions combines the given CountOptions instances into a single CountOptions in a last-one-wins fashion.
func MergeCountOptions(opts ...*CountOptions) *CountOptions {
countOpts := Count()
for _, co := range opts {
if co == nil {
continue
}
if co.Collation != nil {
countOpts.Collation = co.Collation
}
if co.Comment != nil {
countOpts.Comment = co.Comment
}
if co.Hint != nil {
countOpts.Hint = co.Hint
}
if co.Limit != nil {
countOpts.Limit = co.Limit
}
if co.MaxTime != nil {
countOpts.MaxTime = co.MaxTime
}
if co.Skip != nil {
countOpts.Skip = co.Skip
}
}
return countOpts
}
@@ -0,0 +1,326 @@
// 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
// DefaultIndexOptions represents the default options for a collection to apply on new indexes. This type can be used
// when creating a new collection through the CreateCollectionOptions.SetDefaultIndexOptions method.
type DefaultIndexOptions struct {
// 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.
StorageEngine interface{}
}
// DefaultIndex creates a new DefaultIndexOptions instance.
func DefaultIndex() *DefaultIndexOptions {
return &DefaultIndexOptions{}
}
// SetStorageEngine sets the value for the StorageEngine field.
func (d *DefaultIndexOptions) SetStorageEngine(storageEngine interface{}) *DefaultIndexOptions {
d.StorageEngine = storageEngine
return d
}
// TimeSeriesOptions specifies options on a time-series collection.
type TimeSeriesOptions struct {
// Name of 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).
TimeField string
// Optional 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.
MetaField *string
// Optional string specifying granularity of time-series data. Allowed granularity options are
// "seconds", "minutes" and "hours".
Granularity *string
}
// TimeSeries creates a new TimeSeriesOptions instance.
func TimeSeries() *TimeSeriesOptions {
return &TimeSeriesOptions{}
}
// SetTimeField sets the value for the TimeField.
func (tso *TimeSeriesOptions) SetTimeField(timeField string) *TimeSeriesOptions {
tso.TimeField = timeField
return tso
}
// SetMetaField sets the value for the MetaField.
func (tso *TimeSeriesOptions) SetMetaField(metaField string) *TimeSeriesOptions {
tso.MetaField = &metaField
return tso
}
// SetGranularity sets the value for Granularity.
func (tso *TimeSeriesOptions) SetGranularity(granularity string) *TimeSeriesOptions {
tso.Granularity = &granularity
return tso
}
// CreateCollectionOptions represents options that can be used to configure a CreateCollection operation.
type CreateCollectionOptions struct {
// 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.
Capped *bool
// Specifies the default collation for the new collection. This option is only valid for MongoDB versions >= 3.4.
// For previous server versions, the driver will return an error if this option is used. The default value is nil.
Collation *Collation
// 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.
ChangeStreamPreAndPostImages interface{}
// Specifies a default configuration for indexes on the collection. This option is only valid for MongoDB versions
// >= 3.4. The default value is nil, meaning indexes will be configured using server defaults.
DefaultIndexOptions *DefaultIndexOptions
// 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.
MaxDocuments *int64
// Specifies the maximum size in bytes for a capped collection. The default value is 0.
SizeInBytes *int64
// 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.
StorageEngine interface{}
// 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. This option is only valid for MongoDB versions >= 3.2. The default value is "error".
ValidationAction *string
// 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. This option is
// only valid for MongoDB versions >= 3.2. The default value is "strict".
ValidationLevel *string
// A document specifying validation rules for the collection. See
// https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about schema validation. This option
// is only valid for MongoDB versions >= 3.2. The default value is nil, meaning no validator will be used for the
// collection.
Validator interface{}
// 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
ExpireAfterSeconds *int64
// 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
TimeSeriesOptions *TimeSeriesOptions
// EncryptedFields configures encrypted fields.
//
// This option is only valid for MongoDB versions >= 6.0
EncryptedFields interface{}
// ClusteredIndex is used to create a collection with a clustered index.
//
// This option is only valid for MongoDB versions >= 5.3
ClusteredIndex interface{}
}
// CreateCollection creates a new CreateCollectionOptions instance.
func CreateCollection() *CreateCollectionOptions {
return &CreateCollectionOptions{}
}
// SetCapped sets the value for the Capped field.
func (c *CreateCollectionOptions) SetCapped(capped bool) *CreateCollectionOptions {
c.Capped = &capped
return c
}
// SetCollation sets the value for the Collation field.
func (c *CreateCollectionOptions) SetCollation(collation *Collation) *CreateCollectionOptions {
c.Collation = collation
return c
}
// SetChangeStreamPreAndPostImages sets the value for the ChangeStreamPreAndPostImages field.
func (c *CreateCollectionOptions) SetChangeStreamPreAndPostImages(csppi interface{}) *CreateCollectionOptions {
c.ChangeStreamPreAndPostImages = &csppi
return c
}
// SetDefaultIndexOptions sets the value for the DefaultIndexOptions field.
func (c *CreateCollectionOptions) SetDefaultIndexOptions(opts *DefaultIndexOptions) *CreateCollectionOptions {
c.DefaultIndexOptions = opts
return c
}
// SetMaxDocuments sets the value for the MaxDocuments field.
func (c *CreateCollectionOptions) SetMaxDocuments(max int64) *CreateCollectionOptions {
c.MaxDocuments = &max
return c
}
// SetSizeInBytes sets the value for the SizeInBytes field.
func (c *CreateCollectionOptions) SetSizeInBytes(size int64) *CreateCollectionOptions {
c.SizeInBytes = &size
return c
}
// SetStorageEngine sets the value for the StorageEngine field.
func (c *CreateCollectionOptions) SetStorageEngine(storageEngine interface{}) *CreateCollectionOptions {
c.StorageEngine = &storageEngine
return c
}
// SetValidationAction sets the value for the ValidationAction field.
func (c *CreateCollectionOptions) SetValidationAction(action string) *CreateCollectionOptions {
c.ValidationAction = &action
return c
}
// SetValidationLevel sets the value for the ValidationLevel field.
func (c *CreateCollectionOptions) SetValidationLevel(level string) *CreateCollectionOptions {
c.ValidationLevel = &level
return c
}
// SetValidator sets the value for the Validator field.
func (c *CreateCollectionOptions) SetValidator(validator interface{}) *CreateCollectionOptions {
c.Validator = validator
return c
}
// SetExpireAfterSeconds sets the value for the ExpireAfterSeconds field.
func (c *CreateCollectionOptions) SetExpireAfterSeconds(eas int64) *CreateCollectionOptions {
c.ExpireAfterSeconds = &eas
return c
}
// SetTimeSeriesOptions sets the options for time-series collections.
func (c *CreateCollectionOptions) SetTimeSeriesOptions(timeSeriesOpts *TimeSeriesOptions) *CreateCollectionOptions {
c.TimeSeriesOptions = timeSeriesOpts
return c
}
// SetEncryptedFields sets the encrypted fields for encrypted collections.
func (c *CreateCollectionOptions) SetEncryptedFields(encryptedFields interface{}) *CreateCollectionOptions {
c.EncryptedFields = encryptedFields
return c
}
// SetClusteredIndex sets the value for the ClusteredIndex field.
func (c *CreateCollectionOptions) SetClusteredIndex(clusteredIndex interface{}) *CreateCollectionOptions {
c.ClusteredIndex = clusteredIndex
return c
}
// MergeCreateCollectionOptions combines the given CreateCollectionOptions instances into a single
// CreateCollectionOptions in a last-one-wins fashion.
func MergeCreateCollectionOptions(opts ...*CreateCollectionOptions) *CreateCollectionOptions {
cc := CreateCollection()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.Capped != nil {
cc.Capped = opt.Capped
}
if opt.Collation != nil {
cc.Collation = opt.Collation
}
if opt.ChangeStreamPreAndPostImages != nil {
cc.ChangeStreamPreAndPostImages = opt.ChangeStreamPreAndPostImages
}
if opt.DefaultIndexOptions != nil {
cc.DefaultIndexOptions = opt.DefaultIndexOptions
}
if opt.MaxDocuments != nil {
cc.MaxDocuments = opt.MaxDocuments
}
if opt.SizeInBytes != nil {
cc.SizeInBytes = opt.SizeInBytes
}
if opt.StorageEngine != nil {
cc.StorageEngine = opt.StorageEngine
}
if opt.ValidationAction != nil {
cc.ValidationAction = opt.ValidationAction
}
if opt.ValidationLevel != nil {
cc.ValidationLevel = opt.ValidationLevel
}
if opt.Validator != nil {
cc.Validator = opt.Validator
}
if opt.ExpireAfterSeconds != nil {
cc.ExpireAfterSeconds = opt.ExpireAfterSeconds
}
if opt.TimeSeriesOptions != nil {
cc.TimeSeriesOptions = opt.TimeSeriesOptions
}
if opt.EncryptedFields != nil {
cc.EncryptedFields = opt.EncryptedFields
}
if opt.ClusteredIndex != nil {
cc.ClusteredIndex = opt.ClusteredIndex
}
}
return cc
}
// CreateViewOptions represents options that can be used to configure a CreateView operation.
type CreateViewOptions struct {
// Specifies the default collation for the new collection. This option is only valid for MongoDB versions >= 3.4.
// For previous server versions, the driver will return an error if this option is used. The default value is nil.
Collation *Collation
}
// CreateView creates an new CreateViewOptions instance.
func CreateView() *CreateViewOptions {
return &CreateViewOptions{}
}
// SetCollation sets the value for the Collation field.
func (c *CreateViewOptions) SetCollation(collation *Collation) *CreateViewOptions {
c.Collation = collation
return c
}
// MergeCreateViewOptions combines the given CreateViewOptions instances into a single CreateViewOptions in a
// last-one-wins fashion.
func MergeCreateViewOptions(opts ...*CreateViewOptions) *CreateViewOptions {
cv := CreateView()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.Collation != nil {
cv.Collation = opt.Collation
}
}
return cv
}
+95
View File
@@ -0,0 +1,95 @@
// 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.
type DataKeyOptions struct {
MasterKey interface{}
KeyAltNames []string
// KeyMaterial is 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).
KeyMaterial []byte
}
// DataKey creates a new DataKeyOptions instance.
func DataKey() *DataKeyOptions {
return &DataKeyOptions{}
}
// 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 *DataKeyOptions) SetMasterKey(masterKey interface{}) *DataKeyOptions {
dk.MasterKey = masterKey
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 *DataKeyOptions) SetKeyAltNames(keyAltNames []string) *DataKeyOptions {
dk.KeyAltNames = keyAltNames
return dk
}
// SetKeyMaterial will set a custom keyMaterial to DataKeyOptions which can be used to encrypt data.
func (dk *DataKeyOptions) SetKeyMaterial(keyMaterial []byte) *DataKeyOptions {
dk.KeyMaterial = keyMaterial
return dk
}
// MergeDataKeyOptions combines the argued DataKeyOptions in a last-one wins fashion.
func MergeDataKeyOptions(opts ...*DataKeyOptions) *DataKeyOptions {
dko := DataKey()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.MasterKey != nil {
dko.MasterKey = opt.MasterKey
}
if opt.KeyAltNames != nil {
dko.KeyAltNames = opt.KeyAltNames
}
if opt.KeyMaterial != nil {
dko.KeyMaterial = opt.KeyMaterial
}
}
return dko
}
+88
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 options
import (
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
)
// DatabaseOptions represents options that can be used to configure a Database.
type DatabaseOptions struct {
// 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.
ReadConcern *readconcern.ReadConcern
// 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.
WriteConcern *writeconcern.WriteConcern
// 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.
ReadPreference *readpref.ReadPref
// 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.
Registry *bsoncodec.Registry
}
// Database creates a new DatabaseOptions instance.
func Database() *DatabaseOptions {
return &DatabaseOptions{}
}
// SetReadConcern sets the value for the ReadConcern field.
func (d *DatabaseOptions) SetReadConcern(rc *readconcern.ReadConcern) *DatabaseOptions {
d.ReadConcern = rc
return d
}
// SetWriteConcern sets the value for the WriteConcern field.
func (d *DatabaseOptions) SetWriteConcern(wc *writeconcern.WriteConcern) *DatabaseOptions {
d.WriteConcern = wc
return d
}
// SetReadPreference sets the value for the ReadPreference field.
func (d *DatabaseOptions) SetReadPreference(rp *readpref.ReadPref) *DatabaseOptions {
d.ReadPreference = rp
return d
}
// SetRegistry sets the value for the Registry field.
func (d *DatabaseOptions) SetRegistry(r *bsoncodec.Registry) *DatabaseOptions {
d.Registry = r
return d
}
// MergeDatabaseOptions combines the given DatabaseOptions instances into a single DatabaseOptions in a last-one-wins
// fashion.
func MergeDatabaseOptions(opts ...*DatabaseOptions) *DatabaseOptions {
d := Database()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.ReadConcern != nil {
d.ReadConcern = opt.ReadConcern
}
if opt.WriteConcern != nil {
d.WriteConcern = opt.WriteConcern
}
if opt.ReadPreference != nil {
d.ReadPreference = opt.ReadPreference
}
if opt.Registry != nil {
d.Registry = opt.Registry
}
}
return d
}
+86
View File
@@ -0,0 +1,86 @@
// 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
// DeleteOptions represents options that can be used to configure DeleteOne and DeleteMany operations.
type DeleteOptions struct {
// Specifies a collation to use for string comparisons during the operation. This option is only valid for MongoDB
// versions >= 3.4. For previous server versions, the driver will return an error if this option is used. The
// default value is nil, which means the default collation of the collection will be used.
Collation *Collation
// 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.
Comment interface{}
// 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 >= 3.4 will return an error
// if this option is specified. For server versions < 3.4, the driver will return a client-side 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.
Hint interface{}
// 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").
Let interface{}
}
// Delete creates a new DeleteOptions instance.
func Delete() *DeleteOptions {
return &DeleteOptions{}
}
// SetCollation sets the value for the Collation field.
func (do *DeleteOptions) SetCollation(c *Collation) *DeleteOptions {
do.Collation = c
return do
}
// SetComment sets the value for the Comment field.
func (do *DeleteOptions) SetComment(comment interface{}) *DeleteOptions {
do.Comment = comment
return do
}
// SetHint sets the value for the Hint field.
func (do *DeleteOptions) SetHint(hint interface{}) *DeleteOptions {
do.Hint = hint
return do
}
// SetLet sets the value for the Let field.
func (do *DeleteOptions) SetLet(let interface{}) *DeleteOptions {
do.Let = let
return do
}
// MergeDeleteOptions combines the given DeleteOptions instances into a single DeleteOptions in a last-one-wins fashion.
func MergeDeleteOptions(opts ...*DeleteOptions) *DeleteOptions {
dOpts := Delete()
for _, do := range opts {
if do == nil {
continue
}
if do.Collation != nil {
dOpts.Collation = do.Collation
}
if do.Comment != nil {
dOpts.Comment = do.Comment
}
if do.Hint != nil {
dOpts.Hint = do.Hint
}
if do.Let != nil {
dOpts.Let = do.Let
}
}
return dOpts
}
+78
View File
@@ -0,0 +1,78 @@
// 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"
// DistinctOptions represents options that can be used to configure a Distinct operation.
type DistinctOptions struct {
// Specifies a collation to use for string comparisons during the operation. This option is only valid for MongoDB
// versions >= 3.4. For previous server versions, the driver will return an error if this option is used. The
// default value is nil, which means the default collation of the collection will be used.
Collation *Collation
// 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.
Comment interface{}
// The maximum amount of time that the query can run on the server. The default value is nil, meaning that there
// is no time limit for query execution.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout option may be
// used in its place to control the amount of time that a single operation can run before returning an error.
// MaxTime is ignored if Timeout is set on the client.
MaxTime *time.Duration
}
// Distinct creates a new DistinctOptions instance.
func Distinct() *DistinctOptions {
return &DistinctOptions{}
}
// SetCollation sets the value for the Collation field.
func (do *DistinctOptions) SetCollation(c *Collation) *DistinctOptions {
do.Collation = c
return do
}
// SetComment sets the value for the Comment field.
func (do *DistinctOptions) SetComment(comment interface{}) *DistinctOptions {
do.Comment = comment
return do
}
// SetMaxTime sets the value for the MaxTime field.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can
// run before returning an error. MaxTime is ignored if Timeout is set on the client.
func (do *DistinctOptions) SetMaxTime(d time.Duration) *DistinctOptions {
do.MaxTime = &d
return do
}
// MergeDistinctOptions combines the given DistinctOptions instances into a single DistinctOptions in a last-one-wins
// fashion.
func MergeDistinctOptions(opts ...*DistinctOptions) *DistinctOptions {
distinctOpts := Distinct()
for _, do := range opts {
if do == nil {
continue
}
if do.Collation != nil {
distinctOpts.Collation = do.Collation
}
if do.Comment != nil {
distinctOpts.Comment = do.Comment
}
if do.MaxTime != nil {
distinctOpts.MaxTime = do.MaxTime
}
}
return distinctOpts
}
+103
View File
@@ -0,0 +1,103 @@
// 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/bson/primitive"
)
// These constants specify valid values for QueryType
// QueryType is used for Queryable Encryption.
// Queryable Encryption is in Public Technical Preview. Queryable Encryption should not be used in production and is subject to backwards breaking changes.
const (
QueryTypeEquality string = "equality"
)
// EncryptOptions represents options to explicitly encrypt a value.
type EncryptOptions struct {
KeyID *primitive.Binary
KeyAltName *string
Algorithm string
QueryType string
ContentionFactor *int64
}
// Encrypt creates a new EncryptOptions instance.
func Encrypt() *EncryptOptions {
return &EncryptOptions{}
}
// SetKeyID specifies an _id of a data key. This should be a UUID (a primitive.Binary with subtype 4).
func (e *EncryptOptions) SetKeyID(keyID primitive.Binary) *EncryptOptions {
e.KeyID = &keyID
return e
}
// SetKeyAltName identifies a key vault document by 'keyAltName'.
func (e *EncryptOptions) SetKeyAltName(keyAltName string) *EncryptOptions {
e.KeyAltName = &keyAltName
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
// This is required.
// Indexed and Unindexed are used for Queryable Encryption.
// Queryable Encryption is in Public Technical Preview. Queryable Encryption should not be used in production and is subject to backwards breaking changes.
func (e *EncryptOptions) SetAlgorithm(algorithm string) *EncryptOptions {
e.Algorithm = algorithm
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.
// Queryable Encryption is in Public Technical Preview. Queryable Encryption should not be used in production and is subject to backwards breaking changes.
func (e *EncryptOptions) SetQueryType(queryType string) *EncryptOptions {
e.QueryType = queryType
return e
}
// SetContentionFactor specifies the contention factor. It is only valid to set if algorithm is "Indexed".
// ContentionFactor is used for Queryable Encryption.
// Queryable Encryption is in Public Technical Preview. Queryable Encryption should not be used in production and is subject to backwards breaking changes.
func (e *EncryptOptions) SetContentionFactor(contentionFactor int64) *EncryptOptions {
e.ContentionFactor = &contentionFactor
return e
}
// MergeEncryptOptions combines the argued EncryptOptions in a last-one wins fashion.
func MergeEncryptOptions(opts ...*EncryptOptions) *EncryptOptions {
eo := Encrypt()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.KeyID != nil {
eo.KeyID = opt.KeyID
}
if opt.KeyAltName != nil {
eo.KeyAltName = opt.KeyAltName
}
if opt.Algorithm != "" {
eo.Algorithm = opt.Algorithm
}
if opt.QueryType != "" {
eo.QueryType = opt.QueryType
}
if opt.ContentionFactor != nil {
eo.ContentionFactor = opt.ContentionFactor
}
}
return eo
}
@@ -0,0 +1,64 @@
// 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"
// EstimatedDocumentCountOptions represents options that can be used to configure an EstimatedDocumentCount operation.
type EstimatedDocumentCountOptions struct {
// 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.
Comment interface{}
// The maximum amount of time that the query can run on the server. The default value is nil, meaning that there
// is no time limit for query execution.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout option may be used
// in its place to control the amount of time that a single operation can run before returning an error. MaxTime
// is ignored if Timeout is set on the client.
MaxTime *time.Duration
}
// EstimatedDocumentCount creates a new EstimatedDocumentCountOptions instance.
func EstimatedDocumentCount() *EstimatedDocumentCountOptions {
return &EstimatedDocumentCountOptions{}
}
// SetComment sets the value for the Comment field.
func (eco *EstimatedDocumentCountOptions) SetComment(comment interface{}) *EstimatedDocumentCountOptions {
eco.Comment = comment
return eco
}
// SetMaxTime sets the value for the MaxTime field.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout option
// may be used in its place to control the amount of time that a single operation can run before
// returning an error. MaxTime is ignored if Timeout is set on the client.
func (eco *EstimatedDocumentCountOptions) SetMaxTime(d time.Duration) *EstimatedDocumentCountOptions {
eco.MaxTime = &d
return eco
}
// MergeEstimatedDocumentCountOptions combines the given EstimatedDocumentCountOptions instances into a single
// EstimatedDocumentCountOptions in a last-one-wins fashion.
func MergeEstimatedDocumentCountOptions(opts ...*EstimatedDocumentCountOptions) *EstimatedDocumentCountOptions {
e := EstimatedDocumentCount()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.Comment != nil {
e.Comment = opt.Comment
}
if opt.MaxTime != nil {
e.MaxTime = opt.MaxTime
}
}
return e
}
File diff suppressed because it is too large Load Diff
+329
View File
@@ -0,0 +1,329 @@
// 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/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/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 options that can be used to configure GridFS bucket.
type BucketOptions struct {
// The name of the bucket. The default value is "fs".
Name *string
// The number of bytes in each chunk in the bucket. The default value is 255 KiB.
ChunkSizeBytes *int32
// The write concern for the bucket. The default value is the write concern of the database from which the bucket
// is created.
WriteConcern *writeconcern.WriteConcern
// The read concern for the bucket. The default value is the read concern of the database from which the bucket
// is created.
ReadConcern *readconcern.ReadConcern
// The read preference for the bucket. The default value is the read preference of the database from which the
// bucket is created.
ReadPreference *readpref.ReadPref
}
// GridFSBucket creates a new BucketOptions instance.
func GridFSBucket() *BucketOptions {
return &BucketOptions{
Name: &DefaultName,
ChunkSizeBytes: &DefaultChunkSize,
}
}
// SetName sets the value for the Name field.
func (b *BucketOptions) SetName(name string) *BucketOptions {
b.Name = &name
return b
}
// SetChunkSizeBytes sets the value for the ChunkSize field.
func (b *BucketOptions) SetChunkSizeBytes(i int32) *BucketOptions {
b.ChunkSizeBytes = &i
return b
}
// SetWriteConcern sets the value for the WriteConcern field.
func (b *BucketOptions) SetWriteConcern(wc *writeconcern.WriteConcern) *BucketOptions {
b.WriteConcern = wc
return b
}
// SetReadConcern sets the value for the ReadConcern field.
func (b *BucketOptions) SetReadConcern(rc *readconcern.ReadConcern) *BucketOptions {
b.ReadConcern = rc
return b
}
// SetReadPreference sets the value for the ReadPreference field.
func (b *BucketOptions) SetReadPreference(rp *readpref.ReadPref) *BucketOptions {
b.ReadPreference = rp
return b
}
// MergeBucketOptions combines the given BucketOptions instances into a single BucketOptions in a last-one-wins fashion.
func MergeBucketOptions(opts ...*BucketOptions) *BucketOptions {
b := GridFSBucket()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.Name != nil {
b.Name = opt.Name
}
if opt.ChunkSizeBytes != nil {
b.ChunkSizeBytes = opt.ChunkSizeBytes
}
if opt.WriteConcern != nil {
b.WriteConcern = opt.WriteConcern
}
if opt.ReadConcern != nil {
b.ReadConcern = opt.ReadConcern
}
if opt.ReadPreference != nil {
b.ReadPreference = opt.ReadPreference
}
}
return b
}
// UploadOptions represents options that can be used to configure a GridFS upload operation.
type UploadOptions struct {
// The number of bytes in each chunk in the bucket. The default value is DefaultChunkSize (255 KiB).
ChunkSizeBytes *int32
// 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.
Metadata interface{}
// The BSON registry to use for converting filters to BSON documents. The default value is bson.DefaultRegistry.
Registry *bsoncodec.Registry
}
// GridFSUpload creates a new UploadOptions instance.
func GridFSUpload() *UploadOptions {
return &UploadOptions{Registry: bson.DefaultRegistry}
}
// SetChunkSizeBytes sets the value for the ChunkSize field.
func (u *UploadOptions) SetChunkSizeBytes(i int32) *UploadOptions {
u.ChunkSizeBytes = &i
return u
}
// SetMetadata sets the value for the Metadata field.
func (u *UploadOptions) SetMetadata(doc interface{}) *UploadOptions {
u.Metadata = doc
return u
}
// MergeUploadOptions combines the given UploadOptions instances into a single UploadOptions in a last-one-wins fashion.
func MergeUploadOptions(opts ...*UploadOptions) *UploadOptions {
u := GridFSUpload()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.ChunkSizeBytes != nil {
u.ChunkSizeBytes = opt.ChunkSizeBytes
}
if opt.Metadata != nil {
u.Metadata = opt.Metadata
}
if opt.Registry != nil {
u.Registry = opt.Registry
}
}
return u
}
// NameOptions represents options that can be used to configure a GridFS DownloadByName operation.
type NameOptions struct {
// 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
Revision *int32
}
// GridFSName creates a new NameOptions instance.
func GridFSName() *NameOptions {
return &NameOptions{}
}
// SetRevision sets the value for the Revision field.
func (n *NameOptions) SetRevision(r int32) *NameOptions {
n.Revision = &r
return n
}
// MergeNameOptions combines the given NameOptions instances into a single *NameOptions in a last-one-wins fashion.
func MergeNameOptions(opts ...*NameOptions) *NameOptions {
n := GridFSName()
n.Revision = &DefaultRevision
for _, opt := range opts {
if opt == nil {
continue
}
if opt.Revision != nil {
n.Revision = opt.Revision
}
}
return n
}
// GridFSFindOptions represents options that can be used to configure a GridFS Find operation.
type GridFSFindOptions struct {
// 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.
AllowDiskUse *bool
// The maximum number of documents to be included in each batch returned by the server.
BatchSize *int32
// 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.
Limit *int32
// The maximum amount of time that the query can run on the server. The default value is nil, meaning that there
// is no time limit for query execution.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout option may be used
// in its place to control the amount of time that a single operation can run before returning an error. MaxTime
// is ignored if Timeout is set on the client.
MaxTime *time.Duration
// If true, the cursor created by the operation will not timeout after a period of inactivity. The default value
// is false.
NoCursorTimeout *bool
// The number of documents to skip before adding documents to the result. The default value is 0.
Skip *int32
// A document specifying the order in which documents should be returned. The driver will return an error if the
// sort parameter is a multi-key map.
Sort interface{}
}
// GridFSFind creates a new GridFSFindOptions instance.
func GridFSFind() *GridFSFindOptions {
return &GridFSFindOptions{}
}
// SetAllowDiskUse sets the value for the AllowDiskUse field.
func (f *GridFSFindOptions) SetAllowDiskUse(b bool) *GridFSFindOptions {
f.AllowDiskUse = &b
return f
}
// SetBatchSize sets the value for the BatchSize field.
func (f *GridFSFindOptions) SetBatchSize(i int32) *GridFSFindOptions {
f.BatchSize = &i
return f
}
// SetLimit sets the value for the Limit field.
func (f *GridFSFindOptions) SetLimit(i int32) *GridFSFindOptions {
f.Limit = &i
return f
}
// SetMaxTime sets the value for the MaxTime field.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can
// run before returning an error. MaxTime is ignored if Timeout is set on the client.
func (f *GridFSFindOptions) SetMaxTime(d time.Duration) *GridFSFindOptions {
f.MaxTime = &d
return f
}
// SetNoCursorTimeout sets the value for the NoCursorTimeout field.
func (f *GridFSFindOptions) SetNoCursorTimeout(b bool) *GridFSFindOptions {
f.NoCursorTimeout = &b
return f
}
// SetSkip sets the value for the Skip field.
func (f *GridFSFindOptions) SetSkip(i int32) *GridFSFindOptions {
f.Skip = &i
return f
}
// SetSort sets the value for the Sort field.
func (f *GridFSFindOptions) SetSort(sort interface{}) *GridFSFindOptions {
f.Sort = sort
return f
}
// MergeGridFSFindOptions combines the given GridFSFindOptions instances into a single GridFSFindOptions in a
// last-one-wins fashion.
func MergeGridFSFindOptions(opts ...*GridFSFindOptions) *GridFSFindOptions {
fo := GridFSFind()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.AllowDiskUse != nil {
fo.AllowDiskUse = opt.AllowDiskUse
}
if opt.BatchSize != nil {
fo.BatchSize = opt.BatchSize
}
if opt.Limit != nil {
fo.Limit = opt.Limit
}
if opt.MaxTime != nil {
fo.MaxTime = opt.MaxTime
}
if opt.NoCursorTimeout != nil {
fo.NoCursorTimeout = opt.NoCursorTimeout
}
if opt.Skip != nil {
fo.Skip = opt.Skip
}
if opt.Sort != nil {
fo.Sort = opt.Sort
}
}
return fo
}
+482
View File
@@ -0,0 +1,482 @@
// 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"
)
// CreateIndexesOptions represents options that can be used to configure IndexView.CreateOne and IndexView.CreateMany
// operations.
type CreateIndexesOptions struct {
// 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. This should either be a string or int32 value. The
// semantics of the values are as follows:
//
// 1. String: specifies a tag. All members with that tag must complete the build.
// 2. int: the number of members that must complete the build.
// 3. "majority": A special value to indicate that more than half the nodes must complete the build.
// 4. "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.
CommitQuorum interface{}
// The maximum amount of time that the query can run on the server. The default value is nil, meaning that there
// is no time limit for query execution.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout option may be used
// in its place to control the amount of time that a single operation can run before returning an error. MaxTime
// is ignored if Timeout is set on the client.
MaxTime *time.Duration
}
// CreateIndexes creates a new CreateIndexesOptions instance.
func CreateIndexes() *CreateIndexesOptions {
return &CreateIndexesOptions{}
}
// SetMaxTime sets the value for the MaxTime field.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can
// run before returning an error. MaxTime is ignored if Timeout is set on the client.
func (c *CreateIndexesOptions) SetMaxTime(d time.Duration) *CreateIndexesOptions {
c.MaxTime = &d
return c
}
// SetCommitQuorumInt sets the value for the CommitQuorum field as an int32.
func (c *CreateIndexesOptions) SetCommitQuorumInt(quorum int32) *CreateIndexesOptions {
c.CommitQuorum = quorum
return c
}
// SetCommitQuorumString sets the value for the CommitQuorum field as a string.
func (c *CreateIndexesOptions) SetCommitQuorumString(quorum string) *CreateIndexesOptions {
c.CommitQuorum = quorum
return c
}
// SetCommitQuorumMajority sets the value for the CommitQuorum to special "majority" value.
func (c *CreateIndexesOptions) SetCommitQuorumMajority() *CreateIndexesOptions {
c.CommitQuorum = "majority"
return c
}
// SetCommitQuorumVotingMembers sets the value for the CommitQuorum to special "votingMembers" value.
func (c *CreateIndexesOptions) SetCommitQuorumVotingMembers() *CreateIndexesOptions {
c.CommitQuorum = "votingMembers"
return c
}
// MergeCreateIndexesOptions combines the given CreateIndexesOptions into a single CreateIndexesOptions in a last one
// wins fashion.
func MergeCreateIndexesOptions(opts ...*CreateIndexesOptions) *CreateIndexesOptions {
c := CreateIndexes()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.MaxTime != nil {
c.MaxTime = opt.MaxTime
}
if opt.CommitQuorum != nil {
c.CommitQuorum = opt.CommitQuorum
}
}
return c
}
// DropIndexesOptions represents options that can be used to configure IndexView.DropOne and IndexView.DropAll
// operations.
type DropIndexesOptions struct {
// The maximum amount of time that the query can run on the server. The default value is nil, meaning that there
// is no time limit for query execution.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout option may be used
// in its place to control the amount of time that a single operation can run before returning an error. MaxTime
// is ignored if Timeout is set on the client.
MaxTime *time.Duration
}
// DropIndexes creates a new DropIndexesOptions instance.
func DropIndexes() *DropIndexesOptions {
return &DropIndexesOptions{}
}
// SetMaxTime sets the value for the MaxTime field.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can
// run before returning an error. MaxTime is ignored if Timeout is set on the client.
func (d *DropIndexesOptions) SetMaxTime(duration time.Duration) *DropIndexesOptions {
d.MaxTime = &duration
return d
}
// MergeDropIndexesOptions combines the given DropIndexesOptions into a single DropIndexesOptions in a last-one-wins
// fashion.
func MergeDropIndexesOptions(opts ...*DropIndexesOptions) *DropIndexesOptions {
c := DropIndexes()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.MaxTime != nil {
c.MaxTime = opt.MaxTime
}
}
return c
}
// ListIndexesOptions represents options that can be used to configure an IndexView.List operation.
type ListIndexesOptions struct {
// The maximum number of documents to be included in each batch returned by the server.
BatchSize *int32
// The maximum amount of time that the query can run on the server. The default value is nil, meaning that there
// is no time limit for query execution.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout option may be used
// in its place to control the amount of time that a single operation can run before returning an error. MaxTime
// is ignored if Timeout is set on the client.
MaxTime *time.Duration
}
// ListIndexes creates a new ListIndexesOptions instance.
func ListIndexes() *ListIndexesOptions {
return &ListIndexesOptions{}
}
// SetBatchSize sets the value for the BatchSize field.
func (l *ListIndexesOptions) SetBatchSize(i int32) *ListIndexesOptions {
l.BatchSize = &i
return l
}
// SetMaxTime sets the value for the MaxTime field.
//
// NOTE(benjirewis): MaxTime will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can
// run before returning an error. MaxTime is ignored if Timeout is set on the client.
func (l *ListIndexesOptions) SetMaxTime(d time.Duration) *ListIndexesOptions {
l.MaxTime = &d
return l
}
// MergeListIndexesOptions combines the given ListIndexesOptions instances into a single *ListIndexesOptions in a
// last-one-wins fashion.
func MergeListIndexesOptions(opts ...*ListIndexesOptions) *ListIndexesOptions {
c := ListIndexes()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.BatchSize != nil {
c.BatchSize = opt.BatchSize
}
if opt.MaxTime != nil {
c.MaxTime = opt.MaxTime
}
}
return c
}
// IndexOptions represents options that can be used to configure a new index created through the IndexView.CreateOne
// or IndexView.CreateMany operations.
type IndexOptions struct {
// If true, the index will be built in the background on the server and will not block other tasks. The default
// value is false.
//
// Deprecated: This option has been deprecated in MongoDB version 4.2.
Background *bool
// 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
// 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".
Name *string
// If true, the index will only reference documents that contain the fields specified in the index. The default is
// false.
Sparse *bool
// 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. This option is only applicable for MongoDB versions >= 3.0 and is ignored for previous server
// versions.
StorageEngine interface{}
// 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 index version number, either 0 or 1.
Version *int32
// 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".
DefaultLanguage *string
// 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.
LanguageOverride *string
// 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.
TextVersion *int32
// 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.
Weights interface{}
// 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.
SphereVersion *int32
// 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.
Bits *int32
// 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.
Max *float64
// 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.
Min *float64
// 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.
BucketSize *int32
// A document that defines which collection documents the index should reference. This option is only valid for
// MongoDB versions >= 3.2 and is ignored for previous server versions.
PartialFilterExpression interface{}
// The collation to use for string comparisons for the index. This option is only valid for MongoDB versions >= 3.4.
// For previous server versions, the driver will return an error if this option is used.
Collation *Collation
// A document that defines the wildcard projection for the index.
WildcardProjection interface{}
// 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.
Hidden *bool
}
// Index creates a new IndexOptions instance.
func Index() *IndexOptions {
return &IndexOptions{}
}
// SetBackground sets value for the Background field.
//
// Deprecated: This option has been deprecated in MongoDB version 4.2.
func (i *IndexOptions) SetBackground(background bool) *IndexOptions {
i.Background = &background
return i
}
// SetExpireAfterSeconds sets value for the ExpireAfterSeconds field.
func (i *IndexOptions) SetExpireAfterSeconds(seconds int32) *IndexOptions {
i.ExpireAfterSeconds = &seconds
return i
}
// SetName sets the value for the Name field.
func (i *IndexOptions) SetName(name string) *IndexOptions {
i.Name = &name
return i
}
// SetSparse sets the value of the Sparse field.
func (i *IndexOptions) SetSparse(sparse bool) *IndexOptions {
i.Sparse = &sparse
return i
}
// SetStorageEngine sets the value for the StorageEngine field.
func (i *IndexOptions) SetStorageEngine(engine interface{}) *IndexOptions {
i.StorageEngine = engine
return i
}
// SetUnique sets the value for the Unique field.
func (i *IndexOptions) SetUnique(unique bool) *IndexOptions {
i.Unique = &unique
return i
}
// SetVersion sets the value for the Version field.
func (i *IndexOptions) SetVersion(version int32) *IndexOptions {
i.Version = &version
return i
}
// SetDefaultLanguage sets the value for the DefaultLanguage field.
func (i *IndexOptions) SetDefaultLanguage(language string) *IndexOptions {
i.DefaultLanguage = &language
return i
}
// SetLanguageOverride sets the value of the LanguageOverride field.
func (i *IndexOptions) SetLanguageOverride(override string) *IndexOptions {
i.LanguageOverride = &override
return i
}
// SetTextVersion sets the value for the TextVersion field.
func (i *IndexOptions) SetTextVersion(version int32) *IndexOptions {
i.TextVersion = &version
return i
}
// SetWeights sets the value for the Weights field.
func (i *IndexOptions) SetWeights(weights interface{}) *IndexOptions {
i.Weights = weights
return i
}
// SetSphereVersion sets the value for the SphereVersion field.
func (i *IndexOptions) SetSphereVersion(version int32) *IndexOptions {
i.SphereVersion = &version
return i
}
// SetBits sets the value for the Bits field.
func (i *IndexOptions) SetBits(bits int32) *IndexOptions {
i.Bits = &bits
return i
}
// SetMax sets the value for the Max field.
func (i *IndexOptions) SetMax(max float64) *IndexOptions {
i.Max = &max
return i
}
// SetMin sets the value for the Min field.
func (i *IndexOptions) SetMin(min float64) *IndexOptions {
i.Min = &min
return i
}
// SetBucketSize sets the value for the BucketSize field
func (i *IndexOptions) SetBucketSize(bucketSize int32) *IndexOptions {
i.BucketSize = &bucketSize
return i
}
// SetPartialFilterExpression sets the value for the PartialFilterExpression field.
func (i *IndexOptions) SetPartialFilterExpression(expression interface{}) *IndexOptions {
i.PartialFilterExpression = expression
return i
}
// SetCollation sets the value for the Collation field.
func (i *IndexOptions) SetCollation(collation *Collation) *IndexOptions {
i.Collation = collation
return i
}
// SetWildcardProjection sets the value for the WildcardProjection field.
func (i *IndexOptions) SetWildcardProjection(wildcardProjection interface{}) *IndexOptions {
i.WildcardProjection = wildcardProjection
return i
}
// SetHidden sets the value for the Hidden field.
func (i *IndexOptions) SetHidden(hidden bool) *IndexOptions {
i.Hidden = &hidden
return i
}
// MergeIndexOptions combines the given IndexOptions into a single IndexOptions in a last-one-wins fashion.
func MergeIndexOptions(opts ...*IndexOptions) *IndexOptions {
i := Index()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.Background != nil {
i.Background = opt.Background
}
if opt.ExpireAfterSeconds != nil {
i.ExpireAfterSeconds = opt.ExpireAfterSeconds
}
if opt.Name != nil {
i.Name = opt.Name
}
if opt.Sparse != nil {
i.Sparse = opt.Sparse
}
if opt.StorageEngine != nil {
i.StorageEngine = opt.StorageEngine
}
if opt.Unique != nil {
i.Unique = opt.Unique
}
if opt.Version != nil {
i.Version = opt.Version
}
if opt.DefaultLanguage != nil {
i.DefaultLanguage = opt.DefaultLanguage
}
if opt.LanguageOverride != nil {
i.LanguageOverride = opt.LanguageOverride
}
if opt.TextVersion != nil {
i.TextVersion = opt.TextVersion
}
if opt.Weights != nil {
i.Weights = opt.Weights
}
if opt.SphereVersion != nil {
i.SphereVersion = opt.SphereVersion
}
if opt.Bits != nil {
i.Bits = opt.Bits
}
if opt.Max != nil {
i.Max = opt.Max
}
if opt.Min != nil {
i.Min = opt.Min
}
if opt.BucketSize != nil {
i.BucketSize = opt.BucketSize
}
if opt.PartialFilterExpression != nil {
i.PartialFilterExpression = opt.PartialFilterExpression
}
if opt.Collation != nil {
i.Collation = opt.Collation
}
if opt.WildcardProjection != nil {
i.WildcardProjection = opt.WildcardProjection
}
if opt.Hidden != nil {
i.Hidden = opt.Hidden
}
}
return i
}
+119
View File
@@ -0,0 +1,119 @@
// 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
// InsertOneOptions represents options that can be used to configure an InsertOne operation.
type InsertOneOptions struct {
// If true, writes executed as part of the operation will opt out of document-level validation on the server. This
// option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is
// false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document
// validation.
BypassDocumentValidation *bool
// 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.
Comment interface{}
}
// InsertOne creates a new InsertOneOptions instance.
func InsertOne() *InsertOneOptions {
return &InsertOneOptions{}
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field.
func (ioo *InsertOneOptions) SetBypassDocumentValidation(b bool) *InsertOneOptions {
ioo.BypassDocumentValidation = &b
return ioo
}
// SetComment sets the value for the Comment field.
func (ioo *InsertOneOptions) SetComment(comment interface{}) *InsertOneOptions {
ioo.Comment = comment
return ioo
}
// MergeInsertOneOptions combines the given InsertOneOptions instances into a single InsertOneOptions in a last-one-wins
// fashion.
func MergeInsertOneOptions(opts ...*InsertOneOptions) *InsertOneOptions {
ioOpts := InsertOne()
for _, ioo := range opts {
if ioo == nil {
continue
}
if ioo.BypassDocumentValidation != nil {
ioOpts.BypassDocumentValidation = ioo.BypassDocumentValidation
}
if ioo.Comment != nil {
ioOpts.Comment = ioo.Comment
}
}
return ioOpts
}
// InsertManyOptions represents options that can be used to configure an InsertMany operation.
type InsertManyOptions struct {
// If true, writes executed as part of the operation will opt out of document-level validation on the server. This
// option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is
// false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document
// validation.
BypassDocumentValidation *bool
// 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.
Comment interface{}
// If true, no writes will be executed after one fails. The default value is true.
Ordered *bool
}
// InsertMany creates a new InsertManyOptions instance.
func InsertMany() *InsertManyOptions {
return &InsertManyOptions{
Ordered: &DefaultOrdered,
}
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field.
func (imo *InsertManyOptions) SetBypassDocumentValidation(b bool) *InsertManyOptions {
imo.BypassDocumentValidation = &b
return imo
}
// SetComment sets the value for the Comment field.
func (imo *InsertManyOptions) SetComment(comment interface{}) *InsertManyOptions {
imo.Comment = comment
return imo
}
// SetOrdered sets the value for the Ordered field.
func (imo *InsertManyOptions) SetOrdered(b bool) *InsertManyOptions {
imo.Ordered = &b
return imo
}
// MergeInsertManyOptions combines the given InsertManyOptions instances into a single InsertManyOptions in a last one
// wins fashion.
func MergeInsertManyOptions(opts ...*InsertManyOptions) *InsertManyOptions {
imOpts := InsertMany()
for _, imo := range opts {
if imo == nil {
continue
}
if imo.BypassDocumentValidation != nil {
imOpts.BypassDocumentValidation = imo.BypassDocumentValidation
}
if imo.Comment != nil {
imOpts.Comment = imo.Comment
}
if imo.Ordered != nil {
imOpts.Ordered = imo.Ordered
}
}
return imOpts
}
@@ -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
// ListCollectionsOptions represents options that can be used to configure a ListCollections operation.
type ListCollectionsOptions struct {
// If true, each collection document will only contain a field for the collection name. The default value is false.
NameOnly *bool
// The maximum number of documents to be included in each batch returned by the server.
BatchSize *int32
// 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. This option is only valid for MongoDB server versions >= 4.0. Server versions < 4.0 ignore this option.
AuthorizedCollections *bool
}
// ListCollections creates a new ListCollectionsOptions instance.
func ListCollections() *ListCollectionsOptions {
return &ListCollectionsOptions{}
}
// SetNameOnly sets the value for the NameOnly field.
func (lc *ListCollectionsOptions) SetNameOnly(b bool) *ListCollectionsOptions {
lc.NameOnly = &b
return lc
}
// SetBatchSize sets the value for the BatchSize field.
func (lc *ListCollectionsOptions) SetBatchSize(size int32) *ListCollectionsOptions {
lc.BatchSize = &size
return lc
}
// SetAuthorizedCollections sets the value for the AuthorizedCollections field. This option is only valid for MongoDB server versions >= 4.0. Server
// versions < 4.0 ignore this option.
func (lc *ListCollectionsOptions) SetAuthorizedCollections(b bool) *ListCollectionsOptions {
lc.AuthorizedCollections = &b
return lc
}
// MergeListCollectionsOptions combines the given ListCollectionsOptions instances into a single *ListCollectionsOptions
// in a last-one-wins fashion.
func MergeListCollectionsOptions(opts ...*ListCollectionsOptions) *ListCollectionsOptions {
lc := ListCollections()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.NameOnly != nil {
lc.NameOnly = opt.NameOnly
}
if opt.BatchSize != nil {
lc.BatchSize = opt.BatchSize
}
if opt.AuthorizedCollections != nil {
lc.AuthorizedCollections = opt.AuthorizedCollections
}
}
return lc
}
@@ -0,0 +1,55 @@
// 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 options that can be used to configure a ListDatabases operation.
type ListDatabasesOptions struct {
// If true, only the Name field of the returned DatabaseSpecification objects will be populated. The default value
// is false.
NameOnly *bool
// 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.
AuthorizedDatabases *bool
}
// ListDatabases creates a new ListDatabasesOptions instance.
func ListDatabases() *ListDatabasesOptions {
return &ListDatabasesOptions{}
}
// SetNameOnly sets the value for the NameOnly field.
func (ld *ListDatabasesOptions) SetNameOnly(b bool) *ListDatabasesOptions {
ld.NameOnly = &b
return ld
}
// SetAuthorizedDatabases sets the value for the AuthorizedDatabases field.
func (ld *ListDatabasesOptions) SetAuthorizedDatabases(b bool) *ListDatabasesOptions {
ld.AuthorizedDatabases = &b
return ld
}
// MergeListDatabasesOptions combines the given ListDatabasesOptions instances into a single *ListDatabasesOptions in a
// last-one-wins fashion.
func MergeListDatabasesOptions(opts ...*ListDatabasesOptions) *ListDatabasesOptions {
ld := ListDatabases()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.NameOnly != nil {
ld.NameOnly = opt.NameOnly
}
if opt.AuthorizedDatabases != nil {
ld.AuthorizedDatabases = opt.AuthorizedDatabases
}
}
return ld
}
+165
View File
@@ -0,0 +1,165 @@
// 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"
"reflect"
"strconv"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
// 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
}
// ToDocument converts the Collation to a bson.Raw.
func (co *Collation) ToDocument() bson.Raw {
idx, doc := bsoncore.AppendDocumentStart(nil)
if co.Locale != "" {
doc = bsoncore.AppendStringElement(doc, "locale", co.Locale)
}
if co.CaseLevel {
doc = bsoncore.AppendBooleanElement(doc, "caseLevel", true)
}
if co.CaseFirst != "" {
doc = bsoncore.AppendStringElement(doc, "caseFirst", co.CaseFirst)
}
if co.Strength != 0 {
doc = bsoncore.AppendInt32Element(doc, "strength", int32(co.Strength))
}
if co.NumericOrdering {
doc = bsoncore.AppendBooleanElement(doc, "numericOrdering", true)
}
if co.Alternate != "" {
doc = bsoncore.AppendStringElement(doc, "alternate", co.Alternate)
}
if co.MaxVariable != "" {
doc = bsoncore.AppendStringElement(doc, "maxVariable", co.MaxVariable)
}
if co.Normalization {
doc = bsoncore.AppendBooleanElement(doc, "normalization", true)
}
if co.Backwards {
doc = bsoncore.AppendBooleanElement(doc, "backwards", true)
}
doc, _ = bsoncore.AppendDocumentEnd(doc, idx)
return doc
}
// 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 the modified document for replace and update change events
// if the post-image for this event is available.
WhenAvailable FullDocument = "whenAvailable"
)
// ArrayFilters is used to hold filters for the array filters CRUD option. If a registry is nil, bson.DefaultRegistry
// will be used when converting the filter interfaces to BSON.
type ArrayFilters struct {
Registry *bsoncodec.Registry // The registry to use for converting filters. Defaults to bson.DefaultRegistry.
Filters []interface{} // The filters to apply
}
// ToArray builds a []bson.Raw from the provided ArrayFilters.
func (af *ArrayFilters) ToArray() ([]bson.Raw, error) {
registry := af.Registry
if registry == nil {
registry = bson.DefaultRegistry
}
filters := make([]bson.Raw, 0, len(af.Filters))
for _, f := range af.Filters {
filter, err := bson.MarshalWithRegistry(registry, f)
if err != nil {
return nil, err
}
filters = append(filters, filter)
}
return filters, nil
}
// ToArrayDocument builds a BSON array for the array filters CRUD option. If the registry for af is nil,
// bson.DefaultRegistry will be used when converting the filter interfaces to BSON.
func (af *ArrayFilters) ToArrayDocument() (bson.Raw, error) {
registry := af.Registry
if registry == nil {
registry = bson.DefaultRegistry
}
idx, arr := bsoncore.AppendArrayStart(nil)
for i, f := range af.Filters {
filter, err := bson.MarshalWithRegistry(registry, f)
if err != nil {
return nil, err
}
arr = bsoncore.AppendDocumentElement(arr, strconv.Itoa(i), filter)
}
arr, _ = bsoncore.AppendArrayEnd(arr, idx)
return arr, nil
}
// MarshalError is returned when attempting to transform a value into a document
// results in an error.
type MarshalError struct {
Value interface{}
Err error
}
// Error implements the error interface.
func (me MarshalError) Error() string {
return fmt.Sprintf("cannot transform type %s to a bson.Raw", reflect.TypeOf(me.Value))
}
+115
View File
@@ -0,0 +1,115 @@
// 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
// ReplaceOptions represents options that can be used to configure a ReplaceOne operation.
type ReplaceOptions struct {
// If true, writes executed as part of the operation will opt out of document-level validation on the server. This
// option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is
// false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document
// validation.
BypassDocumentValidation *bool
// Specifies a collation to use for string comparisons during the operation. This option is only valid for MongoDB
// versions >= 3.4. For previous server versions, the driver will return an error if this option is used. The
// default value is nil, which means the default collation of the collection will be used.
Collation *Collation
// 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.
Comment interface{}
// 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 >= 3.4 will return an error
// if this option is specified. For server versions < 3.4, the driver will return a client-side 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.
Hint interface{}
// If true, a new document will be inserted if the filter does not match any documents in the collection. The
// default value is false.
Upsert *bool
// 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").
Let interface{}
}
// Replace creates a new ReplaceOptions instance.
func Replace() *ReplaceOptions {
return &ReplaceOptions{}
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field.
func (ro *ReplaceOptions) SetBypassDocumentValidation(b bool) *ReplaceOptions {
ro.BypassDocumentValidation = &b
return ro
}
// SetCollation sets the value for the Collation field.
func (ro *ReplaceOptions) SetCollation(c *Collation) *ReplaceOptions {
ro.Collation = c
return ro
}
// SetComment sets the value for the Comment field.
func (ro *ReplaceOptions) SetComment(comment interface{}) *ReplaceOptions {
ro.Comment = comment
return ro
}
// SetHint sets the value for the Hint field.
func (ro *ReplaceOptions) SetHint(h interface{}) *ReplaceOptions {
ro.Hint = h
return ro
}
// SetUpsert sets the value for the Upsert field.
func (ro *ReplaceOptions) SetUpsert(b bool) *ReplaceOptions {
ro.Upsert = &b
return ro
}
// SetLet sets the value for the Let field.
func (ro *ReplaceOptions) SetLet(l interface{}) *ReplaceOptions {
ro.Let = l
return ro
}
// MergeReplaceOptions combines the given ReplaceOptions instances into a single ReplaceOptions in a last-one-wins
// fashion.
func MergeReplaceOptions(opts ...*ReplaceOptions) *ReplaceOptions {
rOpts := Replace()
for _, ro := range opts {
if ro == nil {
continue
}
if ro.BypassDocumentValidation != nil {
rOpts.BypassDocumentValidation = ro.BypassDocumentValidation
}
if ro.Collation != nil {
rOpts.Collation = ro.Collation
}
if ro.Comment != nil {
rOpts.Comment = ro.Comment
}
if ro.Hint != nil {
rOpts.Hint = ro.Hint
}
if ro.Upsert != nil {
rOpts.Upsert = ro.Upsert
}
if ro.Let != nil {
rOpts.Let = ro.Let
}
}
return rOpts
}
@@ -0,0 +1,52 @@
// 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.
type RewrapManyDataKeyOptions struct {
// Provider identifies the new KMS provider. If omitted, encrypting uses the current KMS provider.
Provider *string
// MasterKey identifies the new masterKey. If omitted, rewraps with the current masterKey.
MasterKey interface{}
}
// RewrapManyDataKey creates a new RewrapManyDataKeyOptions instance.
func RewrapManyDataKey() *RewrapManyDataKeyOptions {
return new(RewrapManyDataKeyOptions)
}
// SetProvider sets the value for the Provider field.
func (rmdko *RewrapManyDataKeyOptions) SetProvider(provider string) *RewrapManyDataKeyOptions {
rmdko.Provider = &provider
return rmdko
}
// SetMasterKey sets the value for the MasterKey field.
func (rmdko *RewrapManyDataKeyOptions) SetMasterKey(masterKey interface{}) *RewrapManyDataKeyOptions {
rmdko.MasterKey = masterKey
return rmdko
}
// MergeRewrapManyDataKeyOptions combines the given RewrapManyDataKeyOptions instances into a single
// RewrapManyDataKeyOptions in a last one wins fashion.
func MergeRewrapManyDataKeyOptions(opts ...*RewrapManyDataKeyOptions) *RewrapManyDataKeyOptions {
rmdkOpts := RewrapManyDataKey()
for _, rmdko := range opts {
if rmdko == nil {
continue
}
if provider := rmdko.Provider; provider != nil {
rmdkOpts.Provider = provider
}
if masterKey := rmdko.MasterKey; masterKey != nil {
rmdkOpts.MasterKey = masterKey
}
}
return rmdkOpts
}
+42
View File
@@ -0,0 +1,42 @@
// 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/mongo/readpref"
// RunCmdOptions represents options that can be used to configure a RunCommand operation.
type RunCmdOptions struct {
// The read preference to use for the operation. The default value is nil, which means that the primary read
// preference will be used.
ReadPreference *readpref.ReadPref
}
// RunCmd creates a new RunCmdOptions instance.
func RunCmd() *RunCmdOptions {
return &RunCmdOptions{}
}
// SetReadPreference sets value for the ReadPreference field.
func (rc *RunCmdOptions) SetReadPreference(rp *readpref.ReadPref) *RunCmdOptions {
rc.ReadPreference = rp
return rc
}
// MergeRunCmdOptions combines the given RunCmdOptions instances into one *RunCmdOptions in a last-one-wins fashion.
func MergeRunCmdOptions(opts ...*RunCmdOptions) *RunCmdOptions {
rc := RunCmd()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.ReadPreference != nil {
rc.ReadPreference = opt.ReadPreference
}
}
return rc
}
+60
View File
@@ -0,0 +1,60 @@
// 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 options 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".
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 {
switch sav {
case ServerAPIVersion1:
return nil
}
return fmt.Errorf("api version %q not supported; this driver version only supports API version \"1\"", sav)
}
+131
View File
@@ -0,0 +1,131 @@
// 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/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
)
// DefaultCausalConsistency is the default value for the CausalConsistency option.
var DefaultCausalConsistency = true
// SessionOptions represents options that can be used to configure a Session.
type SessionOptions struct {
// 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.
CausalConsistency *bool
// The default read concern for transactions started in the session. The default value is nil, which means that
// the read concern of the client used to start the session will be used.
DefaultReadConcern *readconcern.ReadConcern
// The default read preference for transactions started in the session. The default value is nil, which means that
// the read preference of the client used to start the session will be used.
DefaultReadPreference *readpref.ReadPref
// The default write concern for transactions started in the session. The default value is nil, which means that
// the write concern of the client used to start the session will be used.
DefaultWriteConcern *writeconcern.WriteConcern
// The default maximum amount of time that a CommitTransaction operation executed in the session can run on the
// server. The default value is nil, which means that that there is no time limit for execution.
//
// NOTE(benjirewis): DefaultMaxCommitTime will be deprecated in a future release. The more general Timeout option
// may be used in its place to control the amount of time that a single operation can run before returning an
// error. DefaultMaxCommitTime is ignored if Timeout is set on the client.
DefaultMaxCommitTime *time.Duration
// 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.
Snapshot *bool
}
// Session creates a new SessionOptions instance.
func Session() *SessionOptions {
return &SessionOptions{}
}
// SetCausalConsistency sets the value for the CausalConsistency field.
func (s *SessionOptions) SetCausalConsistency(b bool) *SessionOptions {
s.CausalConsistency = &b
return s
}
// SetDefaultReadConcern sets the value for the DefaultReadConcern field.
func (s *SessionOptions) SetDefaultReadConcern(rc *readconcern.ReadConcern) *SessionOptions {
s.DefaultReadConcern = rc
return s
}
// SetDefaultReadPreference sets the value for the DefaultReadPreference field.
func (s *SessionOptions) SetDefaultReadPreference(rp *readpref.ReadPref) *SessionOptions {
s.DefaultReadPreference = rp
return s
}
// SetDefaultWriteConcern sets the value for the DefaultWriteConcern field.
func (s *SessionOptions) SetDefaultWriteConcern(wc *writeconcern.WriteConcern) *SessionOptions {
s.DefaultWriteConcern = wc
return s
}
// SetDefaultMaxCommitTime sets the value for the DefaultMaxCommitTime field.
//
// NOTE(benjirewis): DefaultMaxCommitTime will be deprecated in a future release. The more
// general Timeout option may be used in its place to control the amount of time that a
// single operation can run before returning an error. DefaultMaxCommitTime is ignored if
// Timeout is set on the client.
func (s *SessionOptions) SetDefaultMaxCommitTime(mct *time.Duration) *SessionOptions {
s.DefaultMaxCommitTime = mct
return s
}
// SetSnapshot sets the value for the Snapshot field.
func (s *SessionOptions) SetSnapshot(b bool) *SessionOptions {
s.Snapshot = &b
return s
}
// MergeSessionOptions combines the given SessionOptions instances into a single SessionOptions in a last-one-wins
// fashion.
func MergeSessionOptions(opts ...*SessionOptions) *SessionOptions {
s := Session()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.CausalConsistency != nil {
s.CausalConsistency = opt.CausalConsistency
}
if opt.DefaultReadConcern != nil {
s.DefaultReadConcern = opt.DefaultReadConcern
}
if opt.DefaultReadPreference != nil {
s.DefaultReadPreference = opt.DefaultReadPreference
}
if opt.DefaultWriteConcern != nil {
s.DefaultWriteConcern = opt.DefaultWriteConcern
}
if opt.DefaultMaxCommitTime != nil {
s.DefaultMaxCommitTime = opt.DefaultMaxCommitTime
}
if opt.Snapshot != nil {
s.Snapshot = opt.Snapshot
}
}
if s.CausalConsistency == nil && (s.Snapshot == nil || !*s.Snapshot) {
s.CausalConsistency = &DefaultCausalConsistency
}
return s
}
+100
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 (
"time"
"go.mongodb.org/mongo-driver/mongo/readconcern"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/mongo/writeconcern"
)
// TransactionOptions represents options that can be used to configure a transaction.
type TransactionOptions struct {
// 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.
ReadConcern *readconcern.ReadConcern
// 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.
ReadPreference *readpref.ReadPref
// 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.
WriteConcern *writeconcern.WriteConcern
// The default maximum amount of time that a CommitTransaction operation executed in the session can run on the
// server. The default value is nil, meaning that there is no time limit for execution.
// The maximum amount of time that a CommitTransaction operation can executed in the transaction can run on the
// server. The default value is nil, which means that the default maximum commit time of the session used to
// start the transaction will be used.
//
// NOTE(benjirewis): MaxCommitTime will be deprecated in a future release. The more general Timeout option may
// be used in its place to control the amount of time that a single operation can run before returning an error.
// MaxCommitTime is ignored if Timeout is set on the client.
MaxCommitTime *time.Duration
}
// Transaction creates a new TransactionOptions instance.
func Transaction() *TransactionOptions {
return &TransactionOptions{}
}
// SetReadConcern sets the value for the ReadConcern field.
func (t *TransactionOptions) SetReadConcern(rc *readconcern.ReadConcern) *TransactionOptions {
t.ReadConcern = rc
return t
}
// SetReadPreference sets the value for the ReadPreference field.
func (t *TransactionOptions) SetReadPreference(rp *readpref.ReadPref) *TransactionOptions {
t.ReadPreference = rp
return t
}
// SetWriteConcern sets the value for the WriteConcern field.
func (t *TransactionOptions) SetWriteConcern(wc *writeconcern.WriteConcern) *TransactionOptions {
t.WriteConcern = wc
return t
}
// SetMaxCommitTime sets the value for the MaxCommitTime field.
//
// NOTE(benjirewis): MaxCommitTime will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can run before
// returning an error. MaxCommitTime is ignored if Timeout is set on the client.
func (t *TransactionOptions) SetMaxCommitTime(mct *time.Duration) *TransactionOptions {
t.MaxCommitTime = mct
return t
}
// MergeTransactionOptions combines the given TransactionOptions instances into a single TransactionOptions in a
// last-one-wins fashion.
func MergeTransactionOptions(opts ...*TransactionOptions) *TransactionOptions {
t := Transaction()
for _, opt := range opts {
if opt == nil {
continue
}
if opt.ReadConcern != nil {
t.ReadConcern = opt.ReadConcern
}
if opt.ReadPreference != nil {
t.ReadPreference = opt.ReadPreference
}
if opt.WriteConcern != nil {
t.WriteConcern = opt.WriteConcern
}
if opt.MaxCommitTime != nil {
t.MaxCommitTime = opt.MaxCommitTime
}
}
return t
}
+128
View File
@@ -0,0 +1,128 @@
// 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
// UpdateOptions represents options that can be used to configure UpdateOne and UpdateMany operations.
type UpdateOptions struct {
// A set of filters specifying to which array elements an update should apply. This option is only valid for MongoDB
// versions >= 3.6. For previous server versions, the driver will return an error if this option is used. The
// default value is nil, which means the update will apply to all array elements.
ArrayFilters *ArrayFilters
// If true, writes executed as part of the operation will opt out of document-level validation on the server. This
// option is valid for MongoDB versions >= 3.2 and is ignored for previous server versions. The default value is
// false. See https://www.mongodb.com/docs/manual/core/schema-validation/ for more information about document
// validation.
BypassDocumentValidation *bool
// Specifies a collation to use for string comparisons during the operation. This option is only valid for MongoDB
// versions >= 3.4. For previous server versions, the driver will return an error if this option is used. The
// default value is nil, which means the default collation of the collection will be used.
Collation *Collation
// 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.
Comment interface{}
// 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 >= 3.4 will return an error
// if this option is specified. For server versions < 3.4, the driver will return a client-side 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.
Hint interface{}
// If true, a new document will be inserted if the filter does not match any documents in the collection. The
// default value is false.
Upsert *bool
// 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").
Let interface{}
}
// Update creates a new UpdateOptions instance.
func Update() *UpdateOptions {
return &UpdateOptions{}
}
// SetArrayFilters sets the value for the ArrayFilters field.
func (uo *UpdateOptions) SetArrayFilters(af ArrayFilters) *UpdateOptions {
uo.ArrayFilters = &af
return uo
}
// SetBypassDocumentValidation sets the value for the BypassDocumentValidation field.
func (uo *UpdateOptions) SetBypassDocumentValidation(b bool) *UpdateOptions {
uo.BypassDocumentValidation = &b
return uo
}
// SetCollation sets the value for the Collation field.
func (uo *UpdateOptions) SetCollation(c *Collation) *UpdateOptions {
uo.Collation = c
return uo
}
// SetComment sets the value for the Comment field.
func (uo *UpdateOptions) SetComment(comment interface{}) *UpdateOptions {
uo.Comment = comment
return uo
}
// SetHint sets the value for the Hint field.
func (uo *UpdateOptions) SetHint(h interface{}) *UpdateOptions {
uo.Hint = h
return uo
}
// SetUpsert sets the value for the Upsert field.
func (uo *UpdateOptions) SetUpsert(b bool) *UpdateOptions {
uo.Upsert = &b
return uo
}
// SetLet sets the value for the Let field.
func (uo *UpdateOptions) SetLet(l interface{}) *UpdateOptions {
uo.Let = l
return uo
}
// MergeUpdateOptions combines the given UpdateOptions instances into a single UpdateOptions in a last-one-wins fashion.
func MergeUpdateOptions(opts ...*UpdateOptions) *UpdateOptions {
uOpts := Update()
for _, uo := range opts {
if uo == nil {
continue
}
if uo.ArrayFilters != nil {
uOpts.ArrayFilters = uo.ArrayFilters
}
if uo.BypassDocumentValidation != nil {
uOpts.BypassDocumentValidation = uo.BypassDocumentValidation
}
if uo.Collation != nil {
uOpts.Collation = uo.Collation
}
if uo.Comment != nil {
uOpts.Comment = uo.Comment
}
if uo.Hint != nil {
uOpts.Hint = uo.Hint
}
if uo.Upsert != nil {
uOpts.Upsert = uo.Upsert
}
if uo.Let != nil {
uOpts.Let = uo.Let
}
}
return uOpts
}
+82
View File
@@ -0,0 +1,82 @@
// 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 // import "go.mongodb.org/mongo-driver/mongo/readconcern"
import (
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
// ReadConcern for replica sets and replica set shards determines which data to return from a query.
type ReadConcern struct {
level string
}
// Option is an option to provide when creating a ReadConcern.
type Option func(concern *ReadConcern)
// Level creates an option that sets the level of a ReadConcern.
func Level(level string) Option {
return func(concern *ReadConcern) {
concern.level = level
}
}
// Local specifies that the query should return the instances most recent data.
func Local() *ReadConcern {
return New(Level("local"))
}
// Majority specifies that the query should return the instances most recent data acknowledged as
// having been written to a majority of members in the replica set.
func Majority() *ReadConcern {
return New(Level("majority"))
}
// Linearizable specifies that the query should return data that reflects all successful writes
// issued with a write concern of "majority" and acknowledged prior to the start of the read operation.
func Linearizable() *ReadConcern {
return New(Level("linearizable"))
}
// Available specifies that the query should return 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).
func Available() *ReadConcern {
return New(Level("available"))
}
// Snapshot is only available for operations within multi-document transactions.
func Snapshot() *ReadConcern {
return New(Level("snapshot"))
}
// New constructs a new read concern from the given string.
func New(options ...Option) *ReadConcern {
concern := &ReadConcern{}
for _, option := range options {
option(concern)
}
return concern
}
// MarshalBSONValue implements the bson.ValueMarshaler interface.
func (rc *ReadConcern) MarshalBSONValue() (bsontype.Type, []byte, error) {
var elems []byte
if len(rc.level) > 0 {
elems = bsoncore.AppendStringElement(elems, "level", rc.level)
}
return bsontype.EmbeddedDocument, bsoncore.BuildDocument(nil, elems), nil
}
// GetLevel returns the read concern level.
func (rc *ReadConcern) GetLevel() string {
return rc.level
}
+88
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
}
}
+71
View File
@@ -0,0 +1,71 @@
// 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/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 sets a single tag set used to match
// a server. The last call to WithTags or WithTagSets
// overrides all previous calls to either method.
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 sets the tag sets used to match
// a server. The last call to WithTags or WithTagSets
// overrides all previous calls to either method.
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.
func WithHedgeEnabled(hedgeEnabled bool) Option {
return func(rp *ReadPref) error {
rp.hedgeEnabled = &hedgeEnabled
return nil
}
}
+134
View File
@@ -0,0 +1,134 @@
// 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 "go.mongodb.org/mongo-driver/mongo/readpref"
import (
"bytes"
"errors"
"fmt"
"time"
"go.mongodb.org/mongo-driver/tag"
)
var (
errInvalidReadPreference = errors.New("can not specify tags, max staleness, or hedge with mode primary")
)
var primary = ReadPref{mode: PrimaryMode}
// Primary constructs a read preference with a PrimaryMode.
func Primary() *ReadPref {
return &primary
}
// 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.
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()
}
+281
View File
@@ -0,0 +1,281 @@
// 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 (
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/x/mongo/driver/operation"
)
// 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]interface{}
}
// 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 primitive.ObjectID.
InsertedID interface{}
}
// 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 primitive.ObjectID.
InsertedIDs []interface{}
}
// 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 `bson:"n"` // The number of documents deleted.
}
// 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 // Specfies 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 interface{} // The _id field of the upserted document, or nil if no upsert was done.
}
// UnmarshalBSON implements the bson.Unmarshaler interface.
//
// Deprecated: Unmarshalling an UpdateResult directly from BSON is not supported and may produce
// different results compared to running Update* operations directly.
func (result *UpdateResult) UnmarshalBSON(b []byte) error {
// TODO(GODRIVER-2367): Remove the ability to unmarshal BSON directly to an UpdateResult.
elems, err := bson.Raw(b).Elements()
if err != nil {
return err
}
for _, elem := range elems {
switch elem.Key() {
case "n":
switch elem.Value().Type {
case bson.TypeInt32:
result.MatchedCount = int64(elem.Value().Int32())
case bson.TypeInt64:
result.MatchedCount = elem.Value().Int64()
default:
return fmt.Errorf("Received invalid type for n, should be Int32 or Int64, received %s", elem.Value().Type)
}
case "nModified":
switch elem.Value().Type {
case bson.TypeInt32:
result.ModifiedCount = int64(elem.Value().Int32())
case bson.TypeInt64:
result.ModifiedCount = elem.Value().Int64()
default:
return fmt.Errorf("Received invalid type for nModified, should be Int32 or Int64, received %s", elem.Value().Type)
}
case "upserted":
switch elem.Value().Type {
case bson.TypeArray:
e, err := elem.Value().Array().IndexErr(0)
if err != nil {
break
}
if e.Value().Type != bson.TypeEmbeddedDocument {
break
}
var d struct {
ID interface{} `bson:"_id"`
}
err = bson.Unmarshal(e.Value().Document(), &d)
if err != nil {
return err
}
result.UpsertedID = d.ID
default:
return fmt.Errorf("Received invalid type for upserted, should be Array, received %s", elem.Value().Type)
}
}
}
return nil
}
// 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
}
var _ bson.Unmarshaler = (*IndexSpecification)(nil)
type unmarshalIndexSpecification 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"`
}
// UnmarshalBSON implements the bson.Unmarshaler interface.
func (i *IndexSpecification) UnmarshalBSON(data []byte) error {
var temp unmarshalIndexSpecification
if err := bson.Unmarshal(data, &temp); err != nil {
return err
}
i.Name = temp.Name
i.Namespace = temp.Namespace
i.KeysDocument = temp.KeysDocument
i.Version = temp.Version
i.ExpireAfterSeconds = temp.ExpireAfterSeconds
i.Sparse = temp.Sparse
i.Unique = temp.Unique
i.Clustered = temp.Clustered
return nil
}
// 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. This will be false for MongoDB versions < 3.4.
ReadOnly bool
// The collection UUID. This field will be nil for MongoDB versions < 3.6. For versions 3.6 and higher, this will
// be a primitive.Binary with Subtype 4.
UUID *primitive.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. This will be nil if the NameOnly
// option is used and for MongoDB versions < 3.4.
IDIndex *IndexSpecification
}
var _ bson.Unmarshaler = (*CollectionSpecification)(nil)
// unmarshalCollectionSpecification is used to unmarshal BSON bytes from a listCollections command into a
// CollectionSpecification.
type unmarshalCollectionSpecification struct {
Name string `bson:"name"`
Type string `bson:"type"`
Info *struct {
ReadOnly bool `bson:"readOnly"`
UUID *primitive.Binary `bson:"uuid"`
} `bson:"info"`
Options bson.Raw `bson:"options"`
IDIndex *IndexSpecification `bson:"idIndex"`
}
// UnmarshalBSON implements the bson.Unmarshaler interface.
func (cs *CollectionSpecification) UnmarshalBSON(data []byte) error {
var temp unmarshalCollectionSpecification
if err := bson.Unmarshal(data, &temp); err != nil {
return err
}
cs.Name = temp.Name
cs.Type = temp.Type
if cs.Type == "" {
// The "type" field is only present on 3.4+ because views were introduced in 3.4, so we implicitly set the
// value to "collection" if it's empty.
cs.Type = "collection"
}
if temp.Info != nil {
cs.ReadOnly = temp.Info.ReadOnly
cs.UUID = temp.Info.UUID
}
cs.Options = temp.Options
cs.IDIndex = temp.IDIndex
return nil
}
+377
View File
@@ -0,0 +1,377 @@
// 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"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/internal"
"go.mongodb.org/mongo-driver/mongo/description"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
"go.mongodb.org/mongo-driver/x/mongo/driver"
"go.mongodb.org/mongo-driver/x/mongo/driver/operation"
"go.mongodb.org/mongo-driver/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
// SessionContext combines the context.Context and mongo.Session interfaces. It should be used as the Context arguments
// to operations that should be executed in a session.
//
// Implementations of SessionContext are not safe for concurrent use by multiple goroutines.
//
// There are two ways to create a SessionContext and use it in a session/transaction. The first is to use one of the
// callback-based functions such as WithSession and UseSession. These functions create a SessionContext and pass it to
// the provided callback. The other is to use NewSessionContext to explicitly create a SessionContext.
type SessionContext interface {
context.Context
Session
}
type sessionContext struct {
context.Context
Session
}
type sessionKey struct {
}
// NewSessionContext creates a new SessionContext associated with the given Context and Session parameters.
func NewSessionContext(ctx context.Context, sess Session) SessionContext {
return &sessionContext{
Context: context.WithValue(ctx, sessionKey{}, sess),
Session: 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
}
// Session is an interface that represents 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. Custom implementations of this interface should not be used in production. 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.
//
// StartTransaction starts a new transaction, configured with the given options, on this session. This method will
// return an error if there is already a transaction in-progress for this session.
//
// CommitTransaction commits the active transaction for this session. This method will return an error if there is no
// active transaction for this session or the transaction has been aborted.
//
// AbortTransaction aborts the active transaction for this session. This method will return an error if there is no
// active transaction for this session or the transaction has been committed or aborted.
//
// 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, sessCtx 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. 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.
//
// ClusterTime, OperationTime, Client, and ID return the session's current cluster time, the session's current operation
// time, the Client associated with the session, and the ID document associated with the session, respectively. The ID
// document for a session is in the form {"id": <BSON binary value>}.
//
// EndSession method should abort any existing transactions and close the session.
//
// AdvanceClusterTime advances the cluster time for a session. This method will return an error if the session has ended.
//
// AdvanceOperationTime advances the operation time for a session. This method will return an error if the session has
// ended.
type Session interface {
// Functions to modify session state.
StartTransaction(...*options.TransactionOptions) error
AbortTransaction(context.Context) error
CommitTransaction(context.Context) error
WithTransaction(ctx context.Context, fn func(sessCtx SessionContext) (interface{}, error),
opts ...*options.TransactionOptions) (interface{}, error)
EndSession(context.Context)
// Functions to retrieve session properties.
ClusterTime() bson.Raw
OperationTime() *primitive.Timestamp
Client() *Client
ID() bson.Raw
// Functions to modify mutable session properties.
AdvanceClusterTime(bson.Raw) error
AdvanceOperationTime(*primitive.Timestamp) error
session()
}
// XSession is an unstable interface for internal use only.
//
// Deprecated: This interface is unstable because it provides access to a session.Client object, which exists in the
// "x" package. It should not be used by applications and may be changed or removed in any release.
type XSession interface {
ClientSession() *session.Client
}
// sessionImpl represents a set of sequential operations executed by an application that are related in some way.
type sessionImpl struct {
clientSession *session.Client
client *Client
deployment driver.Deployment
didCommitAfterStart bool // true if commit was called after start with no other operations
}
var _ Session = &sessionImpl{}
var _ XSession = &sessionImpl{}
// ClientSession implements the XSession interface.
func (s *sessionImpl) ClientSession() *session.Client {
return s.clientSession
}
// ID implements the Session interface.
func (s *sessionImpl) ID() bson.Raw {
return bson.Raw(s.clientSession.SessionID)
}
// EndSession implements the Session interface.
func (s *sessionImpl) EndSession(ctx context.Context) {
if s.clientSession.TransactionInProgress() {
// ignore all errors aborting during an end session
_ = s.AbortTransaction(ctx)
}
s.clientSession.EndSession()
}
// WithTransaction implements the Session interface.
func (s *sessionImpl) WithTransaction(ctx context.Context, fn func(sessCtx SessionContext) (interface{}, error),
opts ...*options.TransactionOptions) (interface{}, 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(internal.NewBackgroundContext(ctx))
}
select {
case <-timeout.C:
return nil, err
default:
}
// End if context has timed out or been canceled, as retrying has no chance of success.
if ctx.Err() != nil {
return res, err
}
if errorHasLabel(err, driver.TransientTransactionError) {
continue
}
return res, err
}
err = s.clientSession.CheckAbortTransaction()
if err != nil {
return res, nil
}
CommitLoop:
for {
err = s.CommitTransaction(ctx)
// End when error is nil (transaction has been committed), or when context has timed out or been
// canceled, as retrying has no chance of success.
if err == nil || ctx.Err() != nil {
return res, err
}
select {
case <-timeout.C:
return res, err
default:
}
if cerr, ok := err.(CommandError); ok {
if cerr.HasErrorLabel(driver.UnknownTransactionCommitResult) && !cerr.IsMaxTimeMSExpiredError() {
continue
}
if cerr.HasErrorLabel(driver.TransientTransactionError) {
break CommitLoop
}
}
return res, err
}
}
}
// StartTransaction implements the Session interface.
func (s *sessionImpl) StartTransaction(opts ...*options.TransactionOptions) error {
err := s.clientSession.CheckStartTransaction()
if err != nil {
return err
}
s.didCommitAfterStart = false
topts := options.MergeTransactionOptions(opts...)
coreOpts := &session.TransactionOptions{
ReadConcern: topts.ReadConcern,
ReadPreference: topts.ReadPreference,
WriteConcern: topts.WriteConcern,
MaxCommitTime: topts.MaxCommitTime,
}
return s.clientSession.StartTransaction(coreOpts)
}
// AbortTransaction implements the Session interface.
func (s *sessionImpl) 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, description.WriteSelector())
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).Execute(ctx)
s.clientSession.Aborting = false
_ = s.clientSession.AbortTransaction()
return nil
}
// CommitTransaction implements the Session interface.
func (s *sessionImpl) 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, description.WriteSelector())
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)
if s.clientSession.CurrentMct != nil {
op.MaxTimeMS(int64(*s.clientSession.CurrentMct / time.Millisecond))
}
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 replaceErrors(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 replaceErrors(err)
}
return commitErr
}
// ClusterTime implements the Session interface.
func (s *sessionImpl) ClusterTime() bson.Raw {
return s.clientSession.ClusterTime
}
// AdvanceClusterTime implements the Session interface.
func (s *sessionImpl) AdvanceClusterTime(d bson.Raw) error {
return s.clientSession.AdvanceClusterTime(d)
}
// OperationTime implements the Session interface.
func (s *sessionImpl) OperationTime() *primitive.Timestamp {
return s.clientSession.OperationTime
}
// AdvanceOperationTime implements the Session interface.
func (s *sessionImpl) AdvanceOperationTime(ts *primitive.Timestamp) error {
return s.clientSession.AdvanceOperationTime(ts)
}
// Client implements the Session interface.
func (s *sessionImpl) Client() *Client {
return s.client
}
// session implements the Session interface.
func (*sessionImpl) session() {
}
// sessionFromContext checks for a sessionImpl in the argued context and returns the session if it
// exists
func sessionFromContext(ctx context.Context) *session.Client {
s := ctx.Value(sessionKey{})
if ses, ok := s.(*sessionImpl); ses != nil && ok {
return ses.clientSession
}
return nil
}
+121
View File
@@ -0,0 +1,121 @@
// 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/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
)
// 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 {
err error
cur *Cursor
rdr bson.Raw
reg *bsoncodec.Registry
}
// 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.DefaultRegistry 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 interface{}, err error, registry *bsoncodec.Registry) *SingleResult {
if document == nil {
return &SingleResult{err: ErrNilDocument}
}
if registry == nil {
registry = bson.DefaultRegistry
}
cur, createErr := NewCursorFromDocuments([]interface{}{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 interface{}) 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
}
return bson.UnmarshalWithRegistry(sr.reg, sr.rdr, v)
}
// DecodeBytes will return 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) DecodeBytes() (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(context.TODO())
if !sr.cur.Next(context.TODO()) {
if err := sr.cur.Err(); err != nil {
return err
}
return ErrNoDocuments
}
sr.rdr = sr.cur.Current
return nil
}
return ErrNoDocuments
}
// Err returns the error from the operation that created this SingleResult. If the operation was successful but did not
// return any documents, Err will return ErrNoDocuments. If the operation was successful and returned a document, Err
// will return nil.
func (sr *SingleResult) Err() error {
sr.err = sr.setRdrContents()
return sr.err
}
+7
View File
@@ -0,0 +1,7 @@
// 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
+226
View File
@@ -0,0 +1,226 @@
// 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 // import "go.mongodb.org/mongo-driver/mongo/writeconcern"
import (
"errors"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
// ErrInconsistent indicates that an inconsistent write concern was specified.
var ErrInconsistent = errors.New("a write concern cannot have both w=0 and j=true")
// ErrEmptyWriteConcern indicates that a write concern has no fields set.
var ErrEmptyWriteConcern = errors.New("a write concern must have at least one field set")
// ErrNegativeW indicates that a negative integer `w` field was specified.
var ErrNegativeW = errors.New("write concern `w` field cannot be a negative number")
// ErrNegativeWTimeout indicates that a negative WTimeout was specified.
var ErrNegativeWTimeout = errors.New("write concern `wtimeout` field cannot be negative")
// WriteConcern describes the level of acknowledgement requested from MongoDB for write operations
// to a standalone mongod or to replica sets or to sharded clusters.
type WriteConcern struct {
w interface{}
j bool
// NOTE(benjirewis): wTimeout will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can run
// before returning an error. Using wTimeout and setting Timeout on the client will result in
// undefined behavior.
wTimeout time.Duration
}
// Option is an option to provide when creating a WriteConcern.
type Option func(concern *WriteConcern)
// New constructs a new WriteConcern.
func New(options ...Option) *WriteConcern {
concern := &WriteConcern{}
for _, option := range options {
option(concern)
}
return concern
}
// W requests acknowledgement that write operations propagate to the specified number of mongod
// instances.
func W(w int) Option {
return func(concern *WriteConcern) {
concern.w = w
}
}
// WMajority requests acknowledgement that write operations propagate to the majority of mongod
// instances.
func WMajority() Option {
return func(concern *WriteConcern) {
concern.w = "majority"
}
}
// WTagSet requests acknowledgement that write operations propagate to the specified mongod
// instance.
func WTagSet(tag string) Option {
return func(concern *WriteConcern) {
concern.w = tag
}
}
// J requests acknowledgement from MongoDB that write operations are written to
// the journal.
func J(j bool) Option {
return func(concern *WriteConcern) {
concern.j = j
}
}
// WTimeout specifies specifies a time limit for the write concern.
//
// NOTE(benjirewis): wTimeout will be deprecated in a future release. The more general Timeout
// option may be used in its place to control the amount of time that a single operation can run
// before returning an error. Using wTimeout and setting Timeout on the client will result in
// undefined behavior.
func WTimeout(d time.Duration) Option {
return func(concern *WriteConcern) {
concern.wTimeout = d
}
}
// MarshalBSONValue implements the bson.ValueMarshaler interface.
func (wc *WriteConcern) MarshalBSONValue() (bsontype.Type, []byte, error) {
if !wc.IsValid() {
return bsontype.Type(0), nil, ErrInconsistent
}
var elems []byte
if wc.w != nil {
switch t := wc.w.(type) {
case int:
if t < 0 {
return bsontype.Type(0), nil, ErrNegativeW
}
elems = bsoncore.AppendInt32Element(elems, "w", int32(t))
case string:
elems = bsoncore.AppendStringElement(elems, "w", t)
}
}
if wc.j {
elems = bsoncore.AppendBooleanElement(elems, "j", wc.j)
}
if wc.wTimeout < 0 {
return bsontype.Type(0), nil, ErrNegativeWTimeout
}
if wc.wTimeout != 0 {
elems = bsoncore.AppendInt64Element(elems, "wtimeout", int64(wc.wTimeout/time.Millisecond))
}
if len(elems) == 0 {
return bsontype.Type(0), nil, ErrEmptyWriteConcern
}
return bsontype.EmbeddedDocument, bsoncore.BuildDocument(nil, elems), nil
}
// AcknowledgedValue returns true if a BSON RawValue for a write concern represents an acknowledged write concern.
// The element's value must be a document representing a write concern.
func AcknowledgedValue(rawv bson.RawValue) bool {
doc, ok := bsoncore.Value{Type: rawv.Type, Data: rawv.Value}.DocumentOK()
if !ok {
return false
}
val, err := doc.LookupErr("w")
if err != nil {
// key w not found --> acknowledged
return true
}
i32, ok := val.Int32OK()
if !ok {
return false
}
return i32 != 0
}
// Acknowledged indicates whether or not a write with the given write concern will be acknowledged.
func (wc *WriteConcern) Acknowledged() bool {
if wc == nil || wc.j {
return true
}
switch v := wc.w.(type) {
case int:
if v == 0 {
return false
}
}
return true
}
// IsValid checks whether the write concern is invalid.
func (wc *WriteConcern) IsValid() bool {
if !wc.j {
return true
}
switch v := wc.w.(type) {
case int:
if v == 0 {
return false
}
}
return true
}
// GetW returns the write concern w level.
func (wc *WriteConcern) GetW() interface{} {
return wc.w
}
// GetJ returns the write concern journaling level.
func (wc *WriteConcern) GetJ() bool {
return wc.j
}
// GetWTimeout returns the write concern timeout.
func (wc *WriteConcern) GetWTimeout() time.Duration {
return wc.wTimeout
}
// WithOptions returns a copy of this WriteConcern with the options set.
func (wc *WriteConcern) WithOptions(options ...Option) *WriteConcern {
if wc == nil {
return New(options...)
}
newWC := &WriteConcern{}
*newWC = *wc
for _, option := range options {
option(newWC)
}
return newWC
}
// AckWrite returns true if a write concern represents an acknowledged write
func AckWrite(wc *WriteConcern) bool {
return wc == nil || wc.Acknowledged()
}