1 package sql
2
3 import (
4 "database/sql/driver"
5 "encoding/json"
6 "errors"
7 "strings"
8
9 "github.com/jackc/pgconn"
10 "github.com/jackc/pgx/v4"
11 "github.com/lib/pq"
12
13 "edge-infra.dev/pkg/edge/api/sql/utils"
14 )
15
16 const (
17 EdgeSQLErrType = "EDGE_SQL_STATE"
18 )
19
20 var (
21 sqlErrorMapping = map[error]string{
22 pgx.ErrNoRows: "No results found for query",
23 pgx.ErrTxClosed: "The transaction was closed",
24 pgx.ErrTxCommitRollback: "Commit resulted in transaction rollback",
25 driver.ErrBadConn: "Bad SQL connection",
26 }
27 pgErrorMappings = map[pq.ErrorCode]ErrorAndMsg{
28 "22P02": {
29 Error: "invalid_text_representation",
30 Message: "Invalid input syntax for type",
31 },
32 "57P04": {
33 Error: "database_dropped",
34 Message: "Database has been dropped",
35 },
36 "58000": {
37 Error: "system_error",
38 Message: "Internal System Error",
39 },
40 "P0002": {
41 Error: "no_data_found",
42 Message: "No results found for query",
43 },
44 "XX000": {
45 Error: "internal_error",
46 Message: "Internal System Error",
47 },
48 "40000": {
49 Error: "transaction_rollback",
50 Message: "Transaction has been rolled back",
51 },
52 }
53 specialCases = []SpecialCase{
54 {
55 Matcher: "destination arguments in Scan",
56 Message: "Invalid number of variables in scan statement",
57 },
58 {
59 Matcher: "can't be converted to int32",
60 Message: "Error Converting to int32",
61 },
62 {
63 Matcher: "overflows int32",
64 Message: "Value overflows int32",
65 },
66 {
67 Matcher: "Scan called without calling Next",
68 Message: "Scan statement called without next",
69 },
70 {
71 Matcher: "Scan error on column index",
72 Message: "Error scanning on column index",
73 },
74 {
75 Matcher: "Rows are closed",
76 Message: "Result rows have been closed",
77 },
78 {
79 Matcher: "no Rows available",
80 Message: "No Result rows found",
81 },
82 {
83 Matcher: "statement is closed",
84 Message: "Statement has been closed",
85 },
86 {
87 Matcher: "no rows in result set",
88 Message: "No results found for query",
89 },
90 }
91 )
92
93 type SpecialCase struct {
94 Matcher string
95 Message string
96 }
97
98 type ErrorAndMsg struct {
99 Error string
100 Message string
101 }
102
103 type Error struct {
104 Err error
105 Message string
106 ErrorCode pq.ErrorCode
107 Verbose string
108 ErrorType string
109 Severity string
110 }
111
112
113 func New(msg string) *Error {
114 return Wrap(errors.New(msg))
115 }
116
117
118 func Wrap(err error) *Error {
119 e := &Error{
120 Err: err,
121 Message: err.Error(),
122 ErrorType: EdgeSQLErrType,
123 }
124 return e.unmarshalError(err)
125 }
126
127
128 func (s *Error) SetMessage(msg string) *Error {
129 s.Message = msg
130 return s
131 }
132
133
134 func (s *Error) SetErrorCode(code string) *Error {
135 s.ErrorCode = pq.ErrorCode(code)
136 return s
137 }
138
139
140 func (s *Error) SetErrorType(errorType string) *Error {
141 s.ErrorType = errorType
142 return s
143 }
144
145
146 func (s *Error) SetVerbose(verbose string) *Error {
147 s.Verbose = verbose
148 return s
149 }
150
151
152 func (s *Error) SetSeverity(severity string) *Error {
153 s.Severity = severity
154 return s
155 }
156
157
158 func (s *Error) Error() string {
159 return s.Message
160 }
161
162
163 func (s *Error) Unwrap() error {
164 if s != nil {
165 return s.Err
166 }
167 return nil
168 }
169
170
171
172 func (s *Error) unmarshalError(err error) *Error {
173 switch err {
174 case nil:
175 return s
176 default:
177 s.Err = err
178 castErr := utils.CastError(err)
179 if val, exists := pgErrorMappings[pq.ErrorCode(castErr.Code)]; exists {
180 if val.Message != "" {
181 return s.SetMessage(val.Message).SetErrorCode(castErr.Code).SetVerbose(toVerbose(castErr)).SetSeverity(castErr.Severity)
182 }
183 return s.SetMessage(val.Error).SetErrorCode(castErr.Code).SetVerbose(toVerbose(castErr)).SetSeverity(castErr.Severity)
184 }
185
186 if val, exists := sqlErrorMapping[err]; exists {
187 return s.SetMessage(val).SetErrorCode(castErr.Code).SetVerbose(toVerbose(castErr)).SetSeverity(castErr.Severity)
188 }
189
190
191 for i := range specialCases {
192 if strings.Contains(err.Error(), specialCases[i].Matcher) {
193 return s.SetMessage(specialCases[i].Message).SetErrorCode(castErr.Code).SetVerbose(toVerbose(castErr)).SetSeverity(castErr.Severity)
194 }
195 }
196 return s.SetErrorCode("Unknown")
197 }
198 }
199
200
201 func (s *Error) Extensions() map[string]interface{} {
202 return map[string]interface{}{
203 "severity": s.Severity,
204 "statusCode": s.ErrorCode,
205
206 "errorType": s.ErrorType,
207 }
208 }
209
210
211 func toVerbose(err *pgconn.PgError) string {
212 res, _ := json.Marshal(err)
213 return string(res)
214 }
215
View as plain text