github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/pgwire/pgerror/flatten.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package pgerror 12 13 import ( 14 "bytes" 15 "fmt" 16 "strings" 17 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 19 "github.com/cockroachdb/errors" 20 ) 21 22 // Flatten turns any error into a pgerror with fields populated. As 23 // the name implies, the details from the chain of causes is projected 24 // into a single struct. This is useful in at least two places: 25 // 26 // - to generate Error objects suitable for 19.1 nodes, which 27 // only recognize this type of payload. 28 // - to generate an error packet on pgwire. 29 // 30 // Additionally, this can be used in the remainder of the code 31 // base when an Error object is expected, until that code 32 // is updated to use the errors library directly. 33 // 34 // Flatten() returns a nil ptr if err was nil to start with. 35 func Flatten(err error) *Error { 36 if err == nil { 37 return nil 38 } 39 resErr := &Error{ 40 Code: GetPGCode(err), 41 Message: err.Error(), 42 Severity: GetSeverity(err), 43 } 44 45 // Populate the source field if available. 46 if file, line, fn, ok := errors.GetOneLineSource(err); ok { 47 resErr.Source = &Error_Source{File: file, Line: int32(line), Function: fn} 48 } 49 50 // Populate the details and hints. 51 resErr.Hint = errors.FlattenHints(err) 52 resErr.Detail = errors.FlattenDetails(err) 53 54 // Add a useful error prefix if not already there. 55 switch resErr.Code { 56 case pgcode.Internal: 57 // The string "internal error" clarifies the nature of the error 58 // to users, and is also introduced for compatibility with 59 // previous CockroachDB versions. 60 if !strings.HasPrefix(resErr.Message, InternalErrorPrefix) { 61 resErr.Message = InternalErrorPrefix + ": " + resErr.Message 62 } 63 64 // If the error flows towards a human user and does not get 65 // sent via telemetry, we want to empower the user to 66 // file a moderately useful error report. For this purpose, 67 // append the innermost stack trace. 68 resErr.Detail += getInnerMostStackTraceAsDetail(err) 69 70 case pgcode.SerializationFailure: 71 // The string "restart transaction" is asserted by test code. This 72 // can be changed if/when test code learns to use the 40001 code 73 // (or the errors library) instead. 74 // 75 // TODO(knz): investigate whether 3rd party frameworks parse this 76 // string instead of using the pg code to determine whether to 77 // retry. 78 if !strings.HasPrefix(resErr.Message, TxnRetryMsgPrefix) { 79 resErr.Message = TxnRetryMsgPrefix + ": " + resErr.Message 80 } 81 } 82 83 return resErr 84 } 85 86 func getInnerMostStackTraceAsDetail(err error) string { 87 if c := errors.UnwrapOnce(err); c != nil { 88 s := getInnerMostStackTraceAsDetail(c) 89 if s != "" { 90 return s 91 } 92 } 93 // Fall through: there is no stack trace so far. 94 if st := errors.GetReportableStackTrace(err); st != nil { 95 var t bytes.Buffer 96 t.WriteString("stack trace:\n") 97 for i := len(st.Frames) - 1; i >= 0; i-- { 98 f := st.Frames[i] 99 fmt.Fprintf(&t, "%s:%d: %s()\n", f.Filename, f.Lineno, f.Function) 100 } 101 return t.String() 102 } 103 return "" 104 } 105 106 // InternalErrorPrefix is prepended on internal errors. 107 const InternalErrorPrefix = "internal error" 108 109 // TxnRetryMsgPrefix is the prefix inserted in an error message when flattened 110 const TxnRetryMsgPrefix = "restart transaction" 111 112 // GetPGCode retrieves the error code for an error. 113 func GetPGCode(err error) string { 114 return GetPGCodeInternal(err, ComputeDefaultCode) 115 }