github.com/cockroachdb/cockroachdb-parser@v0.23.3-0.20240213214944-911057d40c9a/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/cockroachdb-parser/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 pgcode.Code) error {
    25  	if err == nil {
    26  		return nil
    27  	}
    28  	return &withCandidateCode{cause: err, code: code.String()}
    29  }
    30  
    31  // HasCandidateCode returns tue iff the error or one of its causes
    32  // has a candidate pg error code.
    33  func HasCandidateCode(err error) bool {
    34  	return errors.HasType(err, (*withCandidateCode)(nil))
    35  }
    36  
    37  // GetPGCodeInternal retrieves a code for the error. It operates by
    38  // combining the inner (cause) code and the code at the current level,
    39  // at each level of cause.
    40  //
    41  // - at each level:
    42  //
    43  //   - if there is a candidate code at that level, that is used;
    44  //
    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  //
    57  //   - if the outer code has the special XX prefix, that is kept.
    58  //     (The "XX" prefix signals importance in the pg code hierarchy.)
    59  //
    60  //   - if the inner code is not uncategorized, it is retained.
    61  //
    62  //   - otherwise the outer code is retained.
    63  //
    64  // This function should not be used directly. It is only exported
    65  // for use in testing code. Use GetPGCode() instead.
    66  func GetPGCodeInternal(
    67  	err error, computeDefaultCode func(err error) (code pgcode.Code),
    68  ) (code pgcode.Code) {
    69  	code = pgcode.Uncategorized
    70  	if c, ok := err.(*withCandidateCode); ok {
    71  		code = pgcode.MakeCode(c.code)
    72  	} else if newCode := computeDefaultCode(err); newCode.String() != "" {
    73  		code = newCode
    74  	}
    75  
    76  	if c := errors.UnwrapOnce(err); c != nil {
    77  		innerCode := GetPGCodeInternal(c, computeDefaultCode)
    78  		code = combineCodes(innerCode, code)
    79  	}
    80  
    81  	return code
    82  }
    83  
    84  // ComputeDefaultCode looks at the current error object
    85  // (not its causes) and returns:
    86  // - the existing code for Error instances
    87  // - SerializationFailure for roachpb retry errors that can be reported to clients
    88  // - StatementCompletionUnknown for ambiguous commit errors
    89  // - InternalError for assertion failures
    90  // - FeatureNotSupportedError for unimplemented errors.
    91  //
    92  // It is not meant to be used directly - it is only exported
    93  // for use by test code. Use GetPGCode() instead.
    94  func ComputeDefaultCode(err error) pgcode.Code {
    95  	switch e := err.(type) {
    96  	// If there was already a pgcode in the cause, use that.
    97  	case *Error:
    98  		return pgcode.MakeCode(e.Code)
    99  	// Special roachpb errors get a special code.
   100  	case ClientVisibleRetryError:
   101  		return pgcode.SerializationFailure
   102  	case ClientVisibleAmbiguousError:
   103  		return pgcode.StatementCompletionUnknown
   104  	}
   105  
   106  	if errors.IsAssertionFailure(err) {
   107  		return pgcode.Internal
   108  	}
   109  	if errors.IsUnimplementedError(err) {
   110  		return pgcode.FeatureNotSupported
   111  	}
   112  	return pgcode.Code{}
   113  }
   114  
   115  // ClientVisibleRetryError mirrors kvpb.ClientVisibleRetryError but
   116  // is defined here to avoid an import cycle.
   117  type ClientVisibleRetryError interface {
   118  	ClientVisibleRetryError()
   119  }
   120  
   121  // ClientVisibleAmbiguousError mirrors
   122  // kvpb.ClientVisibleAmbiguousError but is defined here to avoid an
   123  // import cycle.
   124  type ClientVisibleAmbiguousError interface {
   125  	ClientVisibleAmbiguousError()
   126  }
   127  
   128  // combineCodes combines the inner and outer codes.
   129  func combineCodes(innerCode, outerCode pgcode.Code) pgcode.Code {
   130  	if outerCode == pgcode.Uncategorized {
   131  		return innerCode
   132  	}
   133  	if strings.HasPrefix(outerCode.String(), "XX") {
   134  		return outerCode
   135  	}
   136  	if innerCode != pgcode.Uncategorized {
   137  		return innerCode
   138  	}
   139  	return outerCode
   140  }