Skip to content

Commit 6844171

Browse files
conncheck: allow to disable via config (go-sql-driver#1052)
* conncheck: allow to disable via config * dsn: refactor writing of params in FormatDSN
1 parent 4bdaef4 commit 6844171

File tree

4 files changed

+75
-140
lines changed

4 files changed

+75
-140
lines changed

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,17 @@ Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If
166166
Usage of the `charset` parameter is discouraged because it issues additional queries to the server.
167167
Unless you need the fallback behavior, please use `collation` instead.
168168

169+
##### `checkConnLiveness`
170+
171+
```
172+
Type: bool
173+
Valid Values: true, false
174+
Default: true
175+
```
176+
177+
On supported platforms connections retrieved from the connection pool are checked for liveness before using them. If the check fails, the respective connection is marked as bad and the query retried with another connection.
178+
`checkConnLiveness=false` disables this liveness check of connections.
179+
169180
##### `collation`
170181

171182
```

dsn.go

+45-121
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ type Config struct {
5555
AllowCleartextPasswords bool // Allows the cleartext client side plugin
5656
AllowNativePasswords bool // Allows the native password authentication method
5757
AllowOldPasswords bool // Allows the old insecure password method
58+
CheckConnLiveness bool // Check connections for liveness before using them
5859
ClientFoundRows bool // Return number of matching rows instead of rows changed
5960
ColumnsWithAlias bool // Prepend table alias to column names
6061
InterpolateParams bool // Interpolate placeholders into query string
@@ -70,6 +71,7 @@ func NewConfig() *Config {
7071
Loc: time.UTC,
7172
MaxAllowedPacket: defaultMaxAllowedPacket,
7273
AllowNativePasswords: true,
74+
CheckConnLiveness: true,
7375
}
7476
}
7577

@@ -148,6 +150,19 @@ func (cfg *Config) normalize() error {
148150
return nil
149151
}
150152

153+
func writeDSNParam(buf *bytes.Buffer, hasParam *bool, name, value string) {
154+
buf.Grow(1 + len(name) + 1 + len(value))
155+
if !*hasParam {
156+
*hasParam = true
157+
buf.WriteByte('?')
158+
} else {
159+
buf.WriteByte('&')
160+
}
161+
buf.WriteString(name)
162+
buf.WriteByte('=')
163+
buf.WriteString(value)
164+
}
165+
151166
// FormatDSN formats the given Config into a DSN string which can be passed to
152167
// the driver.
153168
func (cfg *Config) FormatDSN() string {
@@ -186,165 +201,75 @@ func (cfg *Config) FormatDSN() string {
186201
}
187202

188203
if cfg.AllowCleartextPasswords {
189-
if hasParam {
190-
buf.WriteString("&allowCleartextPasswords=true")
191-
} else {
192-
hasParam = true
193-
buf.WriteString("?allowCleartextPasswords=true")
194-
}
204+
writeDSNParam(&buf, &hasParam, "allowCleartextPasswords", "true")
195205
}
196206

197207
if !cfg.AllowNativePasswords {
198-
if hasParam {
199-
buf.WriteString("&allowNativePasswords=false")
200-
} else {
201-
hasParam = true
202-
buf.WriteString("?allowNativePasswords=false")
203-
}
208+
writeDSNParam(&buf, &hasParam, "allowNativePasswords", "false")
204209
}
205210

206211
if cfg.AllowOldPasswords {
207-
if hasParam {
208-
buf.WriteString("&allowOldPasswords=true")
209-
} else {
210-
hasParam = true
211-
buf.WriteString("?allowOldPasswords=true")
212-
}
212+
writeDSNParam(&buf, &hasParam, "allowOldPasswords", "true")
213+
}
214+
215+
if !cfg.CheckConnLiveness {
216+
writeDSNParam(&buf, &hasParam, "checkConnLiveness", "false")
213217
}
214218

215219
if cfg.ClientFoundRows {
216-
if hasParam {
217-
buf.WriteString("&clientFoundRows=true")
218-
} else {
219-
hasParam = true
220-
buf.WriteString("?clientFoundRows=true")
221-
}
220+
writeDSNParam(&buf, &hasParam, "clientFoundRows", "true")
222221
}
223222

224223
if col := cfg.Collation; col != defaultCollation && len(col) > 0 {
225-
if hasParam {
226-
buf.WriteString("&collation=")
227-
} else {
228-
hasParam = true
229-
buf.WriteString("?collation=")
230-
}
231-
buf.WriteString(col)
224+
writeDSNParam(&buf, &hasParam, "collation", col)
232225
}
233226

234227
if cfg.ColumnsWithAlias {
235-
if hasParam {
236-
buf.WriteString("&columnsWithAlias=true")
237-
} else {
238-
hasParam = true
239-
buf.WriteString("?columnsWithAlias=true")
240-
}
228+
writeDSNParam(&buf, &hasParam, "columnsWithAlias", "true")
241229
}
242230

243231
if cfg.InterpolateParams {
244-
if hasParam {
245-
buf.WriteString("&interpolateParams=true")
246-
} else {
247-
hasParam = true
248-
buf.WriteString("?interpolateParams=true")
249-
}
232+
writeDSNParam(&buf, &hasParam, "interpolateParams", "true")
250233
}
251234

252235
if cfg.Loc != time.UTC && cfg.Loc != nil {
253-
if hasParam {
254-
buf.WriteString("&loc=")
255-
} else {
256-
hasParam = true
257-
buf.WriteString("?loc=")
258-
}
259-
buf.WriteString(url.QueryEscape(cfg.Loc.String()))
236+
writeDSNParam(&buf, &hasParam, "loc", url.QueryEscape(cfg.Loc.String()))
260237
}
261238

262239
if cfg.MultiStatements {
263-
if hasParam {
264-
buf.WriteString("&multiStatements=true")
265-
} else {
266-
hasParam = true
267-
buf.WriteString("?multiStatements=true")
268-
}
240+
writeDSNParam(&buf, &hasParam, "multiStatements", "true")
269241
}
270242

271243
if cfg.ParseTime {
272-
if hasParam {
273-
buf.WriteString("&parseTime=true")
274-
} else {
275-
hasParam = true
276-
buf.WriteString("?parseTime=true")
277-
}
244+
writeDSNParam(&buf, &hasParam, "parseTime", "true")
278245
}
279246

280247
if cfg.ReadTimeout > 0 {
281-
if hasParam {
282-
buf.WriteString("&readTimeout=")
283-
} else {
284-
hasParam = true
285-
buf.WriteString("?readTimeout=")
286-
}
287-
buf.WriteString(cfg.ReadTimeout.String())
248+
writeDSNParam(&buf, &hasParam, "readTimeout", cfg.ReadTimeout.String())
288249
}
289250

290251
if cfg.RejectReadOnly {
291-
if hasParam {
292-
buf.WriteString("&rejectReadOnly=true")
293-
} else {
294-
hasParam = true
295-
buf.WriteString("?rejectReadOnly=true")
296-
}
252+
writeDSNParam(&buf, &hasParam, "rejectReadOnly", "true")
297253
}
298254

299255
if len(cfg.ServerPubKey) > 0 {
300-
if hasParam {
301-
buf.WriteString("&serverPubKey=")
302-
} else {
303-
hasParam = true
304-
buf.WriteString("?serverPubKey=")
305-
}
306-
buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
256+
writeDSNParam(&buf, &hasParam, "serverPubKey", url.QueryEscape(cfg.ServerPubKey))
307257
}
308258

309259
if cfg.Timeout > 0 {
310-
if hasParam {
311-
buf.WriteString("&timeout=")
312-
} else {
313-
hasParam = true
314-
buf.WriteString("?timeout=")
315-
}
316-
buf.WriteString(cfg.Timeout.String())
260+
writeDSNParam(&buf, &hasParam, "timeout", cfg.Timeout.String())
317261
}
318262

319263
if len(cfg.TLSConfig) > 0 {
320-
if hasParam {
321-
buf.WriteString("&tls=")
322-
} else {
323-
hasParam = true
324-
buf.WriteString("?tls=")
325-
}
326-
buf.WriteString(url.QueryEscape(cfg.TLSConfig))
264+
writeDSNParam(&buf, &hasParam, "tls", url.QueryEscape(cfg.TLSConfig))
327265
}
328266

329267
if cfg.WriteTimeout > 0 {
330-
if hasParam {
331-
buf.WriteString("&writeTimeout=")
332-
} else {
333-
hasParam = true
334-
buf.WriteString("?writeTimeout=")
335-
}
336-
buf.WriteString(cfg.WriteTimeout.String())
268+
writeDSNParam(&buf, &hasParam, "writeTimeout", cfg.WriteTimeout.String())
337269
}
338270

339271
if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
340-
if hasParam {
341-
buf.WriteString("&maxAllowedPacket=")
342-
} else {
343-
hasParam = true
344-
buf.WriteString("?maxAllowedPacket=")
345-
}
346-
buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
347-
272+
writeDSNParam(&buf, &hasParam, "maxAllowedPacket", strconv.Itoa(cfg.MaxAllowedPacket))
348273
}
349274

350275
// other params
@@ -355,16 +280,7 @@ func (cfg *Config) FormatDSN() string {
355280
}
356281
sort.Strings(params)
357282
for _, param := range params {
358-
if hasParam {
359-
buf.WriteByte('&')
360-
} else {
361-
hasParam = true
362-
buf.WriteByte('?')
363-
}
364-
365-
buf.WriteString(param)
366-
buf.WriteByte('=')
367-
buf.WriteString(url.QueryEscape(cfg.Params[param]))
283+
writeDSNParam(&buf, &hasParam, param, url.QueryEscape(cfg.Params[param]))
368284
}
369285
}
370286

@@ -491,6 +407,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
491407
return errors.New("invalid bool value: " + value)
492408
}
493409

410+
// Check connections for Liveness before using them
411+
case "checkConnLiveness":
412+
var isBool bool
413+
cfg.CheckConnLiveness, isBool = readBool(value)
414+
if !isBool {
415+
return errors.New("invalid bool value: " + value)
416+
}
417+
494418
// Switch "rowsAffected" mode
495419
case "clientFoundRows":
496420
var isBool bool

dsn_test.go

+18-18
Original file line numberDiff line numberDiff line change
@@ -22,55 +22,55 @@ var testDSNs = []struct {
2222
out *Config
2323
}{{
2424
"username:password@protocol(address)/dbname?param=value",
25-
&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
25+
&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
2626
}, {
2727
"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
28-
&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true},
28+
&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true},
2929
}, {
3030
"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
31-
&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, ColumnsWithAlias: true, MultiStatements: true},
31+
&Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, ColumnsWithAlias: true, MultiStatements: true},
3232
}, {
3333
"user@unix(/path/to/socket)/dbname?charset=utf8",
34-
&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
34+
&Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
3535
}, {
3636
"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
37-
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "true"},
37+
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "true"},
3838
}, {
3939
"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
40-
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, TLSConfig: "skip-verify"},
40+
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true, TLSConfig: "skip-verify"},
4141
}, {
4242
"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216&tls=false&allowCleartextPasswords=true&parseTime=true&rejectReadOnly=true",
43-
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
43+
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, TLSConfig: "false", AllowCleartextPasswords: true, AllowNativePasswords: true, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, CheckConnLiveness: true, ClientFoundRows: true, MaxAllowedPacket: 16777216, ParseTime: true, RejectReadOnly: true},
4444
}, {
45-
"user:password@/dbname?allowNativePasswords=false&maxAllowedPacket=0",
46-
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false},
45+
"user:password@/dbname?allowNativePasswords=false&checkConnLiveness=false&maxAllowedPacket=0",
46+
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: 0, AllowNativePasswords: false, CheckConnLiveness: false},
4747
}, {
4848
"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
49-
&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
49+
&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.Local, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
5050
}, {
5151
"/dbname",
52-
&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
52+
&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
5353
}, {
5454
"@/",
55-
&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
55+
&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
5656
}, {
5757
"/",
58-
&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
58+
&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
5959
}, {
6060
"",
61-
&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
61+
&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
6262
}, {
6363
"user:p@/ssword@/",
64-
&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
64+
&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
6565
}, {
6666
"unix/?arg=%2Fsome%2Fpath.ext",
67-
&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
67+
&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
6868
}, {
6969
"tcp(127.0.0.1)/dbname",
70-
&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
70+
&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
7171
}, {
7272
"tcp(de:ad:be:ef::ca:fe)/dbname",
73-
&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true},
73+
&Config{Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:3306", DBName: "dbname", Collation: "utf8mb4_general_ci", Loc: time.UTC, MaxAllowedPacket: defaultMaxAllowedPacket, AllowNativePasswords: true, CheckConnLiveness: true},
7474
},
7575
}
7676

packets.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func (mc *mysqlConn) writePacket(data []byte) error {
115115
if mc.cfg.ReadTimeout != 0 {
116116
err = conn.SetReadDeadline(time.Time{})
117117
}
118-
if err == nil {
118+
if err == nil && mc.cfg.CheckConnLiveness {
119119
err = connCheck(conn)
120120
}
121121
if err != nil {

0 commit comments

Comments
 (0)