From cc7b8e9c9eddeed72a73cdd6d9d217019e8a5a86 Mon Sep 17 00:00:00 2001 From: Charlie Tonneslan Date: Mon, 18 May 2026 13:28:54 -0400 Subject: [PATCH 1/3] dsn: default Net to 'tcp' in NewConfig so Addr-only configs round-trip Closes #1616. NewConfig() left Net empty, so building a Config in code with just an Addr and calling FormatDSN produced '/' (no tcp prefix, no address) instead of 'tcp(my-address)/'. Default Net to 'tcp' to match the docs, and skip the bare protocol in FormatDSN when there's no Addr so a fresh NewConfig().FormatDSN() doesn't grow a useless 'tcp' prefix. Signed-off-by: Charlie Tonneslan --- dsn.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/dsn.go b/dsn.go index 491e10f37..896928a91 100644 --- a/dsn.go +++ b/dsn.go @@ -91,6 +91,7 @@ type Option func(*Config) error // NewConfig creates a new Config and sets default values. func NewConfig() *Config { cfg := &Config{ + Net: "tcp", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, @@ -263,14 +264,14 @@ func (cfg *Config) FormatDSN() string { buf.WriteByte('@') } - // [protocol[(address)]] - if len(cfg.Net) > 0 { + // [protocol[(address)]]. Skip the bare protocol when no address is + // set, otherwise FormatDSN(NewConfig()) would always print 'tcp/' + // even though there's nothing to connect to. + if len(cfg.Net) > 0 && len(cfg.Addr) > 0 { buf.WriteString(cfg.Net) - if len(cfg.Addr) > 0 { - buf.WriteByte('(') - buf.WriteString(cfg.Addr) - buf.WriteByte(')') - } + buf.WriteByte('(') + buf.WriteString(cfg.Addr) + buf.WriteByte(')') } // /dbname From 02c50c95b551477eccdf5076da9204b3a4fca543 Mon Sep 17 00:00:00 2001 From: Charlie Tonneslan Date: Tue, 19 May 2026 05:18:02 -0400 Subject: [PATCH 2/3] dsn: default Net to tcp inline in FormatDSN instead of NewConfig Signed-off-by: Charlie Tonneslan --- dsn.go | 17 +++++++++++------ dsn_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/dsn.go b/dsn.go index 896928a91..41463a503 100644 --- a/dsn.go +++ b/dsn.go @@ -91,7 +91,6 @@ type Option func(*Config) error // NewConfig creates a new Config and sets default values. func NewConfig() *Config { cfg := &Config{ - Net: "tcp", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, Logger: defaultLogger, @@ -264,14 +263,20 @@ func (cfg *Config) FormatDSN() string { buf.WriteByte('@') } - // [protocol[(address)]]. Skip the bare protocol when no address is - // set, otherwise FormatDSN(NewConfig()) would always print 'tcp/' - // even though there's nothing to connect to. - if len(cfg.Net) > 0 && len(cfg.Addr) > 0 { - buf.WriteString(cfg.Net) + // [protocol[(address)]] + 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..57285bafc 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -330,6 +330,31 @@ 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 TestParamsAreSorted(t *testing.T) { expected := "/dbname?interpolateParams=true&foobar=baz&quux=loo" cfg := NewConfig() From 2db657fbf5a9cb1cac26aa7bec422505e69a6557 Mon Sep 17 00:00:00 2001 From: Charlie Tonneslan Date: Tue, 19 May 2026 05:36:40 -0400 Subject: [PATCH 3/3] dsn: add direct AddrWithoutNet round-trip test Signed-off-by: Charlie Tonneslan --- dsn_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dsn_test.go b/dsn_test.go index 57285bafc..0c8ac7a04 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -355,6 +355,18 @@ func TestFormatDSN_NetWithoutAddr(t *testing.T) { } } +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()