233 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package pgconn
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net"
 | 
						|
	"net/url"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// SafeToRetry checks if the err is guaranteed to have occurred before sending any data to the server.
 | 
						|
func SafeToRetry(err error) bool {
 | 
						|
	if e, ok := err.(interface{ SafeToRetry() bool }); ok {
 | 
						|
		return e.SafeToRetry()
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// Timeout checks if err was was caused by a timeout. To be specific, it is true if err was caused within pgconn by a
 | 
						|
// context.DeadlineExceeded or an implementer of net.Error where Timeout() is true.
 | 
						|
func Timeout(err error) bool {
 | 
						|
	var timeoutErr *errTimeout
 | 
						|
	return errors.As(err, &timeoutErr)
 | 
						|
}
 | 
						|
 | 
						|
// PgError represents an error reported by the PostgreSQL server. See
 | 
						|
// http://www.postgresql.org/docs/11/static/protocol-error-fields.html for
 | 
						|
// detailed field description.
 | 
						|
type PgError struct {
 | 
						|
	Severity         string
 | 
						|
	Code             string
 | 
						|
	Message          string
 | 
						|
	Detail           string
 | 
						|
	Hint             string
 | 
						|
	Position         int32
 | 
						|
	InternalPosition int32
 | 
						|
	InternalQuery    string
 | 
						|
	Where            string
 | 
						|
	SchemaName       string
 | 
						|
	TableName        string
 | 
						|
	ColumnName       string
 | 
						|
	DataTypeName     string
 | 
						|
	ConstraintName   string
 | 
						|
	File             string
 | 
						|
	Line             int32
 | 
						|
	Routine          string
 | 
						|
}
 | 
						|
 | 
						|
func (pe *PgError) Error() string {
 | 
						|
	return pe.Severity + ": " + pe.Message + " (SQLSTATE " + pe.Code + ")"
 | 
						|
}
 | 
						|
 | 
						|
// SQLState returns the SQLState of the error.
 | 
						|
func (pe *PgError) SQLState() string {
 | 
						|
	return pe.Code
 | 
						|
}
 | 
						|
 | 
						|
// ConnectError is the error returned when a connection attempt fails.
 | 
						|
type ConnectError struct {
 | 
						|
	Config *Config // The configuration that was used in the connection attempt.
 | 
						|
	msg    string
 | 
						|
	err    error
 | 
						|
}
 | 
						|
 | 
						|
func (e *ConnectError) Error() string {
 | 
						|
	sb := &strings.Builder{}
 | 
						|
	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)
 | 
						|
	if e.err != nil {
 | 
						|
		fmt.Fprintf(sb, " (%s)", e.err.Error())
 | 
						|
	}
 | 
						|
	return sb.String()
 | 
						|
}
 | 
						|
 | 
						|
func (e *ConnectError) Unwrap() error {
 | 
						|
	return e.err
 | 
						|
}
 | 
						|
 | 
						|
type connLockError struct {
 | 
						|
	status string
 | 
						|
}
 | 
						|
 | 
						|
func (e *connLockError) SafeToRetry() bool {
 | 
						|
	return true // a lock failure by definition happens before the connection is used.
 | 
						|
}
 | 
						|
 | 
						|
func (e *connLockError) Error() string {
 | 
						|
	return e.status
 | 
						|
}
 | 
						|
 | 
						|
// ParseConfigError is the error returned when a connection string cannot be parsed.
 | 
						|
type ParseConfigError struct {
 | 
						|
	ConnString string // The connection string that could not be parsed.
 | 
						|
	msg        string
 | 
						|
	err        error
 | 
						|
}
 | 
						|
 | 
						|
func (e *ParseConfigError) Error() string {
 | 
						|
	// Now that ParseConfigError is public and ConnString is available to the developer, perhaps it would be better only
 | 
						|
	// return a static string. That would ensure that the error message cannot leak a password. The ConnString field would
 | 
						|
	// allow access to the original string if desired and Unwrap would allow access to the underlying error.
 | 
						|
	connString := redactPW(e.ConnString)
 | 
						|
	if e.err == nil {
 | 
						|
		return fmt.Sprintf("cannot parse `%s`: %s", connString, e.msg)
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("cannot parse `%s`: %s (%s)", connString, e.msg, e.err.Error())
 | 
						|
}
 | 
						|
 | 
						|
func (e *ParseConfigError) Unwrap() error {
 | 
						|
	return e.err
 | 
						|
}
 | 
						|
 | 
						|
func normalizeTimeoutError(ctx context.Context, err error) error {
 | 
						|
	var netErr net.Error
 | 
						|
	if errors.As(err, &netErr) && netErr.Timeout() {
 | 
						|
		if ctx.Err() == context.Canceled {
 | 
						|
			// Since the timeout was caused by a context cancellation, the actual error is context.Canceled not the timeout error.
 | 
						|
			return context.Canceled
 | 
						|
		} else if ctx.Err() == context.DeadlineExceeded {
 | 
						|
			return &errTimeout{err: ctx.Err()}
 | 
						|
		} else {
 | 
						|
			return &errTimeout{err: netErr}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
type pgconnError struct {
 | 
						|
	msg         string
 | 
						|
	err         error
 | 
						|
	safeToRetry bool
 | 
						|
}
 | 
						|
 | 
						|
func (e *pgconnError) Error() string {
 | 
						|
	if e.msg == "" {
 | 
						|
		return e.err.Error()
 | 
						|
	}
 | 
						|
	if e.err == nil {
 | 
						|
		return e.msg
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%s: %s", e.msg, e.err.Error())
 | 
						|
}
 | 
						|
 | 
						|
func (e *pgconnError) SafeToRetry() bool {
 | 
						|
	return e.safeToRetry
 | 
						|
}
 | 
						|
 | 
						|
func (e *pgconnError) Unwrap() error {
 | 
						|
	return e.err
 | 
						|
}
 | 
						|
 | 
						|
// errTimeout occurs when an error was caused by a timeout. Specifically, it wraps an error which is
 | 
						|
// context.Canceled, context.DeadlineExceeded, or an implementer of net.Error where Timeout() is true.
 | 
						|
type errTimeout struct {
 | 
						|
	err error
 | 
						|
}
 | 
						|
 | 
						|
func (e *errTimeout) Error() string {
 | 
						|
	return fmt.Sprintf("timeout: %s", e.err.Error())
 | 
						|
}
 | 
						|
 | 
						|
func (e *errTimeout) SafeToRetry() bool {
 | 
						|
	return SafeToRetry(e.err)
 | 
						|
}
 | 
						|
 | 
						|
func (e *errTimeout) Unwrap() error {
 | 
						|
	return e.err
 | 
						|
}
 | 
						|
 | 
						|
type contextAlreadyDoneError struct {
 | 
						|
	err error
 | 
						|
}
 | 
						|
 | 
						|
func (e *contextAlreadyDoneError) Error() string {
 | 
						|
	return fmt.Sprintf("context already done: %s", e.err.Error())
 | 
						|
}
 | 
						|
 | 
						|
func (e *contextAlreadyDoneError) SafeToRetry() bool {
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func (e *contextAlreadyDoneError) Unwrap() error {
 | 
						|
	return e.err
 | 
						|
}
 | 
						|
 | 
						|
// newContextAlreadyDoneError double-wraps a context error in `contextAlreadyDoneError` and `errTimeout`.
 | 
						|
func newContextAlreadyDoneError(ctx context.Context) (err error) {
 | 
						|
	return &errTimeout{&contextAlreadyDoneError{err: ctx.Err()}}
 | 
						|
}
 | 
						|
 | 
						|
func redactPW(connString string) string {
 | 
						|
	if strings.HasPrefix(connString, "postgres://") || strings.HasPrefix(connString, "postgresql://") {
 | 
						|
		if u, err := url.Parse(connString); err == nil {
 | 
						|
			return redactURL(u)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	quotedDSN := regexp.MustCompile(`password='[^']*'`)
 | 
						|
	connString = quotedDSN.ReplaceAllLiteralString(connString, "password=xxxxx")
 | 
						|
	plainDSN := regexp.MustCompile(`password=[^ ]*`)
 | 
						|
	connString = plainDSN.ReplaceAllLiteralString(connString, "password=xxxxx")
 | 
						|
	brokenURL := regexp.MustCompile(`:[^:@]+?@`)
 | 
						|
	connString = brokenURL.ReplaceAllLiteralString(connString, ":xxxxxx@")
 | 
						|
	return connString
 | 
						|
}
 | 
						|
 | 
						|
func redactURL(u *url.URL) string {
 | 
						|
	if u == nil {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	if _, pwSet := u.User.Password(); pwSet {
 | 
						|
		u.User = url.UserPassword(u.User.Username(), "xxxxx")
 | 
						|
	}
 | 
						|
	return u.String()
 | 
						|
}
 | 
						|
 | 
						|
type NotPreferredError struct {
 | 
						|
	err         error
 | 
						|
	safeToRetry bool
 | 
						|
}
 | 
						|
 | 
						|
func (e *NotPreferredError) Error() string {
 | 
						|
	return fmt.Sprintf("standby server not found: %s", e.err.Error())
 | 
						|
}
 | 
						|
 | 
						|
func (e *NotPreferredError) SafeToRetry() bool {
 | 
						|
	return e.safeToRetry
 | 
						|
}
 | 
						|
 | 
						|
func (e *NotPreferredError) Unwrap() error {
 | 
						|
	return e.err
 | 
						|
}
 |