github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/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 "regexp" 17 "strings" 18 19 "github.com/cockroachdb/cockroachdb-parser/pkg/docs" 20 "github.com/cockroachdb/cockroachdb-parser/pkg/sql/pgwire/pgcode" 21 "github.com/cockroachdb/errors" 22 ) 23 24 // Flatten turns any error into a pgerror with fields populated. As 25 // the name implies, the details from the chain of causes is projected 26 // into a single struct. This is useful in at least two places: 27 // 28 // - to generate Error objects suitable for 19.1 nodes, which 29 // only recognize this type of payload. 30 // - to generate an error packet on pgwire. 31 // 32 // Additionally, this can be used in the remainder of the code 33 // base when an Error object is expected, until that code 34 // is updated to use the errors library directly. 35 // 36 // Flatten() returns a nil ptr if err was nil to start with. 37 func Flatten(err error) *Error { 38 if err == nil { 39 return nil 40 } 41 resErr := &Error{ 42 Code: GetPGCode(err).String(), 43 Message: err.Error(), 44 Severity: GetSeverity(err), 45 ConstraintName: GetConstraintName(err), 46 } 47 48 // Populate the source field if available. 49 if file, line, fn, ok := errors.GetOneLineSource(err); ok { 50 resErr.Source = &Error_Source{File: file, Line: int32(line), Function: fn} 51 } 52 53 // Add serialization failure hints if available. 54 if resErr.Code == pgcode.SerializationFailure.String() { 55 err = withSerializationFailureHints(err) 56 } 57 58 // Populate the details and hints. 59 resErr.Hint = errors.FlattenHints(err) 60 resErr.Detail = errors.FlattenDetails(err) 61 62 // Add a useful error prefix if not already there. 63 switch resErr.Code { 64 case pgcode.Internal.String(): 65 // The string "internal error" clarifies the nature of the error 66 // to users, and is also introduced for compatibility with 67 // previous CockroachDB versions. 68 if !strings.HasPrefix(resErr.Message, InternalErrorPrefix) { 69 resErr.Message = InternalErrorPrefix + ": " + resErr.Message 70 } 71 72 // If the error flows towards a human user and does not get 73 // sent via telemetry, we want to empower the user to 74 // file a moderately useful error report. For this purpose, 75 // append the innermost stack trace. 76 resErr.Detail += getInnerMostStackTraceAsDetail(err) 77 78 case pgcode.SerializationFailure.String(): 79 // The string "restart transaction" is asserted by test code. This 80 // can be changed if/when test code learns to use the 40001 code 81 // (or the errors library) instead. 82 // 83 // TODO(knz): investigate whether 3rd party frameworks parse this 84 // string instead of using the pg code to determine whether to 85 // retry. 86 if !strings.HasPrefix(resErr.Message, TxnRetryMsgPrefix) { 87 resErr.Message = TxnRetryMsgPrefix + ": " + resErr.Message 88 } 89 } 90 91 return resErr 92 } 93 94 // serializationFailureReasonRegexp captures known failure reasons for 95 // the serialization failure error messages. 96 // We cannot use kvpb.TransactionRetryReason or kvpb.TransactionAbortedReason 97 // as this introduces a circular dependency. 98 var serializationFailureReasonRegexp = regexp.MustCompile( 99 `((?:ABORT_|RETRY_)[A-Z_]*|ReadWithinUncertaintyInterval)`, 100 ) 101 102 // withSerializationFailureHints appends a doc URL that contains information for 103 // commonly seen error messages. 104 func withSerializationFailureHints(err error) error { 105 url := docs.URL("transaction-retry-error-reference.html") 106 if match := serializationFailureReasonRegexp.FindStringSubmatch(err.Error()); len(match) >= 2 { 107 url += "#" + strings.ToLower(match[1]) 108 } 109 return errors.WithIssueLink(err, errors.IssueLink{IssueURL: url}) 110 } 111 112 func getInnerMostStackTraceAsDetail(err error) string { 113 if c := errors.UnwrapOnce(err); c != nil { 114 s := getInnerMostStackTraceAsDetail(c) 115 if s != "" { 116 return s 117 } 118 } 119 // Fall through: there is no stack trace so far. 120 if st := errors.GetReportableStackTrace(err); st != nil { 121 var t bytes.Buffer 122 t.WriteString("stack trace:\n") 123 for i := len(st.Frames) - 1; i >= 0; i-- { 124 f := st.Frames[i] 125 fmt.Fprintf(&t, "%s:%d: %s()\n", f.Filename, f.Lineno, f.Function) 126 } 127 return t.String() 128 } 129 return "" 130 } 131 132 // InternalErrorPrefix is prepended on internal errors. 133 const InternalErrorPrefix = "internal error" 134 135 // TxnRetryMsgPrefix is the prefix inserted in an error message when flattened 136 const TxnRetryMsgPrefix = "restart transaction" 137 138 // GetPGCode retrieves the error code for an error. 139 func GetPGCode(err error) pgcode.Code { 140 return GetPGCodeInternal(err, ComputeDefaultCode) 141 }