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 }