github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/pgwire/pgerror/errors.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package pgerror
    12  
    13  import (
    14  	"bytes"
    15  	"fmt"
    16  	"regexp"
    17  	"strings"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    20  	"github.com/cockroachdb/errors"
    21  	"github.com/lib/pq"
    22  )
    23  
    24  var _ error = (*Error)(nil)
    25  var _ errors.ErrorHinter = (*Error)(nil)
    26  var _ errors.ErrorDetailer = (*Error)(nil)
    27  var _ fmt.Formatter = (*Error)(nil)
    28  
    29  // Error implements the error interface.
    30  func (pg *Error) Error() string { return pg.Message }
    31  
    32  // ErrorHint implements the hintdetail.ErrorHinter interface.
    33  func (pg *Error) ErrorHint() string { return pg.Hint }
    34  
    35  // ErrorDetail implements the hintdetail.ErrorDetailer interface.
    36  func (pg *Error) ErrorDetail() string { return pg.Detail }
    37  
    38  // FullError can be used when the hint and/or detail are to be tested.
    39  func FullError(err error) string {
    40  	var errString string
    41  	if pqErr := (*pq.Error)(nil); errors.As(err, &pqErr) {
    42  		errString = formatMsgHintDetail("pq", pqErr.Message, pqErr.Hint, pqErr.Detail)
    43  	} else {
    44  		pg := Flatten(err)
    45  		errString = formatMsgHintDetail(pg.Severity, err.Error(), pg.Hint, pg.Detail)
    46  	}
    47  	return errString
    48  }
    49  
    50  func formatMsgHintDetail(prefix, msg, hint, detail string) string {
    51  	var b strings.Builder
    52  	b.WriteString(prefix)
    53  	b.WriteString(": ")
    54  	b.WriteString(msg)
    55  	if hint != "" {
    56  		b.WriteString("\nHINT: ")
    57  		b.WriteString(hint)
    58  	}
    59  	if detail != "" {
    60  		b.WriteString("\nDETAIL: ")
    61  		b.WriteString(detail)
    62  	}
    63  	return b.String()
    64  }
    65  
    66  // NewWithDepthf creates an error with a pg code and extracts the context
    67  // information at the specified depth level.
    68  func NewWithDepthf(depth int, code string, format string, args ...interface{}) error {
    69  	err := errors.NewWithDepthf(1+depth, format, args...)
    70  	err = WithCandidateCode(err, code)
    71  	return err
    72  }
    73  
    74  // New creates an error with a code.
    75  func New(code string, msg string) error {
    76  	err := errors.NewWithDepth(1, msg)
    77  	err = WithCandidateCode(err, code)
    78  	return err
    79  }
    80  
    81  // Newf creates an Error with a format string.
    82  func Newf(code string, format string, args ...interface{}) error {
    83  	err := errors.NewWithDepthf(1, format, args...)
    84  	err = WithCandidateCode(err, code)
    85  	return err
    86  }
    87  
    88  // DangerousStatementf creates a new error for "rejected dangerous
    89  // statements".
    90  func DangerousStatementf(format string, args ...interface{}) error {
    91  	var buf bytes.Buffer
    92  	buf.WriteString("rejected: ")
    93  	fmt.Fprintf(&buf, format, args...)
    94  	buf.WriteString(" (sql_safe_updates = true)")
    95  	err := errors.Newf("%s", buf.String())
    96  	err = errors.WithSafeDetails(err, format, args...)
    97  	err = WithCandidateCode(err, pgcode.Warning)
    98  	return err
    99  }
   100  
   101  // WrongNumberOfPreparedStatements creates new an Error for trying to prepare
   102  // a query string containing more than one statement.
   103  func WrongNumberOfPreparedStatements(n int) error {
   104  	err := errors.NewWithDepthf(1, "prepared statement had %d statements, expected 1", errors.Safe(n))
   105  	err = WithCandidateCode(err, pgcode.InvalidPreparedStatementDefinition)
   106  	return err
   107  }
   108  
   109  var _ fmt.Formatter = &Error{}
   110  
   111  // Format implements the fmt.Formatter interface.
   112  //
   113  // %v/%s prints the error as usual.
   114  // %#v adds the pg error code at the beginning.
   115  // %+v prints all the details, including the embedded stack traces.
   116  func (pg *Error) Format(s fmt.State, verb rune) {
   117  	switch {
   118  	case verb == 'v' && s.Flag('+'):
   119  		// %+v prints all details.
   120  		if pg.Source != nil {
   121  			fmt.Fprintf(s, "%s:%d in %s(): ", pg.Source.File, pg.Source.Line, pg.Source.Function)
   122  		}
   123  		fmt.Fprintf(s, "(%s) %s", pg.Code, pg.Message)
   124  		return
   125  	case verb == 'v' && s.Flag('#'):
   126  		// %#v spells out the code as prefix.
   127  		fmt.Fprintf(s, "(%s) %s", pg.Code, pg.Message)
   128  	case verb == 'v':
   129  		fallthrough
   130  	case verb == 's':
   131  		fmt.Fprintf(s, "%s", pg.Message)
   132  	case verb == 'q':
   133  		fmt.Fprintf(s, "%q", pg.Message)
   134  	}
   135  }
   136  
   137  // IsSQLRetryableError returns true if err is retryable. This is true
   138  // for errors that show a connection issue or an issue with the node
   139  // itself. This can occur when a node is restarting or is unstable in
   140  // some other way. Note that retryable errors may occur event in cases
   141  // where the SQL execution ran to completion.
   142  //
   143  // TODO(bdarnell): Why are RPC errors in this list? These should
   144  // generally be retried on the server side or transformed into
   145  // ambiguous result errors ("connection reset/refused" are needed for
   146  // the pgwire connection, but anything RPC-related should be handled
   147  // within the cluster).
   148  // TODO(knz): This should really use the errors library. Investigate
   149  // how to get rid of the error message comparison.
   150  func IsSQLRetryableError(err error) bool {
   151  	// Don't forget to update the corresponding test when making adjustments
   152  	// here.
   153  	errString := FullError(err)
   154  	matched, merr := regexp.MatchString(
   155  		"(no inbound stream connection|connection reset by peer|connection refused|failed to send RPC|rpc error: code = Unavailable|EOF|result is ambiguous)",
   156  		errString)
   157  	if merr != nil {
   158  		return false
   159  	}
   160  	return matched
   161  }