@@ -11,107 +11,119 @@ func AdaptiveMysqlDsn(dsn string) string {
1111 // remove optional scheme prefix
1212 dsn = strings .ReplaceAll (dsn , "mysql://" , "" )
1313
14- // ensure a valid network/address section for go-sql-driver/mysql
15- // Expected forms:
16- // user:pass@tcp(127.0.0.1:3306)/db
17- // user:pass@unix(/path/mysql.sock)/db
18- // If it's like '@(127.0.0.1:3306)' → add 'tcp'
19- // If it's like '@127.0.0.1:3306' → wrap to '@tcp(127.0.0.1:3306)'
20- at := strings .Index (dsn , "@" )
21- if at != - 1 {
22- afterAt := dsn [at + 1 :]
23- slashIdx := strings .Index (afterAt , "/" )
24- if slashIdx != - 1 {
25- addrPart := afterAt [:slashIdx ]
26- // If empty addrPart, nothing to fix
27- if addrPart != "" {
28- if strings .HasPrefix (addrPart , "(" ) {
29- // missing protocol
30- dsn = strings .Replace (dsn , "@(" , "@tcp(" , 1 )
31- } else if ! (strings .HasPrefix (addrPart , "tcp(" ) || strings .HasPrefix (addrPart , "unix(" )) {
32- // no parentheses and no protocol → wrap with tcp()
33- dsn = strings .Replace (dsn , "@" + addrPart , "@tcp(" + addrPart + ")" , 1 )
34- }
35- }
36- }
37- }
38-
39- // ensure the connection prefers utf8mb4 to avoid collation mismatch
40- // issues with MySQL 8 (e.g. mixing utf8mb3_general_ci and utf8mb4_0900_ai_ci).
41- qIdx := strings .Index (dsn , "?" )
42- if qIdx == - 1 {
43- // no query string → add charset parameter
44- return dsn + "?charset=utf8mb4"
45- }
14+ dsn = ensureNetworkAddress (dsn )
15+ return ensureCharsetAndCollation (dsn )
16+ }
4617
47- prefix := dsn [:qIdx ]
48- queryStr := dsn [qIdx + 1 :]
49- parts := strings .Split (queryStr , "&" )
50-
51- hasCharset := false
52- hasCollation := false
53- for i , p := range parts {
54- if strings .HasPrefix (p , "charset=" ) {
55- hasCharset = true
56- val := strings .TrimPrefix (p , "charset=" )
57- // split by comma and de-duplicate while ensuring utf8mb4 comes first if present/added
58- charsets := []string {}
59- for _ , cs := range strings .Split (val , "," ) {
60- cs = strings .TrimSpace (cs )
61- if cs == "" {
62- continue
63- }
64- // skip duplicates
65- dup := false
66- for _ , existing := range charsets {
67- if strings .EqualFold (existing , cs ) {
68- dup = true
69- break
70- }
71- }
72- if ! dup {
73- charsets = append (charsets , cs )
74- }
75- }
76-
77- // ensure utf8mb4 is present and at the first position
78- containsUtf8mb4 := false
79- for _ , cs := range charsets {
80- if strings .EqualFold (cs , "utf8mb4" ) {
81- containsUtf8mb4 = true
82- break
83- }
84- }
85- if ! containsUtf8mb4 {
86- charsets = append ([]string {"utf8mb4" }, charsets ... )
87- } else if len (charsets ) > 0 && ! strings .EqualFold (charsets [0 ], "utf8mb4" ) {
88- // move utf8mb4 to front
89- newOrder := []string {"utf8mb4" }
90- for _ , cs := range charsets {
91- if ! strings .EqualFold (cs , "utf8mb4" ) {
92- newOrder = append (newOrder , cs )
93- }
94- }
95- charsets = newOrder
96- }
97-
98- parts [i ] = "charset=" + strings .Join (charsets , "," )
99- break
100- }
101- if strings .HasPrefix (p , "collation=" ) {
102- hasCollation = true
103- }
104- }
18+ // helper: ensure network/address section is valid for go-sql-driver/mysql
19+ func ensureNetworkAddress (dsn string ) string {
20+ at := strings .Index (dsn , "@" )
21+ if at == - 1 {
22+ return dsn
23+ }
24+
25+ afterAt := dsn [at + 1 :]
26+ slashIdx := strings .Index (afterAt , "/" )
27+ if slashIdx == - 1 {
28+ return dsn
29+ }
30+
31+ addrPart := afterAt [:slashIdx ]
32+ if addrPart == "" {
33+ return dsn
34+ }
35+
36+ if strings .HasPrefix (addrPart , "(" ) {
37+ // missing protocol, add tcp
38+ return strings .Replace (dsn , "@(" , "@tcp(" , 1 )
39+ }
40+
41+ if strings .HasPrefix (addrPart , "tcp(" ) || strings .HasPrefix (addrPart , "unix(" ) {
42+ return dsn
43+ }
44+
45+ // no parentheses and no protocol → wrap with tcp()
46+ return strings .Replace (dsn , "@" + addrPart , "@tcp(" + addrPart + ")" , 1 )
47+ }
10548
106- if ! hasCharset {
107- parts = append (parts , "charset=utf8mb4" )
108- }
109- if ! hasCollation {
110- // default to a broadly compatible utf8mb4 collation
111- parts = append (parts , "collation=utf8mb4_general_ci" )
112- }
49+ // helper: ensure charset utf8mb4 and a reasonable collation are present
50+ func ensureCharsetAndCollation (dsn string ) string {
51+ qIdx := strings .Index (dsn , "?" )
52+ if qIdx == - 1 {
53+ return dsn + "?charset=utf8mb4"
54+ }
55+
56+ prefix := dsn [:qIdx ]
57+ queryStr := dsn [qIdx + 1 :]
58+ parts := strings .Split (queryStr , "&" )
59+
60+ hasCharset := false
61+ hasCollation := false
62+ for i , p := range parts {
63+ if strings .HasPrefix (p , "charset=" ) {
64+ hasCharset = true
65+ parts [i ] = "charset=" + normalizeCharsets (strings .TrimPrefix (p , "charset=" ))
66+ break
67+ }
68+ if strings .HasPrefix (p , "collation=" ) {
69+ hasCollation = true
70+ }
71+ }
72+
73+ if ! hasCharset {
74+ parts = append (parts , "charset=utf8mb4" )
75+ }
76+ if ! hasCollation {
77+ parts = append (parts , "collation=utf8mb4_general_ci" )
78+ }
79+
80+ return prefix + "?" + strings .Join (parts , "&" )
81+ }
11382
114- return prefix + "?" + strings .Join (parts , "&" )
83+ // normalizeCharsets deduplicates a comma-separated charset list and ensures utf8mb4 is first
84+ func normalizeCharsets (val string ) string {
85+ pieces := strings .Split (val , "," )
86+ seen := map [string ]bool {}
87+ ordered := []string {}
88+ for _ , cs := range pieces {
89+ cs = strings .TrimSpace (cs )
90+ if cs == "" {
91+ continue
92+ }
93+ lower := strings .ToLower (cs )
94+ if seen [lower ] {
95+ continue
96+ }
97+ seen [lower ] = true
98+ ordered = append (ordered , cs )
99+ }
100+
101+ // ensure utf8mb4 is present and at the front (case-insensitive)
102+ found := - 1
103+ for i , cs := range ordered {
104+ if strings .EqualFold (cs , "utf8mb4" ) {
105+ found = i
106+ break
107+ }
108+ }
109+ if found == - 1 {
110+ ordered = append ([]string {"utf8mb4" }, ordered ... )
111+ } else if found != 0 {
112+ // move to front
113+ front := []string {"utf8mb4" }
114+ for i , cs := range ordered {
115+ if i == found {
116+ continue
117+ }
118+ if strings .EqualFold (cs , "utf8mb4" ) {
119+ continue
120+ }
121+ front = append (front , cs )
122+ }
123+ ordered = front
124+ }
125+
126+ return strings .Join (ordered , "," )
115127}
116128
117129// AdaptivePostgresqlDsn convert postgres dsn to kv string
0 commit comments