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} }