github.com/acoshift/pgsql@v0.15.3/error.go (about)

     1  package pgsql
     2  
     3  import (
     4  	"errors"
     5  	"regexp"
     6  
     7  	"github.com/lib/pq"
     8  )
     9  
    10  func contains(xs []string, x string) bool {
    11  	for _, p := range xs {
    12  		if p == x {
    13  			return true
    14  		}
    15  	}
    16  	return false
    17  }
    18  
    19  type sqlState interface {
    20  	SQLState() string
    21  }
    22  
    23  // IsErrorCode checks is error has given code
    24  func IsErrorCode(err error, code string) bool {
    25  	var sErr sqlState
    26  	if errors.As(err, &sErr) {
    27  		return sErr.SQLState() == code
    28  	}
    29  	return false
    30  }
    31  
    32  // IsErrorClass checks is error has given class
    33  func IsErrorClass(err error, class string) bool {
    34  	var pqErr *pq.Error
    35  	if errors.As(err, &pqErr) && string(pqErr.Code.Class()) == class {
    36  		return true
    37  	}
    38  	return false
    39  }
    40  
    41  // IsUniqueViolation checks is error an unique_violation with given constraint,
    42  // constraint can be empty to ignore constraint name checks
    43  func IsUniqueViolation(err error, constraint ...string) bool {
    44  	var pqErr *pq.Error
    45  	if errors.As(err, &pqErr) && pqErr.Code == "23505" {
    46  		if len(constraint) == 0 {
    47  			return true
    48  		}
    49  		return contains(constraint, extractConstraint(pqErr))
    50  	}
    51  	return false
    52  }
    53  
    54  // IsInvalidTextRepresentation checks is error an invalid_text_representation
    55  func IsInvalidTextRepresentation(err error) bool {
    56  	return IsErrorCode(err, "22P02")
    57  }
    58  
    59  // IsCharacterNotInRepertoire checks is error a character_not_in_repertoire
    60  func IsCharacterNotInRepertoire(err error) bool {
    61  	return IsErrorCode(err, "22021")
    62  }
    63  
    64  // IsForeignKeyViolation checks is error an foreign_key_violation
    65  func IsForeignKeyViolation(err error, constraint ...string) bool {
    66  	var pqErr *pq.Error
    67  	if errors.As(err, &pqErr) && pqErr.Code == "23503" {
    68  		if len(constraint) == 0 {
    69  			return true
    70  		}
    71  		return contains(constraint, extractConstraint(pqErr))
    72  	}
    73  	return false
    74  }
    75  
    76  // IsQueryCanceled checks is error an query_canceled error
    77  // (pq: canceling statement due to user request)
    78  func IsQueryCanceled(err error) bool {
    79  	return IsErrorCode(err, "57014")
    80  }
    81  
    82  // IsSerializationFailure checks is error a serialization_failure error
    83  // (pq: could not serialize access due to read/write dependencies among transactions)
    84  func IsSerializationFailure(err error) bool {
    85  	return IsErrorCode(err, "40001")
    86  }
    87  
    88  func extractConstraint(err *pq.Error) string {
    89  	if err.Constraint != "" {
    90  		return err.Constraint
    91  	}
    92  	if err.Message == "" {
    93  		return ""
    94  	}
    95  	if s := extractCRDBKey(err.Message); s != "" {
    96  		return s
    97  	}
    98  	if s := extractLastQuote(err.Message); s != "" {
    99  		return s
   100  	}
   101  	return ""
   102  }
   103  
   104  var reLastQuoteExtractor = regexp.MustCompile(`"([^"]*)"[^"]*$`)
   105  
   106  // extractLastQuote extracts last string in quote
   107  // ex. `insert or update on table "b" violates foreign key constraint "a_id_fkey"`
   108  // will return `a_id_fkey`
   109  func extractLastQuote(s string) string {
   110  	rs := reLastQuoteExtractor.FindStringSubmatch(s)
   111  	if len(rs) < 2 {
   112  		return ""
   113  	}
   114  	return rs[1]
   115  }
   116  
   117  var reCRDBKeyExtractor = regexp.MustCompile(`(\w+@\w+)[^@]*$`)
   118  
   119  // extractCRDBKey extracts key from crdb
   120  // until (https://github.com/cockroachdb/cockroach/issues/36494) resolved
   121  // ex. `foreign key violation: value ['b'] not found in a@primary [id] (txn=e3f9af56-5f73-4899-975c-4bb1de800402)`
   122  // will return `a@primary`
   123  func extractCRDBKey(s string) string {
   124  	rs := reCRDBKeyExtractor.FindStringSubmatch(s)
   125  	if len(rs) < 2 {
   126  		return ""
   127  	}
   128  	return rs[1]
   129  }