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 }