升级流程引擎配置文件版本
This commit is contained in:
+49
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
+1111
File diff suppressed because it is too large
Load Diff
+314
@@ -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:]
|
||||
}
|
||||
+1900
File diff suppressed because it is too large
Load Diff
+65
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
+66
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+200
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+138
@@ -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
|
||||
}
|
||||
+1138
File diff suppressed because it is too large
Load Diff
+88
@@ -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
@@ -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
|
||||
}
|
||||
+326
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+64
@@ -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
|
||||
}
|
||||
+1095
File diff suppressed because it is too large
Load Diff
+329
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+66
@@ -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
|
||||
}
|
||||
+55
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+52
@@ -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
@@ -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
@@ -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 driver’s 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
@@ -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
@@ -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
@@ -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
@@ -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 instance’s most recent data.
|
||||
func Local() *ReadConcern {
|
||||
return New(Level("local"))
|
||||
}
|
||||
|
||||
// Majority specifies that the query should return the instance’s 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user