diff --git a/dsn.go b/dsn.go index 491e10f37..41463a503 100644 --- a/dsn.go +++ b/dsn.go @@ -264,13 +264,19 @@ func (cfg *Config) FormatDSN() string { } // [protocol[(address)]] - if len(cfg.Net) > 0 { - buf.WriteString(cfg.Net) - if len(cfg.Addr) > 0 { - buf.WriteByte('(') - buf.WriteString(cfg.Addr) - buf.WriteByte(')') + if len(cfg.Addr) > 0 { + net := cfg.Net + if net == "" { + net = "tcp" } + buf.WriteString(net) + buf.WriteByte('(') + buf.WriteString(cfg.Addr) + buf.WriteByte(')') + } else if cfg.Net != "" && cfg.Net != "tcp" { + // Preserve an explicit non-default protocol when there's no + // address, so e.g. Net="unix" still round-trips. + buf.WriteString(cfg.Net) } // /dbname diff --git a/dsn_test.go b/dsn_test.go index c4ec989c6..0c8ac7a04 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -330,6 +330,43 @@ func TestDSNUnsafeCollation(t *testing.T) { } } +func TestFormatDSN_NetWithoutAddr(t *testing.T) { + // An explicit non-default Net should still appear in the formatted + // DSN when Addr is empty, so the Config round-trips. + cases := []struct { + name string + net string + want string + }{ + {"unix without addr", "unix", "unix/"}, + // tcp is the default; dropping it is a no-op on parse. + {"tcp without addr", "tcp", "/"}, + {"empty net empty addr", "", "/"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + cfg := NewConfig() + cfg.Net = tc.net + cfg.Addr = "" + if got := cfg.FormatDSN(); got != tc.want { + t.Errorf("FormatDSN() = %q, want %q", got, tc.want) + } + }) + } +} + +func TestFormatDSN_AddrWithoutNet(t *testing.T) { + // Direct check of the bug from #1616: an Addr-only Config should + // format with the default tcp protocol so it round-trips. + cfg := NewConfig() + cfg.Addr = "myhost:3306" + got := cfg.FormatDSN() + want := "tcp(myhost:3306)/" + if got != want { + t.Errorf("FormatDSN() = %q, want %q", got, want) + } +} + func TestParamsAreSorted(t *testing.T) { expected := "/dbname?interpolateParams=true&foobar=baz&quux=loo" cfg := NewConfig()