67 lines
1.6 KiB
Go
67 lines
1.6 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"database/sql/driver"
|
|
"errors"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/go-sql-driver/mysql"
|
|
)
|
|
|
|
// IsConnLost reports whether err means the database was UNREACHABLE (network
|
|
// down, server gone, dead pooled connection) as opposed to a legitimate
|
|
// server-side SQL error (constraint violation, bad data, syntax).
|
|
//
|
|
// This distinction is the linchpin of the offline safety net: a QSO is parked in
|
|
// the local ADIF outbox ONLY on a connection loss. If we queued on any error, a
|
|
// genuine data bug would silently vanish into the file instead of being
|
|
// reported — the worst possible outcome.
|
|
//
|
|
// A *mysql.MySQLError means the SERVER answered and rejected us, so it is never
|
|
// a connection loss, whatever its code.
|
|
func IsConnLost(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
// The server responded → a real SQL error, not a lost link.
|
|
var me *mysql.MySQLError
|
|
if errors.As(err, &me) {
|
|
return false
|
|
}
|
|
if errors.Is(err, driver.ErrBadConn) || errors.Is(err, sql.ErrConnDone) || errors.Is(err, sql.ErrTxDone) {
|
|
return true
|
|
}
|
|
var ne net.Error
|
|
if errors.As(err, &ne) {
|
|
return true
|
|
}
|
|
var oe *net.OpError
|
|
if errors.As(err, &oe) {
|
|
return true
|
|
}
|
|
// The MySQL driver reports a few of these as plain strings.
|
|
s := strings.ToLower(err.Error())
|
|
for _, p := range []string{
|
|
"invalid connection",
|
|
"bad connection",
|
|
"connection refused",
|
|
"connection reset",
|
|
"broken pipe",
|
|
"no such host",
|
|
"i/o timeout",
|
|
"dial tcp",
|
|
"unexpected eof",
|
|
"driver: bad connection",
|
|
"can't connect",
|
|
"network is unreachable",
|
|
"host is unreachable",
|
|
} {
|
|
if strings.Contains(s, p) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|