errors.go (5524B)
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 type connectError struct { 61 config *Config 62 msg string 63 err error 64 } 65 66 func (e *connectError) Error() string { 67 sb := &strings.Builder{} 68 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) 69 if e.err != nil { 70 fmt.Fprintf(sb, " (%s)", e.err.Error()) 71 } 72 return sb.String() 73 } 74 75 func (e *connectError) Unwrap() error { 76 return e.err 77 } 78 79 type connLockError struct { 80 status string 81 } 82 83 func (e *connLockError) SafeToRetry() bool { 84 return true // a lock failure by definition happens before the connection is used. 85 } 86 87 func (e *connLockError) Error() string { 88 return e.status 89 } 90 91 type parseConfigError struct { 92 connString string 93 msg string 94 err error 95 } 96 97 func (e *parseConfigError) Error() string { 98 connString := redactPW(e.connString) 99 if e.err == nil { 100 return fmt.Sprintf("cannot parse `%s`: %s", connString, e.msg) 101 } 102 return fmt.Sprintf("cannot parse `%s`: %s (%s)", connString, e.msg, e.err.Error()) 103 } 104 105 func (e *parseConfigError) Unwrap() error { 106 return e.err 107 } 108 109 func normalizeTimeoutError(ctx context.Context, err error) error { 110 if err, ok := err.(net.Error); ok && err.Timeout() { 111 if ctx.Err() == context.Canceled { 112 // Since the timeout was caused by a context cancellation, the actual error is context.Canceled not the timeout error. 113 return context.Canceled 114 } else if ctx.Err() == context.DeadlineExceeded { 115 return &errTimeout{err: ctx.Err()} 116 } else { 117 return &errTimeout{err: err} 118 } 119 } 120 return err 121 } 122 123 type pgconnError struct { 124 msg string 125 err error 126 safeToRetry bool 127 } 128 129 func (e *pgconnError) Error() string { 130 if e.msg == "" { 131 return e.err.Error() 132 } 133 if e.err == nil { 134 return e.msg 135 } 136 return fmt.Sprintf("%s: %s", e.msg, e.err.Error()) 137 } 138 139 func (e *pgconnError) SafeToRetry() bool { 140 return e.safeToRetry 141 } 142 143 func (e *pgconnError) Unwrap() error { 144 return e.err 145 } 146 147 // errTimeout occurs when an error was caused by a timeout. Specifically, it wraps an error which is 148 // context.Canceled, context.DeadlineExceeded, or an implementer of net.Error where Timeout() is true. 149 type errTimeout struct { 150 err error 151 } 152 153 func (e *errTimeout) Error() string { 154 return fmt.Sprintf("timeout: %s", e.err.Error()) 155 } 156 157 func (e *errTimeout) SafeToRetry() bool { 158 return SafeToRetry(e.err) 159 } 160 161 func (e *errTimeout) Unwrap() error { 162 return e.err 163 } 164 165 type contextAlreadyDoneError struct { 166 err error 167 } 168 169 func (e *contextAlreadyDoneError) Error() string { 170 return fmt.Sprintf("context already done: %s", e.err.Error()) 171 } 172 173 func (e *contextAlreadyDoneError) SafeToRetry() bool { 174 return true 175 } 176 177 func (e *contextAlreadyDoneError) Unwrap() error { 178 return e.err 179 } 180 181 // newContextAlreadyDoneError double-wraps a context error in `contextAlreadyDoneError` and `errTimeout`. 182 func newContextAlreadyDoneError(ctx context.Context) (err error) { 183 return &errTimeout{&contextAlreadyDoneError{err: ctx.Err()}} 184 } 185 186 func redactPW(connString string) string { 187 if strings.HasPrefix(connString, "postgres://") || strings.HasPrefix(connString, "postgresql://") { 188 if u, err := url.Parse(connString); err == nil { 189 return redactURL(u) 190 } 191 } 192 quotedDSN := regexp.MustCompile(`password='[^']*'`) 193 connString = quotedDSN.ReplaceAllLiteralString(connString, "password=xxxxx") 194 plainDSN := regexp.MustCompile(`password=[^ ]*`) 195 connString = plainDSN.ReplaceAllLiteralString(connString, "password=xxxxx") 196 brokenURL := regexp.MustCompile(`:[^:@]+?@`) 197 connString = brokenURL.ReplaceAllLiteralString(connString, ":xxxxxx@") 198 return connString 199 } 200 201 func redactURL(u *url.URL) string { 202 if u == nil { 203 return "" 204 } 205 if _, pwSet := u.User.Password(); pwSet { 206 u.User = url.UserPassword(u.User.Username(), "xxxxx") 207 } 208 return u.String() 209 } 210 211 type NotPreferredError struct { 212 err error 213 safeToRetry bool 214 } 215 216 func (e *NotPreferredError) Error() string { 217 return fmt.Sprintf("standby server not found: %s", e.err.Error()) 218 } 219 220 func (e *NotPreferredError) SafeToRetry() bool { 221 return e.safeToRetry 222 } 223 224 func (e *NotPreferredError) Unwrap() error { 225 return e.err 226 }