Skip to content

Commit

Permalink
GODRIVER-2685 Simplify the writeconcern API.
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewdale committed Apr 14, 2023
1 parent 8800d29 commit 18bdc1c
Show file tree
Hide file tree
Showing 2 changed files with 317 additions and 37 deletions.
244 changes: 207 additions & 37 deletions mongo/writeconcern/writeconcern.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

// Package writeconcern defines write concerns for MongoDB operations.
//
// For more information about MongoDB write concerns, see
// https://www.mongodb.com/docs/manual/reference/write-concern/
package writeconcern // import "go.mongodb.org/mongo-driver/mongo/writeconcern"

import (
"errors"
"fmt"
"time"

"go.mongodb.org/mongo-driver/bson"
Expand All @@ -17,34 +21,136 @@ import (
)

// ErrInconsistent indicates that an inconsistent write concern was specified.
//
// Deprecated: ErrInconsistent will be removed in Go Driver 2.0.
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.
//
// Deprecated: ErrEmptyWriteConcern will be removed in Go Driver 2.0.
var ErrEmptyWriteConcern = errors.New("a write concern must have at least one field set")

// ErrNegativeW indicates that a negative integer `w` field was specified.
//
// Deprecated: ErrNegativeW will be removed in Go Driver 2.0.
var ErrNegativeW = errors.New("write concern `w` field cannot be a negative number")

// ErrNegativeWTimeout indicates that a negative WTimeout was specified.
//
// Deprecated: ErrNegativeWTimeout will be removed in Go Driver 2.0.
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.
// A WriteConcern defines a MongoDB read concern, which describes the level of acknowledgment
// requested from MongoDB for write operations to a standalone mongod, to replica sets, or to
// sharded clusters.
//
// For more information about MongoDB write concerns, see
// https://www.mongodb.com/docs/manual/reference/write-concern/
type WriteConcern struct {
w interface{}
j bool
// W requests acknowledgment that the write operation has propagated to a specified number of
// mongod instances or to mongod instances with specified tags.
//
// W must be a type string or int value.
//
// For more information about the "w" option, see
// https://www.mongodb.com/docs/manual/reference/write-concern/#w-option
W interface{}

// J requests acknowledgment from MongoDB that the write operation has been written to the
// on-disk journal.
//
// For more information about the "j" option, see
// https://www.mongodb.com/docs/manual/reference/write-concern/#j-option
J *bool

// WTimeout specifies a time limit for the write concern.
//
// It is only applicable for "w" values greater than 1. Using a WTimeout and setting Timeout on
// the Client at the same time will result in undefined behavior.
//
// For more information about the "wtimeout" option, see
// https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout
WTimeout time.Duration
}

// 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
// Majority returns a WriteConcern that requests acknowledgment that write operations have been
// durably committed to the calculated majority of the data-bearing voting members. If journaling is
// enabled on mongod, nodes only acknowledge when the write operation has been written to the
// on-disk journal.
//
// For more information about write concern "w: majority", see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-writeconcern.-majority-
func Majority() *WriteConcern {
return &WriteConcern{W: "majority"}
}

// Custom returns a WriteConcern that requests acknowledgment that the write operation has
// propagated to tagged members that satisfy the custom write concern defined in
// "settings.getLastErrorModes". If journaling is enabled on mongod, nodes only acknowledge when the
// write operation has been written to the on-disk journal.
//
// For more information about custom write concern names, see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-writeconcern.-custom-write-concern-name-
func Custom(tag string) *WriteConcern {
return &WriteConcern{W: tag}
}

// W0 returns a WriteConcern that requests no acknowledgment of the write operation.
//
// For more information about write concern "w: 0", see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-writeconcern.-number-
func W0() *WriteConcern {
return &WriteConcern{W: 0}
}

// W1 returns a WriteConcern that requests acknowledgment that the write operation has propagated to
// the standalone mongod or the primary in a replica set. If journal is true, mongod nodes only
// acknowledge when the write operation has been written to the on-disk journal.
//
// For more information about write concern "w: 1", see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-writeconcern.-number-
// TODO: Include journal parameter?
func W1(journal bool) *WriteConcern {
return &WriteConcern{W: 1, J: &journal}
}

// WNum returns a WriteConcern that requests acknowledgment that the write operation has propagated
// to n mongod instances. If journal is true, mongod nodes only acknowledge when the write operation
// has been written to the on-disk journal.
//
// For more information about write concern "w: <number>", see
// https://www.mongodb.com/docs/manual/reference/write-concern/#mongodb-writeconcern-writeconcern.-number-
// TODO: Include journal parameter?
func WNum(n int, journal bool) *WriteConcern {
return &WriteConcern{W: n, J: &journal}
}

// Option is an option to provide when creating a WriteConcern.
//
// Deprecated: Use the WriteConcern helpers instead, then set individual fields if necessary.
// For example:
//
// writeconcern.WNum(2, true)
//
// or
//
// wc := writeconcern.Majority()
// journal := true
// wc.J = &journal
type Option func(concern *WriteConcern)

// New constructs a new WriteConcern.
//
// Deprecated: Use the WriteConcern helpers instead, then set individual fields if necessary.
// For example:
//
// writeconcern.WNum(2, true)
//
// or
//
// wc := writeconcern.Majority()
// journal := true
// wc.J = &journal
func New(options ...Option) *WriteConcern {
concern := &WriteConcern{}

Expand All @@ -57,58 +163,87 @@ func New(options ...Option) *WriteConcern {

// W requests acknowledgement that write operations propagate to the specified number of mongod
// instances.
//
// Deprecated: Use the WNum function instead.
func W(w int) Option {
return func(concern *WriteConcern) {
concern.w = w
concern.W = w
}
}

// WMajority requests acknowledgement that write operations propagate to the majority of mongod
// instances.
//
// Deprecated: Use the Majority function instead.
func WMajority() Option {
return func(concern *WriteConcern) {
concern.w = "majority"
concern.W = "majority"
}
}

// WTagSet requests acknowledgement that write operations propagate to the specified mongod
// instance.
//
// Deprecated: Use the Custom function instead.
func WTagSet(tag string) Option {
return func(concern *WriteConcern) {
concern.w = tag
concern.W = tag
}
}

// J requests acknowledgement from MongoDB that write operations are written to
// the journal.
//
// Deprecated: Use the WriteConcern helpers instead, then set individual fields if necessary.
// For example:
//
// writeconcern.WNum(2, true)
//
// or
//
// wc := writeconcern.Majority()
// journal := true
// wc.J = &journal
func J(j bool) Option {
return func(concern *WriteConcern) {
concern.j = j
// To maintain backward compatible behavior (now that the J field is a *bool), only set a
// value for J if the input is true. If the input is false, do not set a value, which omits
// "j" from the marshaled write concern.
if j {
concern.J = &j
}
}
}

// WTimeout specifies specifies a time limit for the write concern.
// WTimeout specifies a time limit for the write concern.
//
// It is only applicable for "w" values greater than 1. Using a WTimeout and setting Timeout on the
// Client at the same time will result in undefined behavior.
//
// 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.
// Deprecated: Use the WriteConcern helpers and then set WTimeout instead. For example:
//
// wc := writeconcern.Majority()
// wc.WTimeout = 30 * time.Second
func WTimeout(d time.Duration) Option {
return func(concern *WriteConcern) {
concern.wTimeout = d
concern.WTimeout = d
}
}

// MarshalBSONValue implements the bson.ValueMarshaler interface.
//
// Deprecated: Marshaling a WriteConcern to BSON will not be supported in Go Driver 2.0.
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) {
if wc.W != nil {
// Only support string or int values for W. That aligns with the documentation and the
// behavior of other functions, like Acknowledged.
switch t := wc.W.(type) {
case int:
if t < 0 {
return bsontype.Type(0), nil, ErrNegativeW
Expand All @@ -117,19 +252,23 @@ func (wc *WriteConcern) MarshalBSONValue() (bsontype.Type, []byte, error) {
elems = bsoncore.AppendInt32Element(elems, "w", int32(t))
case string:
elems = bsoncore.AppendStringElement(elems, "w", t)
default:
return bsontype.Type(0),
nil,
fmt.Errorf("WriteConcern.W must be a string or int, but is a %T", wc.W)
}
}

if wc.j {
elems = bsoncore.AppendBooleanElement(elems, "j", wc.j)
if wc.J != nil {
elems = bsoncore.AppendBooleanElement(elems, "j", *wc.J)
}

if wc.wTimeout < 0 {
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 wc.WTimeout != 0 {
elems = bsoncore.AppendInt64Element(elems, "wtimeout", int64(wc.WTimeout/time.Millisecond))
}

if len(elems) == 0 {
Expand Down Expand Up @@ -163,27 +302,37 @@ func AcknowledgedValue(rawv bson.RawValue) bool {

// 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 {
if wc == nil || (wc.J != nil && *wc.J) {
return true
}

switch v := wc.w.(type) {
case int:
if v == 0 {
return false
}
// Only int value 0 with no "j" or "j: false" is an unacknowledged write concern. All other
// values are either acknowledged or unsupported, which will cause a BSON marshaling error.
if i, ok := wc.W.(int); ok && i == 0 {
return false
}

return true
}

// IsValid checks whether the write concern is invalid.
//
// Deprecated: IsValid will not be supported in Go Driver 2.0.
func (wc *WriteConcern) IsValid() bool {
if !wc.j {
if wc.J == nil || !*wc.J {
return true
}

switch v := wc.w.(type) {
switch v := wc.W.(type) {
// Now that users can set W to any type, IsValid doesn't handle cases where W is an unsupported
// type. However, the only use case of this function appears to be in MarshalBSONValue, which
// now does its own unsupported type handling and returns an usable error for unsupported types
// (MarshalBSONValue returns ErrInconsistent when IsValid is false, which doesn't cover
// unsupported types). To maintain backward compatibility and return good errors in
// MarshalBSONValue, keep the logic here the same and don't handle unsupported types.
//
// IsValid is deprecated and will be removed in Go Driver 2.0, so the inconsistency will only
// exist during the transition from Go Driver 1.x to Go Driver 2.0.
case int:
if v == 0 {
return false
Expand All @@ -194,21 +343,40 @@ func (wc *WriteConcern) IsValid() bool {
}

// GetW returns the write concern w level.
//
// Deprecated: Use the WriteConcern.W field instead.
func (wc *WriteConcern) GetW() interface{} {
return wc.w
return wc.W
}

// GetJ returns the write concern journaling level.
//
// Deprecated: Use the WriteConcern.J field instead.
func (wc *WriteConcern) GetJ() bool {
return wc.j
// Treat a nil J as false. That maintains backward compatibility with the existing behavior of
// GetJ where unset is false. If users want the real value of J, they can access the J field.
return wc.J != nil && *wc.J
}

// GetWTimeout returns the write concern timeout.
//
// Deprecated: Use the WriteConcern.WTimeout field instead.
func (wc *WriteConcern) GetWTimeout() time.Duration {
return wc.wTimeout
return wc.WTimeout
}

// WithOptions returns a copy of this WriteConcern with the options set.
//
// Deprecated: Use the WriteConcern helpers instead, then set individual fields if necessary.
// For example:
//
// writeconcern.WNum(2, true)
//
// or
//
// wc := writeconcern.Majority()
// journal := true
// wc.J = &journal
func (wc *WriteConcern) WithOptions(options ...Option) *WriteConcern {
if wc == nil {
return New(options...)
Expand All @@ -224,6 +392,8 @@ func (wc *WriteConcern) WithOptions(options ...Option) *WriteConcern {
}

// AckWrite returns true if a write concern represents an acknowledged write
//
// Deprecated: AckWrite will not be supported in Go Driver 2.0.
func AckWrite(wc *WriteConcern) bool {
return wc == nil || wc.Acknowledged()
}
Loading

0 comments on commit 18bdc1c

Please sign in to comment.