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  }