error.go (8419B)
1 package pq 2 3 import ( 4 "database/sql/driver" 5 "fmt" 6 "io" 7 "net" 8 "runtime" 9 "strconv" 10 "strings" 11 "unicode/utf8" 12 13 "github.com/lib/pq/pqerror" 14 ) 15 16 // Error returned by the PostgreSQL server. 17 // 18 // The [Error] method returns the error message and error code: 19 // 20 // pq: invalid input syntax for type json (22P02) 21 // 22 // The [ErrorWithDetail] method also includes the error Detail, Hint, and 23 // location context (if any): 24 // 25 // ERROR: invalid input syntax for type json (22P02) 26 // DETAIL: Token "asd" is invalid. 27 // CONTEXT: line 5, column 8: 28 // 29 // 3 | 'def', 30 // 4 | 123, 31 // 5 | 'foo', 'asd'::jsonb 32 // ^ 33 type Error struct { 34 // [Efatal], [Epanic], [Ewarning], [Enotice], [Edebug], [Einfo], or [Elog]. 35 // Always present. 36 Severity string 37 38 // SQLSTATE code. Always present. 39 Code pqerror.Code 40 41 // Primary human-readable error message. This should be accurate but terse 42 // (typically one line). Always present. 43 Message string 44 45 // Optional secondary error message carrying more detail about the problem. 46 // Might run to multiple lines. 47 Detail string 48 49 // Optional suggestion what to do about the problem. This is intended to 50 // differ from Detail in that it offers advice (potentially inappropriate) 51 // rather than hard facts. Might run to multiple lines. 52 Hint string 53 54 // error position as an index into the original query string, as decimal 55 // ASCII integer. The first character has index 1, and positions are 56 // measured in characters not bytes. 57 Position string 58 59 // This is defined the same as the Position field, but it is used when the 60 // cursor position refers to an internally generated command rather than the 61 // one submitted by the client. The InternalQuery field will always appear 62 // when this field appears. 63 InternalPosition string 64 65 // Text of a failed internally-generated command. This could be, for 66 // example, an SQL query issued by a PL/pgSQL function. 67 InternalQuery string 68 69 // An indication of the context in which the error occurred. Presently this 70 // includes a call stack traceback of active procedural language functions 71 // and internally-generated queries. The trace is one entry per line, most 72 // recent first. 73 Where string 74 75 // If the error was associated with a specific database object, the name of 76 // the schema containing that object, if any. 77 Schema string 78 79 // If the error was associated with a specific table, the name of the table. 80 // (Refer to the schema name field for the name of the table's schema.) 81 Table string 82 83 // If the error was associated with a specific table column, the name of the 84 // column. (Refer to the schema and table name fields to identify the 85 // table.) 86 Column string 87 88 // If the error was associated with a specific data type, the name of the 89 // data type. (Refer to the schema name field for the name of the data 90 // type's schema.) 91 DataTypeName string 92 93 // If the error was associated with a specific constraint, the name of the 94 // constraint. Refer to fields listed above for the associated table or 95 // domain. (For this purpose, indexes are treated as constraints, even if 96 // they weren't created with constraint syntax.) 97 Constraint string 98 99 // File name of the source-code location where the error was reported. 100 File string 101 102 // Line number of the source-code location where the error was reported. 103 Line string 104 105 // Name of the source-code routine reporting the error. 106 Routine string 107 108 query string 109 } 110 111 type ( 112 // ErrorCode is a five-character error code. 113 // 114 // Deprecated: use pqerror.Code 115 // 116 //go:fix inline 117 ErrorCode = pqerror.Code 118 119 // ErrorClass is only the class part of an error code. 120 // 121 // Deprecated: use pqerror.Class 122 // 123 //go:fix inline 124 ErrorClass = pqerror.Class 125 ) 126 127 func parseError(r *readBuf, q string) *Error { 128 err := &Error{query: q} 129 for t := r.byte(); t != 0; t = r.byte() { 130 msg := r.string() 131 switch t { 132 case 'S': 133 err.Severity = msg 134 case 'C': 135 err.Code = pqerror.Code(msg) 136 case 'M': 137 err.Message = msg 138 case 'D': 139 err.Detail = msg 140 case 'H': 141 err.Hint = msg 142 case 'P': 143 err.Position = msg 144 case 'p': 145 err.InternalPosition = msg 146 case 'q': 147 err.InternalQuery = msg 148 case 'W': 149 err.Where = msg 150 case 's': 151 err.Schema = msg 152 case 't': 153 err.Table = msg 154 case 'c': 155 err.Column = msg 156 case 'd': 157 err.DataTypeName = msg 158 case 'n': 159 err.Constraint = msg 160 case 'F': 161 err.File = msg 162 case 'L': 163 err.Line = msg 164 case 'R': 165 err.Routine = msg 166 } 167 } 168 return err 169 } 170 171 // Fatal returns true if the Error Severity is fatal. 172 func (e *Error) Fatal() bool { return e.Severity == pqerror.SeverityFatal } 173 174 // SQLState returns the SQLState of the error. 175 func (e *Error) SQLState() string { return string(e.Code) } 176 177 func (e *Error) Error() string { 178 msg := e.Message 179 if e.query != "" && e.Position != "" { 180 pos, err := strconv.Atoi(e.Position) 181 if err == nil { 182 lines := strings.Split(e.query, "\n") 183 line, col := posToLine(pos, lines) 184 if len(lines) == 1 { 185 msg += " at column " + strconv.Itoa(col) 186 } else { 187 msg += " at position " + strconv.Itoa(line) + ":" + strconv.Itoa(col) 188 } 189 } 190 } 191 192 if e.Code != "" { 193 return "pq: " + msg + " (" + string(e.Code) + ")" 194 } 195 return "pq: " + msg 196 } 197 198 // ErrorWithDetail returns the error message with detailed information and 199 // location context (if any). 200 // 201 // See the documentation on [Error]. 202 func (e *Error) ErrorWithDetail() string { 203 b := new(strings.Builder) 204 b.Grow(len(e.Message) + len(e.Detail) + len(e.Hint) + 30) 205 b.WriteString("ERROR: ") 206 b.WriteString(e.Message) 207 if e.Code != "" { 208 b.WriteString(" (") 209 b.WriteString(string(e.Code)) 210 b.WriteByte(')') 211 } 212 if e.Detail != "" { 213 b.WriteString("\nDETAIL: ") 214 b.WriteString(e.Detail) 215 } 216 if e.Hint != "" { 217 b.WriteString("\nHINT: ") 218 b.WriteString(e.Hint) 219 } 220 221 if e.query != "" && e.Position != "" { 222 b.Grow(512) 223 pos, err := strconv.Atoi(e.Position) 224 if err != nil { 225 return b.String() 226 } 227 lines := strings.Split(e.query, "\n") 228 line, col := posToLine(pos, lines) 229 230 fmt.Fprintf(b, "\nCONTEXT: line %d, column %d:\n\n", line, col) 231 if line > 2 { 232 fmt.Fprintf(b, "% 7d | %s\n", line-2, expandTab(lines[line-3])) 233 } 234 if line > 1 { 235 fmt.Fprintf(b, "% 7d | %s\n", line-1, expandTab(lines[line-2])) 236 } 237 /// Expand tabs, so that the ^ is at at the correct position, but leave 238 /// "column 10-13" intact. Adjusting this to the visual column would be 239 /// better, but we don't know the tabsize of the user in their editor, 240 /// which can be 8, 4, 2, or something else. We can't know. So leaving 241 /// it as the character index is probably the "most correct". 242 expanded := expandTab(lines[line-1]) 243 diff := len(expanded) - len(lines[line-1]) 244 fmt.Fprintf(b, "% 7d | %s\n", line, expanded) 245 fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col-1+diff), "^") 246 } 247 248 return b.String() 249 } 250 251 func posToLine(pos int, lines []string) (line, col int) { 252 read := 0 253 for i := range lines { 254 line++ 255 ll := utf8.RuneCountInString(lines[i]) + 1 // +1 for the removed newline 256 if read+ll >= pos { 257 col = max(pos-read, 1) // Should be lower than 1, but just in case. 258 break 259 } 260 read += ll 261 } 262 return line, col 263 } 264 265 func expandTab(s string) string { 266 var ( 267 b strings.Builder 268 l int 269 fill = func(n int) string { 270 b := make([]byte, n) 271 for i := range b { 272 b[i] = ' ' 273 } 274 return string(b) 275 } 276 ) 277 b.Grow(len(s)) 278 for _, r := range s { 279 switch r { 280 case '\t': 281 tw := 8 - l%8 282 b.WriteString(fill(tw)) 283 l += tw 284 default: 285 b.WriteRune(r) 286 l += 1 287 } 288 } 289 return b.String() 290 } 291 292 func (cn *conn) handleError(reported error, query ...string) error { 293 switch err := reported.(type) { 294 case nil: 295 return nil 296 case runtime.Error, *net.OpError: 297 cn.err.set(driver.ErrBadConn) 298 case *safeRetryError: 299 cn.err.set(driver.ErrBadConn) 300 reported = driver.ErrBadConn 301 case *Error: 302 if len(query) > 0 && query[0] != "" { 303 err.query = query[0] 304 reported = err 305 } 306 if err.Fatal() { 307 reported = driver.ErrBadConn 308 } 309 case error: 310 if err == io.EOF || err == io.ErrUnexpectedEOF || err.Error() == "remote error: handshake failure" { 311 reported = driver.ErrBadConn 312 } 313 default: 314 cn.err.set(driver.ErrBadConn) 315 reported = fmt.Errorf("pq: unknown error %T: %[1]s", err) 316 } 317 318 // Any time we return ErrBadConn, we need to remember it since *Tx doesn't 319 // mark the connection bad in database/sql. 320 if reported == driver.ErrBadConn { 321 cn.err.set(driver.ErrBadConn) 322 } 323 return reported 324 }