Files

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
}