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() }