taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

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 }