github.com/jackc/pgx/v5@v5.5.5/pgconn/errors.go (about)

     1  package pgconn
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"net/url"
     9  	"regexp"
    10  	"strings"
    11  )
    12  
    13  // SafeToRetry checks if the err is guaranteed to have occurred before sending any data to the server.
    14  func SafeToRetry(err error) bool {
    15  	if e, ok := err.(interface{ SafeToRetry() bool }); ok {
    16  		return e.SafeToRetry()
    17  	}
    18  	return false
    19  }
    20  
    21  // Timeout checks if err was was caused by a timeout. To be specific, it is true if err was caused within pgconn by a
    22  // context.DeadlineExceeded or an implementer of net.Error where Timeout() is true.
    23  func Timeout(err error) bool {
    24  	var timeoutErr *errTimeout
    25  	return errors.As(err, &timeoutErr)
    26  }
    27  
    28  // PgError represents an error reported by the PostgreSQL server. See
    29  // http://www.postgresql.org/docs/11/static/protocol-error-fields.html for
    30  // detailed field description.
    31  type PgError struct {
    32  	Severity         string
    33  	Code             string
    34  	Message          string
    35  	Detail           string
    36  	Hint             string
    37  	Position         int32
    38  	InternalPosition int32
    39  	InternalQuery    string
    40  	Where            string
    41  	SchemaName       string
    42  	TableName        string
    43  	ColumnName       string
    44  	DataTypeName     string
    45  	ConstraintName   string
    46  	File             string
    47  	Line             int32
    48  	Routine          string
    49  }
    50  
    51  func (pe *PgError) Error() string {
    52  	return pe.Severity + ": " + pe.Message + " (SQLSTATE " + pe.Code + ")"
    53  }
    54  
    55  // SQLState returns the SQLState of the error.
    56  func (pe *PgError) SQLState() string {
    57  	return pe.Code
    58  }
    59  
    60  // ConnectError is the error returned when a connection attempt fails.
    61  type ConnectError struct {
    62  	Config *Config // The configuration that was used in the connection attempt.
    63  	msg    string
    64  	err    error
    65  }
    66  
    67  func (e *ConnectError) Error() string {
    68  	sb := &strings.Builder{}
    69  	fmt.Fprintf(sb, "failed to connect to `host=%s user=%s database=%s`: %s", e.Config.Host, e.Config.User, e.Config.Database, e.msg)
    70  	if e.err != nil {
    71  		fmt.Fprintf(sb, " (%s)", e.err.Error())
    72  	}
    73  	return sb.String()
    74  }
    75  
    76  func (e *ConnectError) Unwrap() error {
    77  	return e.err
    78  }
    79  
    80  type connLockError struct {
    81  	status string
    82  }
    83  
    84  func (e *connLockError) SafeToRetry() bool {
    85  	return true // a lock failure by definition happens before the connection is used.
    86  }
    87  
    88  func (e *connLockError) Error() string {
    89  	return e.status
    90  }
    91  
    92  // ParseConfigError is the error returned when a connection string cannot be parsed.
    93  type ParseConfigError struct {
    94  	ConnString string // The connection string that could not be parsed.
    95  	msg        string
    96  	err        error
    97  }
    98  
    99  func (e *ParseConfigError) Error() string {
   100  	// Now that ParseConfigError is public and ConnString is available to the developer, perhaps it would be better only
   101  	// return a static string. That would ensure that the error message cannot leak a password. The ConnString field would
   102  	// allow access to the original string if desired and Unwrap would allow access to the underlying error.
   103  	connString := redactPW(e.ConnString)
   104  	if e.err == nil {
   105  		return fmt.Sprintf("cannot parse `%s`: %s", connString, e.msg)
   106  	}
   107  	return fmt.Sprintf("cannot parse `%s`: %s (%s)", connString, e.msg, e.err.Error())
   108  }
   109  
   110  func (e *ParseConfigError) Unwrap() error {
   111  	return e.err
   112  }
   113  
   114  func normalizeTimeoutError(ctx context.Context, err error) error {
   115  	var netErr net.Error
   116  	if errors.As(err, &netErr) && netErr.Timeout() {
   117  		if ctx.Err() == context.Canceled {
   118  			// Since the timeout was caused by a context cancellation, the actual error is context.Canceled not the timeout error.
   119  			return context.Canceled
   120  		} else if ctx.Err() == context.DeadlineExceeded {
   121  			return &errTimeout{err: ctx.Err()}
   122  		} else {
   123  			return &errTimeout{err: netErr}
   124  		}
   125  	}
   126  	return err
   127  }
   128  
   129  type pgconnError struct {
   130  	msg         string
   131  	err         error
   132  	safeToRetry bool
   133  }
   134  
   135  func (e *pgconnError) Error() string {
   136  	if e.msg == "" {
   137  		return e.err.Error()
   138  	}
   139  	if e.err == nil {
   140  		return e.msg
   141  	}
   142  	return fmt.Sprintf("%s: %s", e.msg, e.err.Error())
   143  }
   144  
   145  func (e *pgconnError) SafeToRetry() bool {
   146  	return e.safeToRetry
   147  }
   148  
   149  func (e *pgconnError) Unwrap() error {
   150  	return e.err
   151  }
   152  
   153  // errTimeout occurs when an error was caused by a timeout. Specifically, it wraps an error which is
   154  // context.Canceled, context.DeadlineExceeded, or an implementer of net.Error where Timeout() is true.
   155  type errTimeout struct {
   156  	err error
   157  }
   158  
   159  func (e *errTimeout) Error() string {
   160  	return fmt.Sprintf("timeout: %s", e.err.Error())
   161  }
   162  
   163  func (e *errTimeout) SafeToRetry() bool {
   164  	return SafeToRetry(e.err)
   165  }
   166  
   167  func (e *errTimeout) Unwrap() error {
   168  	return e.err
   169  }
   170  
   171  type contextAlreadyDoneError struct {
   172  	err error
   173  }
   174  
   175  func (e *contextAlreadyDoneError) Error() string {
   176  	return fmt.Sprintf("context already done: %s", e.err.Error())
   177  }
   178  
   179  func (e *contextAlreadyDoneError) SafeToRetry() bool {
   180  	return true
   181  }
   182  
   183  func (e *contextAlreadyDoneError) Unwrap() error {
   184  	return e.err
   185  }
   186  
   187  // newContextAlreadyDoneError double-wraps a context error in `contextAlreadyDoneError` and `errTimeout`.
   188  func newContextAlreadyDoneError(ctx context.Context) (err error) {
   189  	return &errTimeout{&contextAlreadyDoneError{err: ctx.Err()}}
   190  }
   191  
   192  func redactPW(connString string) string {
   193  	if strings.HasPrefix(connString, "postgres://") || strings.HasPrefix(connString, "postgresql://") {
   194  		if u, err := url.Parse(connString); err == nil {
   195  			return redactURL(u)
   196  		}
   197  	}
   198  	quotedDSN := regexp.MustCompile(`password='[^']*'`)
   199  	connString = quotedDSN.ReplaceAllLiteralString(connString, "password=xxxxx")
   200  	plainDSN := regexp.MustCompile(`password=[^ ]*`)
   201  	connString = plainDSN.ReplaceAllLiteralString(connString, "password=xxxxx")
   202  	brokenURL := regexp.MustCompile(`:[^:@]+?@`)
   203  	connString = brokenURL.ReplaceAllLiteralString(connString, ":xxxxxx@")
   204  	return connString
   205  }
   206  
   207  func redactURL(u *url.URL) string {
   208  	if u == nil {
   209  		return ""
   210  	}
   211  	if _, pwSet := u.User.Password(); pwSet {
   212  		u.User = url.UserPassword(u.User.Username(), "xxxxx")
   213  	}
   214  	return u.String()
   215  }
   216  
   217  type NotPreferredError struct {
   218  	err         error
   219  	safeToRetry bool
   220  }
   221  
   222  func (e *NotPreferredError) Error() string {
   223  	return fmt.Sprintf("standby server not found: %s", e.err.Error())
   224  }
   225  
   226  func (e *NotPreferredError) SafeToRetry() bool {
   227  	return e.safeToRetry
   228  }
   229  
   230  func (e *NotPreferredError) Unwrap() error {
   231  	return e.err
   232  }