Files
dbx/errors.go

95 lines
2.1 KiB
Go

package dbx
import (
"errors"
"fmt"
"strings"
"github.com/jackc/pgx/v5/pgconn"
)
// Sentinel errors.
var (
ErrNoHealthyNode = errors.New("dbx: no healthy node available")
ErrClusterClosed = errors.New("dbx: cluster is closed")
ErrRetryExhausted = errors.New("dbx: retry attempts exhausted")
)
// IsRetryable reports whether the error is worth retrying.
// Connection errors, serialization failures, deadlocks, and too_many_connections are retryable.
func IsRetryable(err error) bool {
if err == nil {
return false
}
if IsConnectionError(err) {
return true
}
code := PgErrorCode(err)
switch code {
case "40001", // serialization_failure
"40P01", // deadlock_detected
"53300": // too_many_connections
return true
}
return false
}
// IsConnectionError reports whether the error indicates a connection problem (PG class 08).
func IsConnectionError(err error) bool {
if err == nil {
return false
}
code := PgErrorCode(err)
if strings.HasPrefix(code, "08") {
return true
}
// pgx wraps connection errors that may not have a PG code
msg := err.Error()
for _, s := range []string{
"connection refused",
"connection reset",
"broken pipe",
"EOF",
"no connection",
"conn closed",
"timeout",
} {
if strings.Contains(msg, s) {
return true
}
}
return false
}
// IsConstraintViolation reports whether the error is a constraint violation (PG class 23).
func IsConstraintViolation(err error) bool {
return strings.HasPrefix(PgErrorCode(err), "23")
}
// PgErrorCode extracts the PostgreSQL error code from err, or returns "" if not a PG error.
func PgErrorCode(err error) string {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
return pgErr.Code
}
return ""
}
// retryError wraps the last error with ErrRetryExhausted context.
type retryError struct {
attempts int
last error
}
func (e *retryError) Error() string {
return fmt.Sprintf("%s: %d attempts, last error: %v", ErrRetryExhausted, e.attempts, e.last)
}
func (e *retryError) Unwrap() []error {
return []error{ErrRetryExhausted, e.last}
}
func newRetryError(attempts int, last error) error {
return &retryError{attempts: attempts, last: last}
}