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 } type connectError struct { config *Config 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 } type parseConfigError struct { connString string msg string err error } func (e *parseConfigError) Error() string { 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 { if err, ok := err.(net.Error); ok && err.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: err} } } 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 }