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  }