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