github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/pgwire/pgerror/pgcode.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  	"strings"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode"
    17  	"github.com/cockroachdb/errors"
    18  )
    19  
    20  // WithCandidateCode decorates the error with a candidate postgres
    21  // error code. It is called "candidate" because the code is only used
    22  // by GetPGCode() below conditionally.
    23  // The code is considered PII-free and is thus reportable.
    24  func WithCandidateCode(err error, code string) error {
    25  	if err == nil {
    26  		return nil
    27  	}
    28  
    29  	return &withCandidateCode{cause: err, code: code}
    30  }
    31  
    32  // HasCandidateCode returns tue iff the error or one of its causes
    33  // has a candidate pg error code.
    34  func HasCandidateCode(err error) bool {
    35  	return errors.HasType(err, (*withCandidateCode)(nil))
    36  }
    37  
    38  // GetPGCodeInternal retrieves a code for the error. It operates by
    39  // combining the inner (cause) code and the code at the current level,
    40  // at each level of cause.
    41  //
    42  // - at each level:
    43  //
    44  //   - if there is a candidate code at that level, that is used;
    45  //   - otherwise, it calls computeDefaultCode().
    46  //     if the function returns an empty string,
    47  //     UncategorizedError is used.
    48  //     An example implementation for computeDefaultCode is provided below.
    49  //
    50  // - after that, it combines the code computed already for the cause
    51  //   (inner) and the new code just computed at the current level (outer)
    52  //   as follows:
    53  //
    54  //   - if the outer code is uncategorized, the inner code is kept no
    55  //     matter what.
    56  //   - if the outer code has the special XX prefix, that is kept.
    57  //     (The "XX" prefix signals importance in the pg code hierarchy.)
    58  //   - if the inner code is not uncategorized, it is retained.
    59  //   - otherwise the outer code is retained.
    60  //
    61  // This function should not be used directly. It is only exported
    62  // for use in testing code. Use GetPGCode() instead.
    63  func GetPGCodeInternal(err error, computeDefaultCode func(err error) (code string)) (code string) {
    64  	code = pgcode.Uncategorized
    65  	if c, ok := err.(*withCandidateCode); ok {
    66  		code = c.code
    67  	} else if newCode := computeDefaultCode(err); newCode != "" {
    68  		code = newCode
    69  	}
    70  
    71  	if c := errors.UnwrapOnce(err); c != nil {
    72  		innerCode := GetPGCodeInternal(c, computeDefaultCode)
    73  		code = combineCodes(innerCode, code)
    74  	}
    75  
    76  	return code
    77  }
    78  
    79  // ComputeDefaultCode looks at the current error object
    80  // (not its causes) and returns:
    81  // - the existing code for Error instances
    82  // - SerializationFailure for roachpb retry errors that can be reported to clients
    83  // - StatementCompletionUnknown for ambiguous commit errors
    84  // - InternalError for assertion failures
    85  // - FeatureNotSupportedError for unimplemented errors.
    86  //
    87  // It is not meant to be used directly - it is only exported
    88  // for use by test code. Use GetPGCode() instead.
    89  func ComputeDefaultCode(err error) string {
    90  	switch e := err.(type) {
    91  	// If there was already a pgcode in the cause, use that.
    92  	case *Error:
    93  		return e.Code
    94  	// Special roachpb errors get a special code.
    95  	case ClientVisibleRetryError:
    96  		return pgcode.SerializationFailure
    97  	case ClientVisibleAmbiguousError:
    98  		return pgcode.StatementCompletionUnknown
    99  	}
   100  
   101  	if errors.IsAssertionFailure(err) {
   102  		return pgcode.Internal
   103  	}
   104  	if errors.IsUnimplementedError(err) {
   105  		return pgcode.FeatureNotSupported
   106  	}
   107  	return ""
   108  }
   109  
   110  // ClientVisibleRetryError mirrors roachpb.ClientVisibleRetryError but
   111  // is defined here to avoid an import cycle.
   112  type ClientVisibleRetryError interface {
   113  	ClientVisibleRetryError()
   114  }
   115  
   116  // ClientVisibleAmbiguousError mirrors
   117  // roachpb.ClientVisibleAmbiguousError but is defined here to avoid an
   118  // import cycle.
   119  type ClientVisibleAmbiguousError interface {
   120  	ClientVisibleAmbiguousError()
   121  }
   122  
   123  // combineCodes combines the inner and outer codes.
   124  func combineCodes(innerCode, outerCode string) string {
   125  	if outerCode == pgcode.Uncategorized {
   126  		return innerCode
   127  	}
   128  	if strings.HasPrefix(outerCode, "XX") {
   129  		return outerCode
   130  	}
   131  	if innerCode != pgcode.Uncategorized {
   132  		return innerCode
   133  	}
   134  	return outerCode
   135  }