blob: 5ecb48cce7d878e56b5a12340b10adff943ca172 [file] [log] [blame]
// 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 command
import (
"errors"
"fmt"
"strings"
"github.com/mongodb/mongo-go-driver/bson"
"github.com/mongodb/mongo-go-driver/x/network/result"
)
var (
// ErrUnknownCommandFailure occurs when a command fails for an unknown reason.
ErrUnknownCommandFailure = errors.New("unknown command failure")
// ErrNoCommandResponse occurs when the server sent no response document to a command.
ErrNoCommandResponse = errors.New("no command response document")
// ErrMultiDocCommandResponse occurs when the server sent multiple documents in response to a command.
ErrMultiDocCommandResponse = errors.New("command returned multiple documents")
// ErrNoDocCommandResponse occurs when the server indicated a response existed, but none was found.
ErrNoDocCommandResponse = errors.New("command returned no documents")
// ErrDocumentTooLarge occurs when a document that is larger than the maximum size accepted by a
// server is passed to an insert command.
ErrDocumentTooLarge = errors.New("an inserted document is too large")
// ErrNonPrimaryRP occurs when a nonprimary read preference is used with a transaction.
ErrNonPrimaryRP = errors.New("read preference in a transaction must be primary")
// UnknownTransactionCommitResult is an error label for unknown transaction commit results.
UnknownTransactionCommitResult = "UnknownTransactionCommitResult"
// TransientTransactionError is an error label for transient errors with transactions.
TransientTransactionError = "TransientTransactionError"
// NetworkError is an error label for network errors.
NetworkError = "NetworkError"
// ReplyDocumentMismatch is an error label for OP_QUERY field mismatch errors.
ReplyDocumentMismatch = "malformed OP_REPLY: NumberReturned does not match number of documents returned"
)
var retryableCodes = []int32{11600, 11602, 10107, 13435, 13436, 189, 91, 7, 6, 89, 9001}
// QueryFailureError is an error representing a command failure as a document.
type QueryFailureError struct {
Message string
Response bson.Raw
}
// Error implements the error interface.
func (e QueryFailureError) Error() string {
return fmt.Sprintf("%s: %v", e.Message, e.Response)
}
// ResponseError is an error parsing the response to a command.
type ResponseError struct {
Message string
Wrapped error
}
// NewCommandResponseError creates a CommandResponseError.
func NewCommandResponseError(msg string, err error) ResponseError {
return ResponseError{Message: msg, Wrapped: err}
}
// Error implements the error interface.
func (e ResponseError) Error() string {
if e.Wrapped != nil {
return fmt.Sprintf("%s: %s", e.Message, e.Wrapped)
}
return fmt.Sprintf("%s", e.Message)
}
// Error is a command execution error from the database.
type Error struct {
Code int32
Message string
Labels []string
Name string
}
// Error implements the error interface.
func (e Error) Error() string {
if e.Name != "" {
return fmt.Sprintf("(%v) %v", e.Name, e.Message)
}
return e.Message
}
// HasErrorLabel returns true if the error contains the specified label.
func (e Error) HasErrorLabel(label string) bool {
if e.Labels != nil {
for _, l := range e.Labels {
if l == label {
return true
}
}
}
return false
}
// Retryable returns true if the error is retryable
func (e Error) Retryable() bool {
for _, label := range e.Labels {
if label == NetworkError {
return true
}
}
for _, code := range retryableCodes {
if e.Code == code {
return true
}
}
if strings.Contains(e.Message, "not master") || strings.Contains(e.Message, "node is recovering") {
return true
}
return false
}
// IsWriteConcernErrorRetryable returns true if the write concern error is retryable.
func IsWriteConcernErrorRetryable(wce *result.WriteConcernError) bool {
for _, code := range retryableCodes {
if int32(wce.Code) == code {
return true
}
}
if strings.Contains(wce.ErrMsg, "not master") || strings.Contains(wce.ErrMsg, "node is recovering") {
return true
}
return false
}
// IsNotFound indicates if the error is from a namespace not being found.
func IsNotFound(err error) bool {
e, ok := err.(Error)
// need message check because legacy servers don't include the error code
return ok && (e.Code == 26 || e.Message == "ns not found")
}