mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
329fcc6498
Some sub-systems like btcwallet will return an error from the database, but they won't properly wrap it. As a result, we were unable to actually catch the serialization errors in the first place. To work around this, we'll now attempt to parse the error string directly.
146 lines
3.8 KiB
Go
146 lines
3.8 KiB
Go
//go:build !js && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64))
|
|
|
|
package sqldb
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgconn"
|
|
"github.com/jackc/pgerrcode"
|
|
"modernc.org/sqlite"
|
|
sqlite3 "modernc.org/sqlite/lib"
|
|
)
|
|
|
|
var (
|
|
// ErrRetriesExceeded is returned when a transaction is retried more
|
|
// than the max allowed valued without a success.
|
|
ErrRetriesExceeded = errors.New("db tx retries exceeded")
|
|
)
|
|
|
|
// MapSQLError attempts to interpret a given error as a database agnostic SQL
|
|
// error.
|
|
func MapSQLError(err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// Attempt to interpret the error as a sqlite error.
|
|
var sqliteErr *sqlite.Error
|
|
if errors.As(err, &sqliteErr) {
|
|
return parseSqliteError(sqliteErr)
|
|
}
|
|
|
|
// Attempt to interpret the error as a postgres error.
|
|
var pqErr *pgconn.PgError
|
|
if errors.As(err, &pqErr) {
|
|
return parsePostgresError(pqErr)
|
|
}
|
|
|
|
// Sometimes the error won't be properly wrapped, so we'll need to
|
|
// inspect raw error itself to detect something we can wrap properly.
|
|
// This handles a postgres variant of the error.
|
|
const postgresErrMsg = "could not serialize access"
|
|
if strings.Contains(err.Error(), postgresErrMsg) {
|
|
return &ErrSerializationError{
|
|
DBError: err,
|
|
}
|
|
}
|
|
|
|
// We'll also attempt to catch this for sqlite, that uses a slightly
|
|
// different error message. This is taken from:
|
|
// https://gitlab.com/cznic/sqlite/-/blob/v1.25.0/sqlite.go#L75.
|
|
const sqliteErrMsg = "SQLITE_BUSY"
|
|
if strings.Contains(err.Error(), sqliteErrMsg) {
|
|
return &ErrSerializationError{
|
|
DBError: err,
|
|
}
|
|
}
|
|
|
|
// Return original error if it could not be classified as a database
|
|
// specific error.
|
|
return err
|
|
}
|
|
|
|
// parsePostgresError attempts to parse a sqlite error as a database agnostic
|
|
// SQL error.
|
|
func parseSqliteError(sqliteErr *sqlite.Error) error {
|
|
switch sqliteErr.Code() {
|
|
// Handle unique constraint violation error.
|
|
case sqlite3.SQLITE_CONSTRAINT_UNIQUE:
|
|
return &ErrSQLUniqueConstraintViolation{
|
|
DBError: sqliteErr,
|
|
}
|
|
|
|
case sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY:
|
|
return &ErrSQLUniqueConstraintViolation{
|
|
DBError: sqliteErr,
|
|
}
|
|
|
|
// Database is currently busy, so we'll need to try again.
|
|
case sqlite3.SQLITE_BUSY:
|
|
return &ErrSerializationError{
|
|
DBError: sqliteErr,
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("unknown sqlite error: %w", sqliteErr)
|
|
}
|
|
}
|
|
|
|
// parsePostgresError attempts to parse a postgres error as a database agnostic
|
|
// SQL error.
|
|
func parsePostgresError(pqErr *pgconn.PgError) error {
|
|
switch pqErr.Code {
|
|
// Handle unique constraint violation error.
|
|
case pgerrcode.UniqueViolation:
|
|
return &ErrSQLUniqueConstraintViolation{
|
|
DBError: pqErr,
|
|
}
|
|
|
|
// Unable to serialize the transaction, so we'll need to try again.
|
|
case pgerrcode.SerializationFailure:
|
|
return &ErrSerializationError{
|
|
DBError: pqErr,
|
|
}
|
|
|
|
default:
|
|
return fmt.Errorf("unknown postgres error: %w", pqErr)
|
|
}
|
|
}
|
|
|
|
// ErrSQLUniqueConstraintViolation is an error type which represents a database
|
|
// agnostic SQL unique constraint violation.
|
|
type ErrSQLUniqueConstraintViolation struct {
|
|
DBError error
|
|
}
|
|
|
|
func (e ErrSQLUniqueConstraintViolation) Error() string {
|
|
return fmt.Sprintf("sql unique constraint violation: %v", e.DBError)
|
|
}
|
|
|
|
// ErrSerializationError is an error type which represents a database agnostic
|
|
// error that a transaction couldn't be serialized with other concurrent db
|
|
// transactions.
|
|
type ErrSerializationError struct {
|
|
DBError error
|
|
}
|
|
|
|
// Unwrap returns the wrapped error.
|
|
func (e ErrSerializationError) Unwrap() error {
|
|
return e.DBError
|
|
}
|
|
|
|
// Error returns the error message.
|
|
func (e ErrSerializationError) Error() string {
|
|
return e.DBError.Error()
|
|
}
|
|
|
|
// IsSerializationError returns true if the given error is a serialization
|
|
// error.
|
|
func IsSerializationError(err error) bool {
|
|
var serializationError *ErrSerializationError
|
|
return errors.As(err, &serializationError)
|
|
}
|