91 lines
2.0 KiB
Go
91 lines
2.0 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
mysql "github.com/go-sql-driver/mysql"
|
|
)
|
|
|
|
const (
|
|
defaultMaxOpenConns = 40
|
|
defaultMaxIdleConns = 5
|
|
defaultConnMaxLifetime = 5 * time.Minute
|
|
defaultConnMaxIdleTime = 20 * time.Second
|
|
defaultPingTimeout = 3 * time.Second
|
|
)
|
|
|
|
func Open(dsn string) (*sql.DB, error) {
|
|
if dsn == "" {
|
|
return nil, fmt.Errorf("DATABASE_DSN is required")
|
|
}
|
|
normalizedDSN := normalizeDSN(dsn)
|
|
|
|
db, err := sql.Open("mysql", normalizedDSN)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
db.SetMaxOpenConns(defaultMaxOpenConns)
|
|
db.SetMaxIdleConns(defaultMaxIdleConns)
|
|
db.SetConnMaxLifetime(defaultConnMaxLifetime)
|
|
db.SetConnMaxIdleTime(defaultConnMaxIdleTime)
|
|
|
|
if err := pingWithRetry(db, 3); err != nil {
|
|
_ = db.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|
|
func pingWithRetry(db *sql.DB, attempts int) error {
|
|
if attempts <= 0 {
|
|
attempts = 1
|
|
}
|
|
var lastErr error
|
|
for i := 0; i < attempts; i++ {
|
|
ctx, cancel := context.WithTimeout(context.Background(), defaultPingTimeout)
|
|
err := db.PingContext(ctx)
|
|
cancel()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
lastErr = err
|
|
time.Sleep(time.Duration(100*(i+1)) * time.Millisecond)
|
|
}
|
|
return lastErr
|
|
}
|
|
|
|
func normalizeDSN(dsn string) string {
|
|
cfg, err := mysql.ParseDSN(dsn)
|
|
if err != nil {
|
|
return dsn
|
|
}
|
|
if cfg.Params == nil {
|
|
cfg.Params = map[string]string{}
|
|
}
|
|
if _, ok := cfg.Params["parseTime"]; !ok {
|
|
cfg.Params["parseTime"] = "true"
|
|
}
|
|
// Work around false-positive stale-connection detection observed on some
|
|
// Docker Desktop + macOS setups that surface as "invalid connection".
|
|
if _, ok := cfg.Params["checkConnLiveness"]; !ok {
|
|
cfg.Params["checkConnLiveness"] = "false"
|
|
}
|
|
if cfg.Timeout == 0 {
|
|
cfg.Timeout = 5 * time.Second
|
|
}
|
|
if cfg.ReadTimeout == 0 {
|
|
cfg.ReadTimeout = 30 * time.Second
|
|
}
|
|
if cfg.WriteTimeout == 0 {
|
|
cfg.WriteTimeout = 30 * time.Second
|
|
}
|
|
if cfg.Collation == "" {
|
|
cfg.Collation = "utf8mb4_unicode_ci"
|
|
}
|
|
return cfg.FormatDSN()
|
|
}
|