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 }