Skip to content

Commit acdcb22

Browse files
committed
Merge pull request #419 from go-sql-driver/dsn
DSN: Add cfg.FormatDSN method
2 parents b4db83c + edc7880 commit acdcb22

File tree

5 files changed

+280
-54
lines changed

5 files changed

+280
-54
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ This has the same effect as an empty DSN string:
9393
9494
```
9595

96+
Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mysql#FormatDSN) can be used to create a DSN string by filling a struct.
97+
9698
#### Password
9799
Passwords can consist of any character. Escaping is **not** necessary.
98100

collations.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
package mysql
1010

11-
const defaultCollation byte = 33 // utf8_general_ci
11+
const defaultCollation = "utf8_general_ci"
1212

1313
// A list of available collations mapped to the internal ID.
1414
// To update this map use the following MySQL query:
@@ -237,14 +237,14 @@ var collations = map[string]byte{
237237

238238
// A blacklist of collations which is unsafe to interpolate parameters.
239239
// These multibyte encodings may contains 0x5c (`\`) in their trailing bytes.
240-
var unsafeCollations = map[byte]bool{
241-
1: true, // big5_chinese_ci
242-
13: true, // sjis_japanese_ci
243-
28: true, // gbk_chinese_ci
244-
84: true, // big5_bin
245-
86: true, // gb2312_bin
246-
87: true, // gbk_bin
247-
88: true, // sjis_bin
248-
95: true, // cp932_japanese_ci
249-
96: true, // cp932_bin
240+
var unsafeCollations = map[string]bool{
241+
"big5_chinese_ci": true,
242+
"sjis_japanese_ci": true,
243+
"gbk_chinese_ci": true,
244+
"big5_bin": true,
245+
"gb2312_bin": true,
246+
"gbk_bin": true,
247+
"sjis_bin": true,
248+
"cp932_japanese_ci": true,
249+
"cp932_bin": true,
250250
}

dsn.go

Lines changed: 209 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package mysql
1010

1111
import (
12+
"bytes"
1213
"crypto/tls"
1314
"errors"
1415
"fmt"
@@ -33,12 +34,13 @@ type Config struct {
3334
Addr string // Network address
3435
DBName string // Database name
3536
Params map[string]string // Connection parameters
37+
Collation string // Connection collation
3638
Loc *time.Location // Location for time.Time values
37-
TLS *tls.Config // TLS configuration
39+
TLSConfig string // TLS configuration name
40+
tls *tls.Config // TLS configuration
3841
Timeout time.Duration // Dial timeout
3942
ReadTimeout time.Duration // I/O read timeout
4043
WriteTimeout time.Duration // I/O write timeout
41-
Collation uint8 // Connection collation
4244

4345
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
4446
AllowCleartextPasswords bool // Allows the cleartext client side plugin
@@ -51,6 +53,194 @@ type Config struct {
5153
Strict bool // Return warnings as errors
5254
}
5355

56+
// FormatDSN formats the given Config into a DSN string which can be passed to
57+
// the driver.
58+
func (cfg *Config) FormatDSN() string {
59+
var buf bytes.Buffer
60+
61+
// [username[:password]@]
62+
if len(cfg.User) > 0 {
63+
buf.WriteString(cfg.User)
64+
if len(cfg.Passwd) > 0 {
65+
buf.WriteByte(':')
66+
buf.WriteString(cfg.Passwd)
67+
}
68+
buf.WriteByte('@')
69+
}
70+
71+
// [protocol[(address)]]
72+
if len(cfg.Net) > 0 {
73+
buf.WriteString(cfg.Net)
74+
if len(cfg.Addr) > 0 {
75+
buf.WriteByte('(')
76+
buf.WriteString(cfg.Addr)
77+
buf.WriteByte(')')
78+
}
79+
}
80+
81+
// /dbname
82+
buf.WriteByte('/')
83+
buf.WriteString(cfg.DBName)
84+
85+
// [?param1=value1&...&paramN=valueN]
86+
hasParam := false
87+
88+
if cfg.AllowAllFiles {
89+
hasParam = true
90+
buf.WriteString("?allowAllFiles=true")
91+
}
92+
93+
if cfg.AllowCleartextPasswords {
94+
if hasParam {
95+
buf.WriteString("&allowCleartextPasswords=true")
96+
} else {
97+
hasParam = true
98+
buf.WriteString("?allowCleartextPasswords=true")
99+
}
100+
}
101+
102+
if cfg.AllowOldPasswords {
103+
if hasParam {
104+
buf.WriteString("&allowOldPasswords=true")
105+
} else {
106+
hasParam = true
107+
buf.WriteString("?allowOldPasswords=true")
108+
}
109+
}
110+
111+
if cfg.ClientFoundRows {
112+
if hasParam {
113+
buf.WriteString("&clientFoundRows=true")
114+
} else {
115+
hasParam = true
116+
buf.WriteString("?clientFoundRows=true")
117+
}
118+
}
119+
120+
if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
121+
if hasParam {
122+
buf.WriteString("&collation=")
123+
} else {
124+
hasParam = true
125+
buf.WriteString("?collation=")
126+
}
127+
buf.WriteString(col)
128+
}
129+
130+
if cfg.ColumnsWithAlias {
131+
if hasParam {
132+
buf.WriteString("&columnsWithAlias=true")
133+
} else {
134+
hasParam = true
135+
buf.WriteString("?columnsWithAlias=true")
136+
}
137+
}
138+
139+
if cfg.InterpolateParams {
140+
if hasParam {
141+
buf.WriteString("&interpolateParams=true")
142+
} else {
143+
hasParam = true
144+
buf.WriteString("?interpolateParams=true")
145+
}
146+
}
147+
148+
if cfg.Loc != time.UTC && cfg.Loc != nil {
149+
if hasParam {
150+
buf.WriteString("&loc=")
151+
} else {
152+
hasParam = true
153+
buf.WriteString("?loc=")
154+
}
155+
buf.WriteString(url.QueryEscape(cfg.Loc.String()))
156+
}
157+
158+
if cfg.MultiStatements {
159+
if hasParam {
160+
buf.WriteString("&multiStatements=true")
161+
} else {
162+
hasParam = true
163+
buf.WriteString("?multiStatements=true")
164+
}
165+
}
166+
167+
if cfg.ParseTime {
168+
if hasParam {
169+
buf.WriteString("&parseTime=true")
170+
} else {
171+
hasParam = true
172+
buf.WriteString("?parseTime=true")
173+
}
174+
}
175+
176+
if cfg.ReadTimeout > 0 {
177+
if hasParam {
178+
buf.WriteString("&readTimeout=")
179+
} else {
180+
hasParam = true
181+
buf.WriteString("?readTimeout=")
182+
}
183+
buf.WriteString(cfg.ReadTimeout.String())
184+
}
185+
186+
if cfg.Strict {
187+
if hasParam {
188+
buf.WriteString("&strict=true")
189+
} else {
190+
hasParam = true
191+
buf.WriteString("?strict=true")
192+
}
193+
}
194+
195+
if cfg.Timeout > 0 {
196+
if hasParam {
197+
buf.WriteString("&timeout=")
198+
} else {
199+
hasParam = true
200+
buf.WriteString("?timeout=")
201+
}
202+
buf.WriteString(cfg.Timeout.String())
203+
}
204+
205+
if len(cfg.TLSConfig) > 0 {
206+
if hasParam {
207+
buf.WriteString("&tls=")
208+
} else {
209+
hasParam = true
210+
buf.WriteString("?tls=")
211+
}
212+
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
213+
}
214+
215+
if cfg.WriteTimeout > 0 {
216+
if hasParam {
217+
buf.WriteString("&writeTimeout=")
218+
} else {
219+
hasParam = true
220+
buf.WriteString("?writeTimeout=")
221+
}
222+
buf.WriteString(cfg.WriteTimeout.String())
223+
}
224+
225+
// other params
226+
if cfg.Params != nil {
227+
for param, value := range cfg.Params {
228+
if hasParam {
229+
buf.WriteByte('&')
230+
} else {
231+
hasParam = true
232+
buf.WriteByte('?')
233+
}
234+
235+
buf.WriteString(param)
236+
buf.WriteByte('=')
237+
buf.WriteString(url.QueryEscape(value))
238+
}
239+
}
240+
241+
return buf.String()
242+
}
243+
54244
// ParseDSN parses the DSN string to a Config
55245
func ParseDSN(dsn string) (cfg *Config, err error) {
56246
// New config with some default values
@@ -196,15 +386,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
196386

197387
// Collation
198388
case "collation":
199-
collation, ok := collations[value]
200-
if !ok {
201-
// Note possibility for false negatives:
202-
// could be triggered although the collation is valid if the
203-
// collations map does not contain entries the server supports.
204-
err = errors.New("unknown collation")
205-
return
206-
}
207-
cfg.Collation = collation
389+
cfg.Collation = value
208390
break
209391

210392
case "columnsWithAlias":
@@ -279,24 +461,32 @@ func parseDSNParams(cfg *Config, params string) (err error) {
279461
boolValue, isBool := readBool(value)
280462
if isBool {
281463
if boolValue {
282-
cfg.TLS = &tls.Config{}
464+
cfg.TLSConfig = "true"
465+
cfg.tls = &tls.Config{}
466+
} else {
467+
cfg.TLSConfig = "false"
283468
}
284-
} else if value, err := url.QueryUnescape(value); err != nil {
285-
return fmt.Errorf("invalid value for TLS config name: %v", err)
469+
} else if vl := strings.ToLower(value); vl == "skip-verify" {
470+
cfg.TLSConfig = vl
471+
cfg.tls = &tls.Config{InsecureSkipVerify: true}
286472
} else {
287-
if strings.ToLower(value) == "skip-verify" {
288-
cfg.TLS = &tls.Config{InsecureSkipVerify: true}
289-
} else if tlsConfig, ok := tlsConfigRegister[value]; ok {
473+
name, err := url.QueryUnescape(value)
474+
if err != nil {
475+
return fmt.Errorf("invalid value for TLS config name: %v", err)
476+
}
477+
478+
if tlsConfig, ok := tlsConfigRegister[name]; ok {
290479
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
291480
host, _, err := net.SplitHostPort(cfg.Addr)
292481
if err == nil {
293482
tlsConfig.ServerName = host
294483
}
295484
}
296485

297-
cfg.TLS = tlsConfig
486+
cfg.TLSConfig = name
487+
cfg.tls = tlsConfig
298488
} else {
299-
return errors.New("invalid value / unknown config name: " + value)
489+
return errors.New("invalid value / unknown config name: " + name)
300490
}
301491
}
302492

0 commit comments

Comments
 (0)